From 2324bf5d054ec96e8d49c6e8927590483619c9e4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Sep 2021 19:52:37 +0200 Subject: [PATCH 001/172] Mavericks 2: include the libs and make it compile --- build.gradle | 2 +- dependencies.gradle | 4 +++- vector/build.gradle | 7 +++++-- .../main/java/im/vector/app/VectorApplication.kt | 3 ++- .../im/vector/app/core/extensions/Parcelable.kt | 3 ++- .../vector/app/core/platform/VectorBaseActivity.kt | 5 ++++- .../platform/VectorBaseBottomSheetDialogFragment.kt | 13 ------------- .../im/vector/app/core/platform/VectorViewModel.kt | 2 +- .../vector/app/features/call/VectorCallActivity.kt | 4 ++-- .../createdirect/CreateDirectRoomActivity.kt | 2 +- .../keysbackup/settings/KeysBackupManageActivity.kt | 2 +- .../vector/app/features/home/HomeDetailFragment.kt | 6 +++--- .../room/detail/JoinReplacementRoomBottomSheet.kt | 2 +- .../room/detail/widget/RoomWidgetsBottomSheet.kt | 2 +- .../app/features/home/room/list/RoomListFragment.kt | 2 +- .../vector/app/features/login/LoginWebFragment.kt | 3 ++- .../vector/app/features/login2/LoginWebFragment2.kt | 3 ++- .../settings/joinrule/RoomJoinRuleActivity.kt | 2 +- .../features/signout/soft/SoftLogoutViewModel.kt | 1 - .../spaces/explore/SpaceDirectoryFragment.kt | 2 +- .../features/spaces/manage/SpaceAddRoomFragment.kt | 10 +++++----- .../app/features/usercode/UserCodeActivity.kt | 2 +- .../app/features/userdirectory/UserListFragment.kt | 2 +- .../vector/app/features/widgets/WidgetActivity.kt | 9 ++++----- 24 files changed, 45 insertions(+), 48 deletions(-) diff --git a/build.gradle b/build.gradle index 49c3e07ece..c2ed966dae 100644 --- a/build.gradle +++ b/build.gradle @@ -73,7 +73,7 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { // Warnings are potential errors, so stop ignoring them // You can override by passing `-PallWarningsAsErrors=false` in the command line - kotlinOptions.allWarningsAsErrors = project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean() + kotlinOptions.allWarningsAsErrors = false //project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean() } } diff --git a/dependencies.gradle b/dependencies.gradle index 37486b8d91..e3e77e4cd8 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -19,6 +19,7 @@ def moshi = "1.12.0" def lifecycle = "2.2.0" def rxBinding = "3.1.0" def epoxy = "4.6.2" +def mavericks = "2.3.0" def glide = "4.12.0" def bigImageViewer = "1.8.1" def jjwt = "0.11.2" @@ -98,7 +99,8 @@ ext.libs = [ 'epoxyGlide' : "com.airbnb.android:epoxy-glide-preloading:$epoxy", 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", - 'mvrx' : "com.airbnb.android:mvrx:1.5.1" + 'mavericks' : "com.airbnb.android:mavericks:$mavericks", + 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks" ], mockk : [ 'mockk' : "io.mockk:mockk:$mockk", diff --git a/vector/build.gradle b/vector/build.gradle index f2cb2831bd..a9c6a407d8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -376,7 +376,8 @@ dependencies { implementation libs.airbnb.epoxyGlide kapt libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging - implementation libs.airbnb.mvrx + implementation libs.airbnb.mavericks + implementation libs.airbnb.mavericksRx // Work implementation libs.androidx.work @@ -458,7 +459,9 @@ dependencies { implementation "androidx.emoji:emoji-appcompat:1.1.0" - implementation 'com.github.BillCarsonFr:JsonViewer:0.6' + implementation ('com.github.BillCarsonFr:JsonViewer:0.6'){ + exclude group: 'com.airbnb.android' + } // WebRTC // org.webrtc:google-webrtc is for development purposes only diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index be8447d409..3e4cf3ff85 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -34,6 +34,7 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController +import com.airbnb.mvrx.Mavericks import com.facebook.stetho.Stetho import com.gabrielittner.threetenbp.LazyThreeTen import com.vanniktech.emoji.EmojiManager @@ -137,7 +138,7 @@ class VectorApplication : } logInfo() LazyThreeTen.init(this) - + Mavericks.initialize(this) EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager)) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt b/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt index 6ca0144601..87f5ac84f7 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt @@ -18,8 +18,9 @@ package im.vector.app.core.extensions import android.os.Bundle import android.os.Parcelable +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MvRx fun Parcelable?.toMvRxBundle(): Bundle? { - return this?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } } + return this?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index dc19520865..e9973e82b8 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -40,6 +40,7 @@ import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding +import com.airbnb.mvrx.MvRxView import com.bumptech.glide.util.Util import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.snackbar.Snackbar @@ -88,7 +89,7 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis -abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { +abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector, MvRxView { /* ========================================================================================== * View * ========================================================================================== */ @@ -576,6 +577,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasSc open fun initUiAndData() = Unit + override fun invalidate() = Unit + @StringRes open fun getTitleRes() = -1 diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index b9b5bc8ca5..9adaed288a 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -29,7 +29,6 @@ import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRxView -import com.airbnb.mvrx.MvRxViewId import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment @@ -48,8 +47,6 @@ import java.util.concurrent.TimeUnit */ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView { - private val mvrxViewIdProperty = MvRxViewId() - final override val mvrxViewId: String by mvrxViewIdProperty private lateinit var screenComponent: ScreenComponent /* ========================================================================================== @@ -133,11 +130,6 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected open fun injectWith(injector: ScreenComponent) = Unit - override fun onCreate(savedInstanceState: Bundle?) { - mvrxViewIdProperty.restoreFrom(savedInstanceState) - super.onCreate(savedInstanceState) - } - override fun onResume() { super.onResume() Timber.i("onResume BottomSheet ${javaClass.simpleName}") @@ -154,11 +146,6 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe } } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - mvrxViewIdProperty.saveTo(outState) - } - override fun onStart() { super.onStart() // This ensures that invalidate() is called for static screens that don't diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index d6f43beaf7..c066dacc08 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -28,7 +28,7 @@ import io.reactivex.Observable import io.reactivex.Single abstract class VectorViewModel(initialState: S) - : BaseMvRxViewModel(initialState, false) { + : BaseMvRxViewModel(initialState) { interface Factory { fun create(state: S): BaseMvRxViewModel diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index f71dcc0635..64abdef72a 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -138,7 +138,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro renderState(it) } - callViewModel.asyncSubscribe(this, VectorCallViewState::callState) { + callViewModel.asyncSubscribe(VectorCallViewState::callState) { if (it is CallState.Ended) { handleCallEnded(it) } @@ -152,7 +152,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } .disposeOnDestroy() - callViewModel.selectSubscribe(this, VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> + callViewModel.selectSubscribe(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> if (isVideoCall) { if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_camera_and_audio)) { setupRenderersIfNeeded() diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 68123d5e82..752a7d0d83 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -102,7 +102,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac ) ) } - viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { + viewModel.selectSubscribe(CreateDirectRoomViewState::createAndInviteState) { renderCreateAndInviteState(it) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt index d78fa37d62..c03488ab5d 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt @@ -55,7 +55,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() { } // Observe the deletion of keys backup - viewModel.selectSubscribe(this, KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete -> + viewModel.selectSubscribe(KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete -> when (asyncDelete) { is Fail -> { updateWaitingView(null) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 627f4b4581..b6d628071b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -134,7 +134,7 @@ class HomeDetailFragment @Inject constructor( views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId() } - viewModel.selectSubscribe(this, HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> + viewModel.selectSubscribe(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> when (roomGroupingMethod) { is RoomGroupingMethod.ByLegacyGroup -> { onGroupChange(roomGroupingMethod.groupSummary) @@ -145,11 +145,11 @@ class HomeDetailFragment @Inject constructor( } } - viewModel.selectSubscribe(this, HomeDetailViewState::currentTab) { currentTab -> + viewModel.selectSubscribe(HomeDetailViewState::currentTab) { currentTab -> updateUIForTab(currentTab) } - viewModel.selectSubscribe(this, HomeDetailViewState::showDialPadTab) { showDialPadTab -> + viewModel.selectSubscribe(HomeDetailViewState::showDialPadTab) { showDialPadTab -> updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt index 54681366e0..9cfca9ecd2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt @@ -61,7 +61,7 @@ class JoinReplacementRoomBottomSheet : } } - viewModel.selectSubscribe(this, RoomDetailViewState::joinUpgradedRoomAsync) { joinState -> + viewModel.selectSubscribe(RoomDetailViewState::joinUpgradedRoomAsync) { joinState -> when (joinState) { // it should never be Uninitialized Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt index 4e77873522..3b305abbe4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt @@ -65,7 +65,7 @@ class RoomWidgetsBottomSheet : views.bottomSheetTitle.textSize = 20f views.bottomSheetTitle.setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) epoxyController.listener = this - roomDetailViewModel.asyncSubscribe(this, RoomDetailViewState::activeRoomWidgets) { + roomDetailViewModel.asyncSubscribe(RoomDetailViewState::activeRoomWidgets) { epoxyController.setData(it) } } 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 fdfe171439..2df90a6c16 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 @@ -123,7 +123,7 @@ class RoomListFragment @Inject constructor( .subscribe { handleQuickActions(it) } .disposeOnDestroyView() - roomListViewModel.selectSubscribe(viewLifecycleOwner, RoomListViewState::roomMembershipChanges) { ms -> + roomListViewModel.selectSubscribe(RoomListViewState::roomMembershipChanges) { ms -> // it's for invites local echo adapterInfosList.filter { it.section.notifyOfLocalEcho } .onEach { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt index f01bf01029..67bc1ddd1c 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt @@ -52,6 +52,8 @@ class LoginWebFragment @Inject constructor( private val assetReader: AssetReader ) : AbstractLoginFragment() { + val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding { return FragmentLoginWebBinding.inflate(inflater, container, false) } @@ -233,7 +235,6 @@ class LoginWebFragment @Inject constructor( private fun notifyViewModel(credentials: Credentials) { if (isForSessionRecovery) { - val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials)) } else { loginViewModel.handle(LoginAction.WebLoginSuccess(credentials)) diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt index b1e3eaf098..7ea938e310 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt @@ -57,6 +57,8 @@ class LoginWebFragment2 @Inject constructor( return FragmentLoginWebBinding.inflate(inflater, container, false) } + val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() + private var isWebViewLoaded = false private var isForSessionRecovery = false @@ -234,7 +236,6 @@ class LoginWebFragment2 @Inject constructor( private fun notifyViewModel(credentials: Credentials) { if (isForSessionRecovery) { - val softLogoutViewModel: SoftLogoutViewModel by activityViewModel() softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials)) } else { loginViewModel.handle(LoginAction2.WebLoginSuccess(credentials)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index 21c39ad49d..dc0d06ac86 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -78,7 +78,7 @@ class RoomJoinRuleActivity : VectorBaseActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.selectSubscribe(this, RoomJoinRuleChooseRestrictedState::updatingStatus) { + viewModel.selectSubscribe(RoomJoinRuleChooseRestrictedState::updatingStatus) { when (it) { Uninitialized -> { // nop diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index f49527bd1d..f17847fdfd 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.signout.soft -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 3af66cbb6b..b03d1e0b14 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -110,7 +110,7 @@ class SpaceDirectoryFragment @Inject constructor( views.spaceDirectoryList.configureWith(epoxyController) epoxyVisibilityTracker.attach(views.spaceDirectoryList) - viewModel.selectSubscribe(this, SpaceDirectoryState::canAddRooms) { + viewModel.selectSubscribe(SpaceDirectoryState::canAddRooms) { invalidateOptionsMenu() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index e192ec3c88..cea4d0059e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -91,17 +91,17 @@ class SpaceAddRoomFragment @Inject constructor( invalidateOptionsMenu() } - viewModel.selectSubscribe(this, SpaceAddRoomsState::spaceName) { + viewModel.selectSubscribe(SpaceAddRoomsState::spaceName) { views.appBarSpaceInfo.text = it }.disposeOnDestroyView() - viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) { + viewModel.selectSubscribe(SpaceAddRoomsState::ignoreRooms) { spaceEpoxyController.ignoreRooms = it roomEpoxyController.ignoreRooms = it dmEpoxyController.ignoreRooms = it }.disposeOnDestroyView() - viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) { + viewModel.selectSubscribe(SpaceAddRoomsState::isSaving) { if (it is Loading) { sharedViewModel.handle(SpaceManagedSharedAction.ShowLoading) } else { @@ -109,11 +109,11 @@ class SpaceAddRoomFragment @Inject constructor( } }.disposeOnDestroyView() - viewModel.selectSubscribe(this, SpaceAddRoomsState::shouldShowDMs) { + viewModel.selectSubscribe(SpaceAddRoomsState::shouldShowDMs) { dmEpoxyController.disabled = !it }.disposeOnDestroyView() - viewModel.selectSubscribe(this, SpaceAddRoomsState::onlyShowSpaces) { + viewModel.selectSubscribe(SpaceAddRoomsState::onlyShowSpaces) { spaceEpoxyController.disabled = !it roomEpoxyController.disabled = it views.createNewRoom.text = if (it) getString(R.string.create_space) else getString(R.string.create_new_room) diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 0771a5d238..064905f188 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -68,7 +68,7 @@ class UserCodeActivity : VectorBaseActivity(), showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) } - sharedViewModel.selectSubscribe(this, UserCodeState::mode) { mode -> + sharedViewModel.selectSubscribe(UserCodeState::mode) { mode -> when (mode) { UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index f251a672b8..843ead0eb4 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -85,7 +85,7 @@ class UserListFragment @Inject constructor( views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } - viewModel.selectSubscribe(this, UserListViewState::pendingSelections) { + viewModel.selectSubscribe(UserListViewState::pendingSelections) { renderSelectedUsers(it) } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 9072957a95..31895dda16 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -19,10 +19,10 @@ package im.vector.app.features.widgets import android.app.Activity import android.content.Context import android.content.Intent -import com.google.android.material.appbar.MaterialToolbar import androidx.core.view.isVisible import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel +import com.google.android.material.appbar.MaterialToolbar import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment @@ -33,7 +33,6 @@ import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomShee import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewEvents import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewModel import im.vector.app.features.widgets.permissions.RoomWidgetPermissionViewState - import org.matrix.android.sdk.api.session.events.model.Content import java.io.Serializable import javax.inject.Inject @@ -103,7 +102,7 @@ class WidgetActivity : VectorBaseActivity(), } } - viewModel.selectSubscribe(this, WidgetViewState::status) { ws -> + viewModel.selectSubscribe(WidgetViewState::status) { ws -> when (ws) { WidgetStatus.UNKNOWN -> { } @@ -125,11 +124,11 @@ class WidgetActivity : VectorBaseActivity(), } } - viewModel.selectSubscribe(this, WidgetViewState::widgetName) { name -> + viewModel.selectSubscribe(WidgetViewState::widgetName) { name -> supportActionBar?.title = name } - viewModel.selectSubscribe(this, WidgetViewState::canManageWidgets) { + viewModel.selectSubscribe(WidgetViewState::canManageWidgets) { invalidateOptionsMenu() } } From f6b81b36d08584e7aeeb3db7ff151dc7873bfe1d Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 10:08:06 +0200 Subject: [PATCH 002/172] Mavericks 2: switch from MvRxState to MavericksState --- .../ElementFeature/root/src/app_package/ViewState.kt.ftl | 4 ++-- .../java/im/vector/app/core/platform/VectorViewModel.kt | 6 +++--- .../app/core/ui/bottomsheet/BottomSheetGenericState.kt | 4 ++-- .../app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt | 4 ++-- .../attachments/preview/AttachmentsPreviewViewState.kt | 4 ++-- .../main/java/im/vector/app/features/auth/ReAuthState.kt | 4 ++-- .../java/im/vector/app/features/call/VectorCallViewState.kt | 4 ++-- .../app/features/call/conference/JitsiCallViewState.kt | 4 ++-- .../app/features/call/transfer/CallTransferViewState.kt | 4 ++-- .../app/features/contactsbook/ContactsBookViewState.kt | 4 ++-- .../app/features/createdirect/CreateDirectRoomViewState.kt | 4 ++-- .../keysbackup/settings/KeysBackupSettingViewState.kt | 4 ++-- .../features/crypto/quads/SharedSecureStorageViewModel.kt | 4 ++-- .../app/features/crypto/recover/BootstrapViewState.kt | 4 ++-- .../crypto/verification/VerificationBottomSheetViewModel.kt | 4 ++-- .../choose/VerificationChooseMethodViewModel.kt | 4 ++-- .../conclusion/VerificationConclusionViewModel.kt | 4 ++-- .../verification/emoji/VerificationEmojiCodeViewModel.kt | 4 ++-- .../im/vector/app/features/devtools/RoomDevToolViewState.kt | 4 ++-- .../vector/app/features/discovery/DiscoverySettingsState.kt | 4 ++-- .../app/features/discovery/change/SetIdentityServerState.kt | 4 ++-- .../im/vector/app/features/home/HomeActivityViewState.kt | 4 ++-- .../java/im/vector/app/features/home/HomeDetailViewState.kt | 4 ++-- .../vector/app/features/home/PromoteRestrictedViewModel.kt | 4 ++-- .../features/home/UnknownDeviceDetectorSharedViewModel.kt | 4 ++-- .../app/features/home/UnreadMessagesSharedViewModel.kt | 4 ++-- .../features/home/room/breadcrumbs/BreadcrumbsViewState.kt | 4 ++-- .../app/features/home/room/detail/RoomDetailViewState.kt | 4 ++-- .../app/features/home/room/detail/search/SearchViewState.kt | 4 ++-- .../home/room/detail/timeline/action/MessageActionState.kt | 4 ++-- .../detail/timeline/edithistory/ViewEditHistoryViewState.kt | 4 ++-- .../detail/timeline/reactions/ViewReactionsViewModel.kt | 4 ++-- .../home/room/detail/upgrade/MigrateRoomViewState.kt | 4 ++-- .../vector/app/features/home/room/list/RoomListViewState.kt | 4 ++-- .../features/homeserver/HomeServerCapabilitiesViewState.kt | 4 ++-- .../app/features/invite/InviteUsersToRoomViewState.kt | 4 ++-- .../java/im/vector/app/features/login/LoginViewState.kt | 4 ++-- .../vector/app/features/login/terms/LoginTermsViewState.kt | 4 ++-- .../java/im/vector/app/features/login2/LoginViewState2.kt | 4 ++-- .../app/features/login2/created/AccountCreatedViewState.kt | 4 ++-- .../app/features/matrixto/MatrixToBottomSheetState.kt | 4 ++-- .../java/im/vector/app/features/rageshake/BugReportState.kt | 4 ++-- .../app/features/reactions/EmojiSearchResultViewModel.kt | 4 ++-- .../app/features/room/RequireActiveMembershipViewState.kt | 4 ++-- .../app/features/roomdirectory/PublicRoomsViewState.kt | 4 ++-- .../roomdirectory/createroom/CreateRoomViewState.kt | 4 ++-- .../roomdirectory/picker/RoomDirectoryPickerViewState.kt | 4 ++-- .../roomdirectory/roompreview/RoomPreviewViewState.kt | 4 ++-- .../roommemberprofile/RoomMemberProfileViewState.kt | 4 ++-- .../devices/DeviceListBottomSheetViewModel.kt | 4 ++-- .../vector/app/features/roomprofile/RoomProfileViewState.kt | 4 ++-- .../app/features/roomprofile/alias/RoomAliasViewState.kt | 4 ++-- .../roomprofile/alias/detail/RoomAliasBottomSheetState.kt | 4 ++-- .../roomprofile/banned/RoomBannedMemberListViewState.kt | 4 ++-- .../features/roomprofile/members/RoomMemberListViewState.kt | 4 ++-- .../notifications/RoomNotificationSettingsViewState.kt | 4 ++-- .../roomprofile/permissions/RoomPermissionsViewState.kt | 4 ++-- .../features/roomprofile/settings/RoomSettingsViewState.kt | 4 ++-- .../joinrule/advanced/RoomJoinRuleChooseRestrictedState.kt | 4 ++-- .../features/roomprofile/uploads/RoomUploadsViewState.kt | 4 ++-- .../account/deactivation/DeactivateAccountViewModel.kt | 4 ++-- .../settings/crosssigning/CrossSigningSettingsViewState.kt | 4 ++-- .../devices/DeviceVerificationInfoBottomSheetViewState.kt | 4 ++-- .../app/features/settings/devices/DevicesViewModel.kt | 4 ++-- .../app/features/settings/devtools/AccountDataViewModel.kt | 4 ++-- .../settings/devtools/GossipingEventsPaperTrailViewModel.kt | 4 ++-- .../features/settings/devtools/KeyRequestListViewModel.kt | 4 ++-- .../app/features/settings/devtools/KeyRequestViewModel.kt | 4 ++-- .../settings/homeserver/HomeServerSettingsViewState.kt | 4 ++-- .../app/features/settings/ignored/IgnoredUsersViewModel.kt | 4 ++-- .../app/features/settings/locale/LocalePickerViewState.kt | 4 ++-- .../app/features/settings/push/PushGatewaysViewModel.kt | 4 ++-- .../vector/app/features/settings/push/PushRulesViewModel.kt | 4 ++-- .../settings/threepids/ThreePidsSettingsViewState.kt | 4 ++-- .../im/vector/app/features/share/IncomingShareViewState.kt | 4 ++-- .../vector/app/features/signout/soft/SoftLogoutViewState.kt | 4 ++-- .../im/vector/app/features/spaces/SpaceListViewState.kt | 4 ++-- .../java/im/vector/app/features/spaces/SpaceMenuState.kt | 4 ++-- .../vector/app/features/spaces/create/CreateSpaceState.kt | 4 ++-- .../app/features/spaces/explore/SpaceDirectoryState.kt | 4 ++-- .../features/spaces/invite/SpaceInviteBottomSheetState.kt | 4 ++-- .../app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt | 4 ++-- .../vector/app/features/spaces/manage/SpaceAddRoomsState.kt | 4 ++-- .../app/features/spaces/manage/SpaceManageRoomViewState.kt | 4 ++-- .../app/features/spaces/manage/SpaceManageViewState.kt | 4 ++-- .../app/features/spaces/people/SpacePeopleViewState.kt | 4 ++-- .../vector/app/features/spaces/preview/SpacePreviewState.kt | 4 ++-- .../vector/app/features/spaces/share/ShareSpaceViewState.kt | 4 ++-- .../im/vector/app/features/terms/ReviewTermsViewState.kt | 4 ++-- .../java/im/vector/app/features/usercode/UserCodeState.kt | 4 ++-- .../vector/app/features/userdirectory/UserListViewState.kt | 4 ++-- .../java/im/vector/app/features/widgets/WidgetViewState.kt | 4 ++-- .../widgets/permissions/RoomWidgetPermissionViewState.kt | 4 ++-- .../features/workers/signout/ServerBackupStatusViewModel.kt | 4 ++-- .../app/features/workers/signout/SignoutCheckViewModel.kt | 4 ++-- vector/src/test/java/im/vector/app/test/Extensions.kt | 4 ++-- 96 files changed, 193 insertions(+), 193 deletions(-) diff --git a/tools/templates/ElementFeature/root/src/app_package/ViewState.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/ViewState.kt.ftl index 55e1f5f549..0acb3f78da 100644 --- a/tools/templates/ElementFeature/root/src/app_package/ViewState.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/ViewState.kt.ftl @@ -1,5 +1,5 @@ package ${escapeKotlinIdentifiers(packageName)} -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState -data class ${viewStateClass}() : MvRxState \ No newline at end of file +data class ${viewStateClass}() : MavericksState \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index c066dacc08..1a77a00fab 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -20,17 +20,17 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Success import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.PublishDataSource import io.reactivex.Observable import io.reactivex.Single -abstract class VectorViewModel(initialState: S) +abstract class VectorViewModel(initialState: S) : BaseMvRxViewModel(initialState) { - interface Factory { + interface Factory { fun create(state: S): BaseMvRxViewModel } diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt index 38c81a7ef6..0f57600b13 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt @@ -16,6 +16,6 @@ package im.vector.app.core.ui.bottomsheet -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState -abstract class BottomSheetGenericState : MvRxState +abstract class BottomSheetGenericState : MavericksState diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt index 6cc2c4c981..bde87a5588 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt @@ -16,12 +16,12 @@ package im.vector.app.core.ui.bottomsheet -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -abstract class BottomSheetGenericViewModel(initialState: State) : +abstract class BottomSheetGenericViewModel(initialState: State) : VectorViewModel(initialState) { override fun handle(action: EmptyAction) { diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewState.kt index cee2a7ddd7..8b8208fc4f 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewState.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewState.kt @@ -17,14 +17,14 @@ package im.vector.app.features.attachments.preview -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.content.ContentAttachmentData data class AttachmentsPreviewViewState( val attachments: List, val currentAttachmentIndex: Int = 0, val sendImagesWithOriginalSize: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: AttachmentsPreviewArgs) : this(attachments = args.attachments) } diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthState.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthState.kt index 075ef758b8..733516c691 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthState.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthState.kt @@ -16,7 +16,7 @@ package im.vector.app.features.auth -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState data class ReAuthState( val title: String? = null, @@ -25,7 +25,7 @@ data class ReAuthState( val ssoFallbackPageWasShown: Boolean = false, val lastErrorCode: String? = null, val resultKeyStoreAlias: String = "" -) : MvRxState { +) : MavericksState { constructor(args: ReAuthActivity.Args) : this( args.title, args.session, diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt index a351806e1a..2d33cbf9b9 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.call import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.call.audio.CallAudioManager import org.matrix.android.sdk.api.session.call.CallState @@ -43,7 +43,7 @@ data class VectorCallViewState( val formattedDuration: String = "", val canOpponentBeTransferred: Boolean = false, val transferee: TransfereeState = TransfereeState.NoTransferee -) : MvRxState { +) : MavericksState { sealed class TransfereeState { object NoTransferee : TransfereeState() diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt index 1fd04542e0..d4c70d7333 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.call.conference import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -26,4 +26,4 @@ data class JitsiCallViewState( val widgetId: String = "", val enableVideo: Boolean = false, val widget: Async = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt index 2b29d9f6f2..babda1dd19 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewState.kt @@ -16,11 +16,11 @@ package im.vector.app.features.call.transfer -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState data class CallTransferViewState( val callId: String -) : MvRxState { +) : MavericksState { constructor(args: CallTransferArgs) : this(callId = args.callId) } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt index d2ee684c4d..d4f279787d 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.contactsbook import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import im.vector.app.core.contacts.MappedContact data class ContactsBookViewState( @@ -36,4 +36,4 @@ data class ContactsBookViewState( val identityServerUrl: String? = null, // User consent to perform lookup (send emails to the identity server) val userConsent: Boolean = false -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt index 27a8cc0a97..41366a7110 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt @@ -17,9 +17,9 @@ package im.vector.app.features.createdirect import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class CreateDirectRoomViewState( val createAndInviteState: Async = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt index 874e3765ca..f5541d532e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.crypto.keysbackup.settings import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.internal.crypto.keysbackup.model.KeysBackupVersionTrust @@ -27,4 +27,4 @@ data class KeysBackupSettingViewState(val keysBackupVersionTrust: Async = Uninitialized) - : MvRxState + : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index b4ff9ab22c..3f3b3a751a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -55,7 +55,7 @@ data class SharedSecureStorageViewState( val activeDeviceCount: Int = 0, val showResetAllAction: Boolean = false, val userId: String = "" -) : MvRxState { +) : MavericksState { enum class Step { EnterPassphrase, EnterKey, diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewState.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewState.kt index 4e94ddf0bf..b8c9f10b49 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewState.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.crypto.recover import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import com.nulabinc.zxcvbn.Strength import im.vector.app.core.platform.WaitingViewData @@ -34,4 +34,4 @@ data class BootstrapViewState( val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, val initializationWaitingViewData: WaitingViewData? = null, val recoverySaveFileProcess: Async = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 8e21412715..b2f259772e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -79,7 +79,7 @@ data class VerificationBottomSheetViewState( val quadSContainsSecrets: Boolean = true, val quadSHasBeenReset: Boolean = false, val hasAnyOtherSession: Boolean = false -) : MvRxState +) : MavericksState class VerificationBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: VerificationBottomSheetViewState, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index 7b9acd2f57..79878fcf83 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.crypto.verification.choose import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -43,7 +43,7 @@ data class VerificationChooseMethodViewState( val sasModeAvailable: Boolean = false, val isMe: Boolean = false, val canCrossSign: Boolean = false -) : MvRxState +) : MavericksState class VerificationChooseMethodViewModel @AssistedInject constructor( @Assisted initialState: VerificationChooseMethodViewState, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index 50912af1c2..93133184e9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -15,7 +15,7 @@ */ package im.vector.app.features.crypto.verification.conclusion -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.app.core.platform.EmptyAction @@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf data class VerificationConclusionViewState( val conclusionState: ConclusionState = ConclusionState.CANCELLED, val isSelfVerification: Boolean = false -) : MvRxState +) : MavericksState enum class ConclusionState { SUCCESS, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt index 44f0e752c8..c8dd9b34d8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt @@ -19,7 +19,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -48,7 +48,7 @@ data class VerificationEmojiCodeViewState( val emojiDescription: Async> = Uninitialized, val decimalDescription: Async = Uninitialized, val isWaitingFromOther: Boolean = false -) : MvRxState +) : MavericksState class VerificationEmojiCodeViewModel @AssistedInject constructor( @Assisted initialState: VerificationEmojiCodeViewState, diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewState.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewState.kt index 885de005b0..a5b7db9821 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewState.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.devtools import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.events.model.Event @@ -31,7 +31,7 @@ data class RoomDevToolViewState( val editedContent: String? = null, val modalLoading: Async = Uninitialized, val sendEventDraft: SendEventDraft? = null -) : MvRxState { +) : MavericksState { constructor(args: RoomDevToolActivity.Args) : this(roomId = args.roomId, displayMode = Mode.Root) diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt index 21fbcf1ca7..46c516331f 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.discovery import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class DiscoverySettingsState( @@ -27,4 +27,4 @@ data class DiscoverySettingsState( // Can be true if terms are updated val termsNotSigned: Boolean = false, val userConsent: Boolean = false -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerState.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerState.kt index 4c2f437e07..558cc4dcf7 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerState.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerState.kt @@ -16,10 +16,10 @@ package im.vector.app.features.discovery.change -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState data class SetIdentityServerState( val homeServerUrl: String = "", // Will contain the default identity server url if any val defaultIdentityServerUrl: String? = null -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt index f3bddaf0d2..68131e8569 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt @@ -16,9 +16,9 @@ package im.vector.app.features.home -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.initsync.SyncStatusService data class HomeActivityViewState( val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index 4022a0d9fb..7665dd41e5 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home import androidx.annotation.StringRes import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.RoomGroupingMethod @@ -43,7 +43,7 @@ data class HomeDetailViewState( val incrementalSyncStatus: SyncStatusService.Status.IncrementalSyncStatus = SyncStatusService.Status.IncrementalSyncIdle, val pushCounter: Int = 0, val showDialPadTab: Boolean = false -) : MvRxState +) : MavericksState sealed class HomeTab(@StringRes val titleRes: Int) { data class RoomList(val displayMode: RoomListDisplayMode) : HomeTab(displayMode.titleRes) diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt index ae7b495aa2..da29d4c5fc 100644 --- a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -41,7 +41,7 @@ data class ActiveSpaceViewState( val isInSpaceMode: Boolean = false, val activeSpaceSummary: RoomSummary? = null, val canUserManageSpace: Boolean = false -) : MvRxState +) : MavericksState class PromoteRestrictedViewModel @AssistedInject constructor( @Assisted initialState: ActiveSpaceViewState, diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 988b4fbabe..7b25f20f77 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -48,7 +48,7 @@ import java.util.concurrent.TimeUnit data class UnknownDevicesState( val myMatrixItem: MatrixItem.UserItem? = null, val unknownSessions: Async> = Uninitialized -) : MvRxState +) : MavericksState data class DeviceDetectionInfo( val deviceInfo: DeviceInfo, diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index ac983a9f0c..1e2b9e542a 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit data class UnreadMessagesState( val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0), val otherSpacesUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0) -) : MvRxState +) : MavericksState data class CountInfo( val homeCount: RoomAggregateNotificationCount, diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewState.kt index f067591e83..f2971db093 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewState.kt @@ -17,10 +17,10 @@ package im.vector.app.features.home.room.breadcrumbs import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary data class BreadcrumbsViewState( val asyncBreadcrumbs: Async> = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 8f4ad97b72..6dee6d2b00 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event @@ -90,7 +90,7 @@ data class RoomDetailViewState( val isAllowedToStartWebRTCCall: Boolean = true, val hasFailedSending: Boolean = false, val jitsiState: JitsiState = JitsiState() -) : MvRxState { +) : MavericksState { constructor(args: RoomDetailArgs) : this( roomId = args.roomId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt index 41fecbb5e2..4b272b6c4c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.search import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.search.EventAndSender @@ -32,7 +32,7 @@ data class SearchViewState( val roomId: String = "", // Current pagination request val asyncSearchRequest: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: SearchArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt index caee485aba..c1c145040e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.action import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.core.extensions.canReact import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData @@ -50,7 +50,7 @@ data class MessageActionState( val actions: List = emptyList(), val expendedReportContentMenu: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions() -) : MvRxState { +) : MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt index 62f08eef7f..d367dff835 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.edithistory import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import org.matrix.android.sdk.api.session.events.model.Event @@ -27,7 +27,7 @@ data class ViewEditHistoryViewState( val roomId: String, val isOriginalAReply: Boolean = false, val editList: Async> = Uninitialized) - : MvRxState { + : MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 35d208e30e..6a78161d28 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.reactions import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -42,7 +42,7 @@ data class DisplayReactionsViewState( val eventId: String, val roomId: String, val mapReactionKeyToMemberList: Async> = Uninitialized) - : MvRxState { + : MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt index e3ea98682b..6a155aa22a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.upgrade import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class MigrateRoomViewState( @@ -36,7 +36,7 @@ data class MigrateRoomViewState( val upgradingProgressIndeterminate: Boolean = true, val migrationReason: MigrateRoomBottomSheet.MigrationReason = MigrateRoomBottomSheet.MigrationReason.MANUAL, val autoMigrateMembersAndParents: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: MigrateRoomBottomSheet.Args) : this( roomId = args.roomId, newVersion = args.newVersion, 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 68a8b9e515..46ff6c242b 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 @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.list import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.RoomGroupingMethod import im.vector.app.features.home.RoomListDisplayMode @@ -31,7 +31,7 @@ data class RoomListViewState( val asyncSuggestedRooms: Async> = Uninitialized, val currentUserName: String? = null, val currentRoomGrouping: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) } diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewState.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewState.kt index 14d19b2e6a..d7ced5e632 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewState.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewState.kt @@ -16,10 +16,10 @@ package im.vector.app.features.homeserver -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities data class HomeServerCapabilitiesViewState( val capabilities: HomeServerCapabilities = HomeServerCapabilities(), val isE2EByDefault: Boolean = true -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewState.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewState.kt index cd688f097b..37f0861a83 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewState.kt @@ -17,13 +17,13 @@ package im.vector.app.features.invite import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class InviteUsersToRoomViewState( val roomId: String, val inviteState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: InviteUsersToRoomArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt index 187d39d9eb..8d3a3040ff 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.login import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -55,7 +55,7 @@ data class LoginViewState( // Supported types for the login. We cannot use a sealed class for LoginType because it is not serializable val loginModeSupportedTypes: List = emptyList(), val knownCustomHomeServersUrls: List = emptyList() -) : MvRxState { +) : MavericksState { fun isLoading(): Boolean { return asyncLoginAction is Loading diff --git a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt index c09c76efc3..3641b443e3 100644 --- a/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/login/terms/LoginTermsViewState.kt @@ -16,12 +16,12 @@ package im.vector.app.features.login.terms -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.internal.auth.registration.LocalizedFlowDataLoginTerms data class LoginTermsViewState( val localizedFlowDataLoginTermsChecked: List -) : MvRxState { +) : MavericksState { fun check(data: LocalizedFlowDataLoginTerms) { localizedFlowDataLoginTermsChecked.find { it.localizedFlowDataLoginTerms == data }?.checked = true } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt index d629c6dfe7..276d1111bb 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt @@ -17,7 +17,7 @@ package im.vector.app.features.login2 import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.PersistState import com.airbnb.mvrx.Uninitialized import im.vector.app.core.extensions.toReducedUrl @@ -55,7 +55,7 @@ data class LoginViewState2( // From database val knownCustomHomeServersUrls: List = emptyList() -) : MvRxState { +) : MavericksState { // Pending user identifier fun userIdentifier(): String { diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt index 80211b3da2..0ae60e910c 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.login2.created import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.util.MatrixItem @@ -26,4 +26,4 @@ data class AccountCreatedViewState( val isLoading: Boolean = false, val currentUser: Async = Uninitialized, val hasBeenModified: Boolean = false -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 40213dc0ee..0fff2495f9 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.matrixto import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser @@ -31,7 +31,7 @@ data class MatrixToBottomSheetState( val startChattingState: Async = Uninitialized, val roomPeekResult: Async = Uninitialized, val peopleYouKnow: Async> = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( deepLink = args.matrixToLink, diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportState.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportState.kt index a5019115fb..37a379104b 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportState.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportState.kt @@ -16,8 +16,8 @@ package im.vector.app.features.rageshake -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState data class BugReportState( val serverVersion: String = "" -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt index 8e12dd2cca..04ee95f68e 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.reactions import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -32,7 +32,7 @@ import kotlinx.coroutines.launch data class EmojiSearchResultViewState( val query: String = "", val results: List = emptyList() -) : MvRxState +) : MavericksState class EmojiSearchResultViewModel @AssistedInject constructor( @Assisted initialState: EmojiSearchResultViewState, diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt index 2ca68b5e8b..dbf399bdf2 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt @@ -16,13 +16,13 @@ package im.vector.app.features.room -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import im.vector.app.features.roommemberprofile.RoomMemberProfileArgs import im.vector.app.features.roomprofile.RoomProfileArgs data class RequireActiveMembershipViewState( val roomId: String? = null -) : MvRxState { +) : MavericksState { // No constructor for RoomDetailArgs because of intent for Shortcut diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt index fdab72caba..3b9995a13d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomdirectory import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom @@ -36,4 +36,4 @@ data class PublicRoomsViewState( // keys are room alias or roomId val changeMembershipStates: Map = emptyMap(), val roomDirectoryData: RoomDirectoryData = RoomDirectoryData() -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt index db56a19904..f605150ee0 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomdirectory.createroom import android.net.Uri import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -39,7 +39,7 @@ data class CreateRoomViewState( val supportsRestricted: Boolean = false, val aliasLocalPart: String? = null, val isSubSpace: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: CreateRoomArgs) : this( roomName = args.initialName, diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt index 5cdee862ab..56aa807e41 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomdirectory.picker import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomdirectory.RoomDirectoryServer import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol @@ -30,4 +30,4 @@ data class RoomDirectoryPickerViewState( val addServerAsync: Async = Uninitialized, // computed val directories: List = emptyList() -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt index 75286ea24f..725498c4fc 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomdirectory.roompreview import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomdirectory.JoinState import org.matrix.android.sdk.api.session.permalinks.PermalinkData @@ -48,7 +48,7 @@ data class RoomPreviewViewState( val fromEmailInvite: PermalinkData.RoomEmailInviteLink? = null, // used only if it's an email invite val isEmailBoundToAccount: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomPreviewData) : this( roomId = args.roomId, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt index 5c2751f0dc..a4730153c2 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roommemberprofile import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.room.model.Membership @@ -42,7 +42,7 @@ data class RoomMemberProfileViewState( val asyncMembership: Async = Uninitialized, val hasReadReceipt: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions() -) : MvRxState { +) : MavericksState { constructor(args: RoomMemberProfileArgs) : this(userId = args.userId, roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 7d31a5c811..15c62af35b 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roommemberprofile.devices import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -42,7 +42,7 @@ data class DeviceListViewState( val memberCrossSigningKey: MXCrossSigningInfo? = null, val cryptoDevices: Async> = Loading(), val selectedDevice: CryptoDeviceInfo? = null -) : MvRxState +) : MavericksState class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted private val initialState: DeviceListViewState, @Assisted private val args: DeviceListBottomSheet.Args, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt index 999b6540bd..14b415c53a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -35,7 +35,7 @@ data class RoomProfileViewState( val recommendedRoomVersion: String? = null, val canUpgradeRoom: Boolean = false, val isTombstoned: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index f6341f4f64..aabdb7530f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.alias import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility @@ -35,7 +35,7 @@ data class RoomAliasViewState( val publishManuallyState: AddAliasState = AddAliasState.Hidden, val localAliases: Async> = Uninitialized, val newLocalAliasState: AddAliasState = AddAliasState.Closed -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt index a61075cef6..1accc85e45 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.alias.detail -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState data class RoomAliasBottomSheetState( val alias: String, @@ -25,7 +25,7 @@ data class RoomAliasBottomSheetState( val isMainAlias: Boolean, val isLocal: Boolean, val canEditCanonicalAlias: Boolean -) : MvRxState { +) : MavericksState { constructor(args: RoomAliasBottomSheetArgs) : this( alias = args.alias, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt index 2861b30222..e36de58f97 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.banned import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary @@ -30,7 +30,7 @@ data class RoomBannedMemberListViewState( val filter: String = "", val onGoingModerationAction: List = emptyList(), val canUserBan: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt index 63d07cc4dd..d736260f10 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.members import androidx.annotation.StringRes import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.platform.GenericIdArgs @@ -36,7 +36,7 @@ data class RoomMemberListViewState( val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = Uninitialized, val actionsPermissions: ActionPermissions = ActionPermissions() -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt index 72e61fba70..236c5c36fc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.notifications import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.app.features.home.room.list.actions.RoomListActionsArgs @@ -30,7 +30,7 @@ data class RoomNotificationSettingsViewState( val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val notificationState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) constructor(args: RoomListActionsArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt index ce38ab87e5..9a5ac4c19f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.permissions import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent @@ -30,7 +30,7 @@ data class RoomPermissionsViewState( val showAdvancedPermissions: Boolean = false, val currentPowerLevelsContent: Async = Uninitialized, val isLoading: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 403836b268..122e0034c6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.settings import android.net.Uri import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.resources.StringProvider @@ -46,7 +46,7 @@ data class RoomSettingsViewState( val actionPermissions: ActionPermissions = ActionPermissions(), val supportsRestricted: Boolean = false, val canUpgradeToRestricted: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedState.kt index 8a107ce8f1..157f53b56e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.settings.joinrule.advanced import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.settings.joinrule.JoinRulesOptionSupport @@ -45,6 +45,6 @@ data class RoomJoinRuleChooseRestrictedState( val restrictedSupportedByThisVersion: Boolean = false, val restrictedVersionNeeded: String? = null, val didSwitchToReplacementRoom: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewState.kt index b85e4f04de..a2b5aa99df 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.uploads import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -33,7 +33,7 @@ data class RoomUploadsViewState( val asyncEventsRequest: Async = Uninitialized, // True if more result are available server side val hasMore: Boolean = true -) : MvRxState { +) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) } diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt index 80c64220c0..bc030775e0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.account.deactivation import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -42,7 +42,7 @@ import kotlin.coroutines.resumeWithException data class DeactivateAccountViewState( val dummy: Boolean = false -) : MvRxState +) : MavericksState class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private val initialState: DeactivateAccountViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewState.kt index 8a371ada68..9e349253ca 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewState.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.crosssigning -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo data class CrossSigningSettingsViewState( @@ -24,4 +24,4 @@ data class CrossSigningSettingsViewState( val xSigningIsEnableInAccount: Boolean = false, val xSigningKeysAreTrusted: Boolean = false, val xSigningKeyCanSign: Boolean = true -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt index a736b0442c..e320642ed0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.devices import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo @@ -30,7 +30,7 @@ data class DeviceVerificationInfoBottomSheetViewState( val isMine: Boolean = false, val hasOtherSessions: Boolean = false, val isRecoverySetup: Boolean = false -) : MvRxState { +) : MavericksState { val canVerifySession: Boolean get() = hasOtherSessions || isRecoverySetup 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 c48b08e806..ec2d6dd53e 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 @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -75,7 +75,7 @@ data class DevicesViewState( val request: Async = Uninitialized, val hasAccountCrossSigning: Boolean = false, val accountCrossSigningIsTrusted: Boolean = false -) : MvRxState +) : MavericksState data class DeviceFullInfo( val deviceInfo: DeviceInfo, diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index d50caea579..c42e2440c1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.settings.devtools import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -36,7 +36,7 @@ import org.matrix.android.sdk.rx.rx data class AccountDataViewState( val accountData: Async> = Uninitialized -) : MvRxState +) : MavericksState class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index 325538ee5e..25834e5580 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -20,7 +20,7 @@ import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -36,7 +36,7 @@ import org.matrix.android.sdk.rx.asObservable data class GossipingEventsPaperTrailState( val events: Async> = Uninitialized -) : MvRxState +) : MavericksState class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index c0a791233f..b4e872000e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -39,7 +39,7 @@ import org.matrix.android.sdk.rx.asObservable data class KeyRequestListViewState( val incomingRequests: Async> = Uninitialized, val outgoingRoomKeyRequests: Async> = Uninitialized -) : MvRxState +) : MavericksState class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index e7a56ef9df..e1554cb0a4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -48,7 +48,7 @@ sealed class KeyRequestEvents : VectorViewEvents { data class KeyRequestViewState( val exporting: Async = Uninitialized -) : MvRxState +) : MavericksState class KeyRequestViewModel @AssistedInject constructor( @Assisted initialState: KeyRequestViewState, diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt index 87ad637ca5..3acd79d768 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeServerSettingsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.homeserver import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.federation.FederationVersion import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -27,4 +27,4 @@ data class HomeServerSettingsViewState( val homeserverClientServerApiUrl: String = "", val homeServerCapabilities: HomeServerCapabilities = HomeServerCapabilities(), val federationVersion: Async = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index aa00f71542..34d2bc4821 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -39,7 +39,7 @@ import org.matrix.android.sdk.rx.rx data class IgnoredUsersViewState( val ignoredUsers: List = emptyList(), val unIgnoreRequest: Async = Uninitialized -) : MvRxState +) : MavericksState sealed class IgnoredUsersAction : VectorViewModelAction { data class UnIgnore(val userId: String) : IgnoredUsersAction() diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt index 64c95468f0..8cb5978393 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.locale import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.settings.VectorLocale import java.util.Locale @@ -25,4 +25,4 @@ import java.util.Locale data class LocalePickerViewState( val currentLocale: Locale = VectorLocale.applicationLocale, val locales: Async> = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt index 9a47fa2a15..509e38b0f3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.settings.push import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -35,7 +35,7 @@ import org.matrix.android.sdk.rx.RxSession data class PushGatewayViewState( val pushGateways: Async> = Uninitialized -) : MvRxState +) : MavericksState class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt index 64ddc275eb..dd21cfb5c9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt @@ -15,7 +15,7 @@ */ package im.vector.app.features.settings.push -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.app.core.di.HasScreenInjector @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.pushrules.rest.PushRule data class PushRulesViewState( val rules: List = emptyList() -) : MvRxState +) : MavericksState class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel(initialState) { diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt index b080c06cbd..dbc81fd8f3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.threepids import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.core.utils.ReadOnceTrue import org.matrix.android.sdk.api.session.identity.ThreePid @@ -30,4 +30,4 @@ data class ThreePidsSettingsViewState( val msisdnValidationRequests: Map> = emptyMap(), val editTextReinitiator: ReadOnceTrue = ReadOnceTrue(), val msisdnValidationReinitiator: Map = emptyMap() -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewState.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewState.kt index 751dc999a2..620709a515 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewState.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.share import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -27,4 +27,4 @@ data class IncomingShareViewState( val filteredRoomSummaries: Async> = Uninitialized, val selectedRoomIds: Set = emptySet(), val isInMultiSelectionMode: Boolean = false -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewState.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewState.kt index ccc186aba7..d209d13176 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewState.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.signout.soft import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.app.features.login.LoginMode @@ -32,7 +32,7 @@ data class SoftLogoutViewState( val userDisplayName: String, val hasUnsavedKeys: Boolean, val enteredPassword: String = "" -) : MvRxState { +) : MavericksState { fun isLoading(): Boolean { return asyncLoginAction is Loading diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt index 7482f4881e..e46bcc30b1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.RoomGroupingMethod import org.matrix.android.sdk.api.session.group.model.GroupSummary @@ -35,4 +35,4 @@ data class SpaceListViewState( val legacyGroups: List? = null, val expandedStates: Map = emptyMap(), val homeAggregateCount : RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0) -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuState.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuState.kt index 395fcc9df1..7ac844d297 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -30,7 +30,7 @@ data class SpaceMenuState( val isLastAdmin: Boolean = false, val leaveMode: LeaveMode = LeaveMode.LEAVE_NONE, val leavingState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: SpaceBottomSheetSettingsArgs) : this(spaceId = args.spaceId) enum class LeaveMode { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt index 6fb5853269..f7433dca7c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.spaces.create import android.net.Uri import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class CreateSpaceState( @@ -38,7 +38,7 @@ data class CreateSpaceState( val emailValidationResult: Map? = null, val creationResult: Async = Uninitialized, val canInviteByMail: Boolean = false -) : MvRxState { +) : MavericksState { enum class Step { ChooseType, diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt index 33b494075d..1467b69659 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.explore import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo @@ -39,7 +39,7 @@ data class SpaceDirectoryState( // cached room summaries of known rooms, we use it because computed room name would be better using it val knownRoomSummaries: List = emptyList(), val paginationStatus: Map> = emptyMap() -) : MvRxState { +) : MavericksState { constructor(args: SpaceDirectoryArgs) : this( spaceId = args.spaceId ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetState.kt index d712cf9e8a..5963d491d5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.invite import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.user.model.User @@ -29,7 +29,7 @@ data class SpaceInviteBottomSheetState( val peopleYouKnow: Async> = Uninitialized, val joinActionState: Async = Uninitialized, val rejectActionState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: SpaceInviteBottomSheet.Args) : this( spaceId = args.spaceId ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt index f7802d2a31..b8dcd3f7a2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvanceViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.leave import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.spaces.SpaceBottomSheetSettingsArgs import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -29,7 +29,7 @@ data class SpaceLeaveAdvanceViewState( val selectedRooms: List = emptyList(), val currentFilter: String = "", val leaveState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: SpaceBottomSheetSettingsArgs) : this( spaceId = args.spaceId ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsState.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsState.kt index e941d04b22..bec8b905d8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.manage import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class SpaceAddRoomsState( @@ -30,7 +30,7 @@ data class SpaceAddRoomsState( val shouldShowDMs: Boolean = false, val onlyShowSpaces: Boolean = false // val selectionList: Map = emptyMap() -) : MvRxState { +) : MavericksState { constructor(args: SpaceManageArgs) : this( spaceId = args.spaceId, onlyShowSpaces = args.manageType == ManageType.AddRoomsOnlySpaces diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewState.kt index 34173828a7..211d3645f5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.manage import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.space.SpaceHierarchyData @@ -32,7 +32,7 @@ data class SpaceManageRoomViewState( val paginationStatus: Async = Uninitialized, // cached room summaries of known rooms, we use it because computed room name would be better using it val knownRoomSummaries: List = emptyList() -) : MvRxState { +) : MavericksState { constructor(args: SpaceManageArgs) : this( spaceId = args.spaceId ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt index 35596f0884..82abc823c3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageViewState.kt @@ -16,7 +16,7 @@ package im.vector.app.features.spaces.manage -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState enum class ManageType { AddRooms, @@ -27,7 +27,7 @@ enum class ManageType { data class SpaceManageViewState( val spaceId: String = "", val manageType: ManageType -) : MvRxState { +) : MavericksState { constructor(args: SpaceManageArgs) : this( spaceId = args.spaceId, manageType = args.manageType diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt index ea322e3fbd..b24636a9d4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewState.kt @@ -17,14 +17,14 @@ package im.vector.app.features.spaces.people import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.core.platform.GenericIdArgs data class SpacePeopleViewState( val spaceId: String, val createAndInviteState: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: GenericIdArgs) : this( spaceId = args.id ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt index d31d05cf96..14f9a45d68 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.preview import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class SpacePreviewState( @@ -28,7 +28,7 @@ data class SpacePreviewState( val spaceInfo: Async = Uninitialized, val childInfoList: Async> = Uninitialized, val inviteTermination: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(args: SpacePreviewArgs) : this(idOrAlias = args.idOrAlias) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewState.kt index 97606e9506..826719b762 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.spaces.share import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -27,7 +27,7 @@ data class ShareSpaceViewState( val canInviteByMxId: Boolean = false, val canShareLink: Boolean = false, val postCreation: Boolean = false -) : MvRxState { +) : MavericksState { constructor(args: ShareSpaceBottomSheet.Args) : this( spaceId = args.spaceId, postCreation = args.postCreation diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewState.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewState.kt index 20b09f0cd7..e87fd9620c 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewState.kt @@ -17,9 +17,9 @@ package im.vector.app.features.terms import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized data class ReviewTermsViewState( val termsList: Async> = Uninitialized -) : MvRxState +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt index c26da7c0a4..a323609344 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt @@ -16,7 +16,7 @@ package im.vector.app.features.usercode -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import org.matrix.android.sdk.api.util.MatrixItem data class UserCodeState( @@ -24,7 +24,7 @@ data class UserCodeState( val matrixItem: MatrixItem? = null, val shareLink: String? = null, val mode: Mode = Mode.SHOW -) : MvRxState { +) : MavericksState { sealed class Mode { object SHOW : Mode() object SCAN : Mode() diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index b66d36c5f0..e389bbefee 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.userdirectory import androidx.paging.PagedList import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.core.contacts.MappedContact import org.matrix.android.sdk.api.session.user.model.User @@ -35,7 +35,7 @@ data class UserListViewState( val configuredIdentityServer: String? = null, private val showInviteActions: Boolean, val showContactBookAction: Boolean -) : MvRxState { +) : MavericksState { constructor(args: UserListFragmentArgs) : this( excludedUserIds = args.excludedUserIds, diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt index 845ee81a2d..2d98f734dd 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewState.kt @@ -18,7 +18,7 @@ package im.vector.app.features.widgets import androidx.annotation.StringRes import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.R import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -52,7 +52,7 @@ data class WidgetViewState( val widgetName: String = "", val canManageWidgets: Boolean = false, val asyncWidget: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(widgetArgs: WidgetArgs) : this( widgetKind = widgetArgs.kind, diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewState.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewState.kt index 1cc14a91c2..79f9b8cee3 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewState.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.widgets.permissions import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.widgets.WidgetArgs import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -26,7 +26,7 @@ data class RoomWidgetPermissionViewState( val roomId: String, val widgetId: String?, val permissionData: Async = Uninitialized -) : MvRxState { +) : MavericksState { constructor(widgetArgs: WidgetArgs) : this( roomId = widgetArgs.roomId, diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 6d95911bdb..619f14e559 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.MutableLiveData import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -49,7 +49,7 @@ import java.util.concurrent.TimeUnit data class ServerBackupStatusViewState( val bannerState: Async = Uninitialized -) : MvRxState +) : MavericksState /** * The state representing the view diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index df7a826b48..7916caca66 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -51,7 +51,7 @@ data class SignoutCheckViewState( val crossSigningSetupAllKeysKnown: Boolean = false, val keysBackupState: KeysBackupState = KeysBackupState.Unknown, val hasBeenExportedToFile: Async = Uninitialized -) : MvRxState +) : MavericksState class SignoutCheckViewModel @AssistedInject constructor( @Assisted initialState: SignoutCheckViewState, diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 0d89208b2e..f2a087fd52 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -16,7 +16,7 @@ package im.vector.app.test -import com.airbnb.mvrx.MvRxState +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 @@ -25,7 +25,7 @@ import org.amshove.kluent.shouldBeEqualTo fun String.trimIndentOneLine() = trimIndent().replace("\n", "") -fun VectorViewModel.test(): ViewModelTest { +fun VectorViewModel.test(): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } val viewEvents = viewEvents.observe().test() return ViewModelTest(state, viewEvents) From 9337e0e76da5ca14e305ee54d982d5d585648f5b Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 15:03:02 +0200 Subject: [PATCH 003/172] Mavericks 2: create sdk flow module --- matrix-sdk-android-flow/.gitignore | 1 + matrix-sdk-android-flow/build.gradle | 49 +++++++ matrix-sdk-android-flow/consumer-rules.pro | 0 matrix-sdk-android-flow/proguard-rules.pro | 21 +++ .../sdk/flow/ExampleInstrumentedTest.kt | 40 +++++ .../src/main/AndroidManifest.xml | 5 + .../org/matrix/android/sdk/flow/FlowRoom.kt | 83 +++++++++++ .../matrix/android/sdk/flow/FlowSession.kt | 138 ++++++++++++++++++ .../matrix/android/sdk/flow/OptionalFlow.kt | 32 ++++ .../android/sdk/flow/ExampleUnitTest.kt | 33 +++++ settings.gradle | 1 + vector/build.gradle | 1 + 12 files changed, 404 insertions(+) create mode 100644 matrix-sdk-android-flow/.gitignore create mode 100644 matrix-sdk-android-flow/build.gradle create mode 100644 matrix-sdk-android-flow/consumer-rules.pro create mode 100644 matrix-sdk-android-flow/proguard-rules.pro create mode 100644 matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt create mode 100644 matrix-sdk-android-flow/src/main/AndroidManifest.xml create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt create mode 100644 matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt diff --git a/matrix-sdk-android-flow/.gitignore b/matrix-sdk-android-flow/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/matrix-sdk-android-flow/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle new file mode 100644 index 0000000000..4aecec169b --- /dev/null +++ b/matrix-sdk-android-flow/build.gradle @@ -0,0 +1,49 @@ + +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 31 + + defaultConfig { + minSdk 21 + targetSdk 31 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation project(":matrix-sdk-android") + implementation libs.androidx.appCompat + + implementation libs.jetbrains.kotlinStdlibJdk7 + implementation libs.jetbrains.coroutinesCore + implementation libs.jetbrains.coroutinesAndroid + implementation libs.androidx.lifecycleLivedata + + // Paging + implementation libs.androidx.pagingRuntimeKtx + + // Logging + implementation libs.jakewharton.timber + +} \ No newline at end of file diff --git a/matrix-sdk-android-flow/consumer-rules.pro b/matrix-sdk-android-flow/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/matrix-sdk-android-flow/proguard-rules.pro b/matrix-sdk-android-flow/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/matrix-sdk-android-flow/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..41799955a4 --- /dev/null +++ b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("org.matrix.android.sdk.flow.test", appContext.packageName) + } +} diff --git a/matrix-sdk-android-flow/src/main/AndroidManifest.xml b/matrix-sdk-android-flow/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..2392c0bfcb --- /dev/null +++ b/matrix-sdk-android-flow/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..a3e476ce08 --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.lifecycle.asFlow +import kotlinx.coroutines.flow.Flow +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +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 +import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState +import org.matrix.android.sdk.api.session.room.send.UserDraft +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.util.Optional + +class FlowRoom(private val room: Room) { + + fun liveRoomSummary(): Flow> { + return room.getRoomSummaryLive().asFlow() + } + + fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { + return room.getRoomMembersLive(queryParams).asFlow() + } + + fun liveAnnotationSummary(eventId: String): Flow> { + return room.getEventAnnotationsSummaryLive(eventId).asFlow() + } + + fun liveTimelineEvent(eventId: String): Flow> { + return room.getTimeLineEventLive(eventId).asFlow() + } + + fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { + return room.getStateEventLive(eventType, stateKey).asFlow() + } + + fun liveStateEvents(eventTypes: Set): Flow> { + return room.getStateEventsLive(eventTypes).asFlow() + } + + fun liveReadMarker(): Flow> { + return room.getReadMarkerLive().asFlow() + } + + fun liveReadReceipt(): Flow> { + return room.getMyReadReceiptLive().asFlow() + } + + fun liveEventReadReceipts(eventId: String): Flow> { + return room.getEventReadReceiptsLive(eventId).asFlow() + } + + fun liveDraft(): Flow> { + return room.getDraftLive().asFlow() + } + + fun liveNotificationState(): Flow { + return room.getLiveRoomNotificationState().asFlow() + } +} + +fun Room.flow(): FlowRoom { + return FlowRoom(this) +} 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 new file mode 100644 index 0000000000..affcd4a65d --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.lifecycle.asFlow +import androidx.paging.PagedList +import kotlinx.coroutines.flow.Flow +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams +import org.matrix.android.sdk.api.session.group.model.GroupSummary +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +import org.matrix.android.sdk.api.session.sync.SyncState +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.session.widgets.model.Widget +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo + +class RxFlow(private val session: Session) { + + fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { + return session.getRoomSummariesLive(queryParams).asFlow() + } + + fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { + return session.getGroupSummariesLive(queryParams).asFlow() + } + + fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow> { + return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() + } + + fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { + return session.getBreadcrumbsLive(queryParams).asFlow() + } + + fun liveMyDevicesInfo(): Flow> { + return session.cryptoService().getLiveMyDevicesInfo().asFlow() + } + + fun liveSyncState(): Flow { + return session.getSyncStateLive().asFlow() + } + + fun livePushers(): Flow> { + return session.getPushersLive().asFlow() + } + + fun liveUser(userId: String): Flow> { + return session.getUserLive(userId).asFlow() + } + + fun liveRoomMember(userId: String, roomId: String): Flow> { + return session.getRoomMemberLive(userId, roomId).asFlow() + } + + fun liveUsers(): Flow> { + return session.getUsersLive().asFlow() + } + + fun liveIgnoredUsers(): Flow> { + return session.getIgnoredUsersLive().asFlow() + } + + fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Flow> { + return session.getPagedUsersLive(filter, excludedUserIds).asFlow() + } + + fun liveThreePIds(refreshData: Boolean): Flow> { + return session.getThreePidsLive(refreshData).asFlow() + } + + fun livePendingThreePIds(): Flow> { + return session.getPendingThreePidsLive().asFlow() + } + + fun liveUserCryptoDevices(userId: String): Flow> { + return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() + } + + fun liveCrossSigningInfo(userId: String): Flow> { + return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() + } + + fun liveCrossSigningPrivateKeys(): Flow> { + return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() + } + + fun liveUserAccountData(types: Set): Flow> { + return session.accountDataService().getLiveUserAccountDataEvents(types).asFlow() + } + + fun liveRoomAccountData(types: Set): Flow> { + return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() + } + + fun liveRoomWidgets( + roomId: String, + widgetId: QueryStringValue, + widgetTypes: Set? = null, + excludedTypes: Set? = null + ): Flow> { + return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asFlow() + } + + fun liveRoomChangeMembershipState(): Flow> { + return session.getChangeMembershipsLive().asFlow() + } +} + +fun Session.flow(): RxFlow { + return RxFlow(this) +} diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt new file mode 100644 index 0000000000..a9f062f379 --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.util.Optional + +fun Flow>.unwrap(): Flow { + return filter { it.hasValue() }.map { it.get() } +} + +fun Flow>.mapOptional(fn: (T) -> U?): Flow> { + return map { + it.map(fn) + } +} diff --git a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt new file mode 100644 index 0000000000..bd627b2041 --- /dev/null +++ b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle b/settings.gradle index b88ea99b05..e3b84b4733 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,4 @@ include ':diff-match-patch' include ':attachment-viewer' include ':multipicker' include ':library:ui-styles' +include ':matrix-sdk-android-flow' diff --git a/vector/build.gradle b/vector/build.gradle index a9c6a407d8..76bc71b2d4 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -323,6 +323,7 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") + implementation project(":matrix-sdk-android-flow") implementation project(":diff-match-patch") implementation project(":multipicker") implementation project(":attachment-viewer") From 19ae2987079220c418a8d1fe0ad043c5ba8ef6f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 1 Oct 2021 15:57:41 +0200 Subject: [PATCH 004/172] Add documentation about Figma -- very first draft --- docs/design.md | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 docs/design.md diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000000..959ca40864 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,104 @@ +# Element Android design + +## Introduction + +Design at element.io is done using Figma - https://www.figma.com + +## How to import from Figma to the Element Android project + +Integration should be done using the Android development best practice, and should follow the existing convention in the code. + +### Colors + +Element Android already contains all the colors which can be used by the designer, in the module `ui-style`. +Some of them depend on the theme, so ensure to use theme attributes and not colors directly. + +### Text + + - click on a text on Figma + - on the right panel, information about the style and colors are displayed + - in Element Android, text style are already defined, generally you should not create new style + - apply the style and the color to the layout + +### Dimension, position and margin + + - click on an item on Figma + - dimensions of the item will be displayed. + - move the mouse to other items to get relative positioning, margin, etc. + +### Icons + +#### Export drawable from Figma + + - click on the element to export + - ensure that the correct layer is selected. Sometimes the parent layer has to be selected on the left panel + - on the right panel, click on "export" + - select SVG + - you cqn check the preview of what will be exported + - click on "export" and save the file locally + - unzip the file if necessary + +It's also possible for any icon to go to the main component by right-clicking on the icon. + +#### Import in Android Studio + + - right click on the drawable folder where the drawable will be created + - click on "New"/"Vector Asset" + - select the exported file + - update the filename if necessary + - click on "Next" and click on "Finish" + - open the created vector drawable + - optionally update the color(s) to "#FF0000" (red) to ensure that the drawable is correctly tinted at runtime. + +## Figma links + +Figma links can be included in the layout, for future reference, but it is also OK to add a paragraph below here, to centralize the information + +Main entry point: https://www.figma.com/files/project/5612863/Element?fuid=779371459522484071 + +Note: all the Figma links are not publicly available. + +### Coumpound + +Coumpound contains the theme of the application, with all the components, in Light and Dark theme: palette (colors), typography, iconography, etc. + +https://www.figma.com/file/X4XTH9iS2KGJ2wFKDqkyed/Compound + +### Login + +TBD + +#### Login v2 + +https://www.figma.com/file/xdV4PuI3DlzA1EiBvbrggz/Login-Flow-v2 + +### Room list + +TBD + +### Timeline + +https://www.figma.com/file/x1HYYLYMmbYnhfoz2c2nGD/%5BRiotX%5D-Misc?node-id=0%3A1 + +### Voice message + +https://www.figma.com/file/uaWc62Ux2DkZC4OGtAGcNc/Voice-Messages?node-id=473%3A12 + +### Room settings + +TBD + +### VoIP + +https://www.figma.com/file/V6m2z0oAtUV1l8MdyIrAep/VoIP?node-id=4254%3A25767 + +### Presence + +https://www.figma.com/file/qmvEskET5JWva8jZJ4jX8o/Presence---User-Status?node-id=114%3A9174 +(Option B is chosen) + +### Spaces + +https://www.figma.com/file/m7L63aGPW7iHnIYStfdxCe/Spaces?node-id=192%3A30161 + +### List to be continued... From bbce37e694923c57a7c0147f5e0d14974a6ff3e0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 16:34:47 +0200 Subject: [PATCH 005/172] Mavericks 2: start replacing rx by flow --- .../quads/SharedSecureStorageViewModel.kt | 4 +- .../features/devtools/RoomDevToolViewModel.kt | 3 +- .../home/room/detail/RoomDetailViewModel.kt | 43 ++++++------ .../action/MessageActionsViewModel.kt | 20 ++++-- .../home/room/list/RoomListViewModel.kt | 24 +++---- .../invite/InviteUsersToRoomViewModel.kt | 66 +++++++++++-------- .../permissions/RoomPermissionsViewModel.kt | 9 ++- .../settings/VectorSettingsGeneralFragment.kt | 28 ++++---- .../settings/devtools/AccountDataViewModel.kt | 10 +-- 9 files changed, 113 insertions(+), 94 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 3f3b3a751a..49059391f0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -35,6 +35,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.listeners.ProgressListener @@ -42,6 +43,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -114,7 +116,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } } - session.rx() + session.flow() .liveUserCryptoDevices(session.myUserId) .distinctUntilChanged() .execute { diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index 9fffe70872..cf4b54c7fa 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.rx.rx @@ -69,7 +70,7 @@ class RoomDevToolViewModel @AssistedInject constructor( init { session.getRoom(initialState.roomId) - ?.rx() + ?.flow() ?.liveStateEvents(emptySet()) ?.execute { async -> copy(stateEvents = async) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index cacf9b8902..c64fb89f28 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.detail import android.net.Uri import androidx.annotation.IdRes -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -65,6 +65,10 @@ import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.commonmark.parser.Parser @@ -104,8 +108,9 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode -import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber @@ -252,7 +257,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeActiveRoomWidgets() { - session.rx() + session.flow() .liveRoomWidgets( roomId = initialState.roomId, widgetId = QueryStringValue.NoCondition @@ -285,7 +290,7 @@ class RoomDetailViewModel @AssistedInject constructor( val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE) } - room.rx() + room.flow() .liveRoomMembers(queryParams) .map { it.firstOrNull().toOptional() @@ -1503,29 +1508,22 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeSyncState() { - session.rx() + session.flow() .liveSyncState() - .subscribe { syncState -> - setState { - copy(syncState = syncState) - } + .setOnEach { syncState -> + copy(syncState = syncState) } - .disposeOnClear() session.getSyncStatusLive() - .asObservable() - .subscribe { it -> - if (it is SyncStatusService.Status.IncrementalSyncStatus) { - setState { - copy(incrementalSyncStatus = it) - } - } + .asFlow() + .filterIsInstance() + .setOnEach { + copy(incrementalSyncStatus = it) } - .disposeOnClear() } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( @@ -1587,16 +1585,15 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.rx() + session.flow() .liveRoomChangeMembershipState() .map { it[initialState.roomId] ?: ChangeMembershipState.Unknown } .distinctUntilChanged() - .subscribe { - setState { copy(changeMembershipState = it) } + .setOnEach { + copy(changeMembershipState = it) } - .disposeOnClear() } private fun observeSummaryState() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 6e6c7c1dbe..fd80a56870 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -36,6 +36,11 @@ import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.switchMap import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState @@ -54,8 +59,9 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import java.util.ArrayList /** @@ -79,7 +85,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted pillsPostProcessorFactory.create(initialState.roomId) } - private val eventIdObservable = BehaviorRelay.createDefault(initialState.eventId) + private val eventIdFlow = MutableStateFlow(initialState.eventId) @AssistedFactory interface Factory { @@ -135,7 +141,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeEvent() { if (room == null) return - room.rx() + room.flow() .liveTimelineEvent(initialState.eventId) .unwrap() .execute { @@ -145,9 +151,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeReactions() { if (room == null) return - eventIdObservable - .switchMap { eventId -> - room.rx() + eventIdFlow + .flatMapLatest { eventId -> + room.flow() .liveAnnotationSummary(eventId) .map { annotations -> EmojiDataSource.quickEmojis.map { emoji -> @@ -163,7 +169,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeTimelineEventState() { selectSubscribe(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions -> val nonNullTimelineEvent = timelineEvent() ?: return@selectSubscribe - eventIdObservable.accept(nonNullTimelineEvent.eventId) + eventIdFlow.tryEmit(nonNullTimelineEvent.eventId) setState { copy( eventId = nonNullTimelineEvent.eventId, 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 11f92284a6..b54d2b1b4f 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 @@ -17,12 +17,11 @@ package im.vector.app.features.home.room.list import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import im.vector.app.AppStateHandler @@ -33,6 +32,8 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue @@ -41,7 +42,7 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject @@ -72,11 +73,13 @@ class RoomListViewModel @Inject constructor( * If current space is null, will return orphan rooms only */ ORPHANS_IF_SPACE_NULL, + /** * Special case when we don't want to discriminate rooms when current space is null. * In this case return all. */ ALL_IF_SPACE_NULL, + /** Do not filter based on space*/ NONE } @@ -92,7 +95,7 @@ class RoomListViewModel @Inject constructor( ) } - session.rx().liveUser(session.myUserId) + session.flow().liveUser(session.myUserId) .map { it.getOrNull()?.getBestName() } .distinctUntilChanged() .execute { @@ -103,18 +106,17 @@ class RoomListViewModel @Inject constructor( } private fun observeMembershipChanges() { - session.rx() + session.flow() .liveRoomChangeMembershipState() - .subscribe { - setState { copy(roomMembershipChanges = it) } + .setOnEach { + copy(roomMembershipChanges = it) } - .disposeOnClear() } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { + override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel { val fragment: RoomListFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.roomListViewModelFactory.create(state) } @@ -320,7 +322,7 @@ class RoomListViewModel @Inject constructor( private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 2f20fef3c9..1f723104cf 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -18,16 +18,23 @@ package im.vector.app.features.invite import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.userdirectory.PendingSelection -import io.reactivex.Observable +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.rx.rx @@ -44,7 +51,7 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted fun create(initialState: InviteUsersToRoomViewState): InviteUsersToRoomViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: InviteUsersToRoomViewState): InviteUsersToRoomViewModel? { @@ -63,32 +70,33 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted } private fun inviteUsersToRoom(selections: Set) { - _viewEvents.post(InviteUsersToRoomViewEvents.Loading) - - Observable.fromIterable(selections).flatMapCompletable { user -> - when (user) { - is PendingSelection.UserPendingSelection -> room.rx().invite(user.user.userId, null) - is PendingSelection.ThreePidPendingSelection -> room.rx().invite3pid(user.threePid) - } - }.subscribe( - { - val successMessage = when (selections.size) { - 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, - selections.first().getBestName()) - 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, - selections.first().getBestName(), - selections.last().getBestName()) - else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, - selections.size - 1, - selections.first().getBestName(), - selections.size - 1) + viewModelScope.launch { + _viewEvents.post(InviteUsersToRoomViewEvents.Loading) + selections.asFlow() + .map { user -> + when (user) { + is PendingSelection.UserPendingSelection -> room.invite(user.user.userId, null) + is PendingSelection.ThreePidPendingSelection -> room.invite3pid(user.threePid) + } } - _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) - }, - { - _viewEvents.post(InviteUsersToRoomViewEvents.Failure(it)) - }) - .disposeOnClear() + .catch { cause -> + _viewEvents.post(InviteUsersToRoomViewEvents.Failure(cause)) + } + .collect { + val successMessage = when (selections.size) { + 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, + selections.first().getBestName()) + 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, + selections.first().getBestName(), + selections.last().getBestName()) + else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, + selections.size - 1, + selections.first().getBestName(), + selections.size - 1) + } + _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) + } + } } fun getUserIdsOfRoomMembers(): Set { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index f8d2f8315f..56c3940523 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -16,14 +16,13 @@ package im.vector.app.features.roomprofile.permissions -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsObservableFactory @@ -33,8 +32,8 @@ 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.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState, private val session: Session) @@ -62,7 +61,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index f40079c615..8eb9e2cdf3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -51,15 +51,19 @@ import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.workers.signout.SignOutUiWorker -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import java.io.File import java.util.UUID import javax.inject.Inject @@ -118,29 +122,29 @@ class VectorSettingsGeneralFragment @Inject constructor( } private fun observeUserAvatar() { - session.rx() + session.flow() .liveUser(session.myUserId) .unwrap() - .distinctUntilChanged { user -> user.avatarUrl } - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { mUserAvatarPreference.refreshAvatar(it) } - .disposeOnDestroyView() + .distinctUntilChangedBy { user -> user.avatarUrl } + .onEach { + mUserAvatarPreference.refreshAvatar(it) + } + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun observeUserDisplayName() { - session.rx() + session.flow() .liveUser(session.myUserId) .unwrap() .map { it.displayName ?: "" } .distinctUntilChanged() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { displayName -> + .onEach { displayName -> mDisplayNamePreference.let { it.summary = displayName it.text = displayName } } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun bindPref() { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index c42e2440c1..d226b14b23 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -16,23 +16,23 @@ package im.vector.app.features.settings.devtools -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow data class AccountDataViewState( val accountData: Async> = Uninitialized @@ -43,7 +43,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A : VectorViewModel(initialState) { init { - session.rx().liveUserAccountData(emptySet()) + session.flow().liveUserAccountData(emptySet()) .execute { copy(accountData = it) } @@ -66,7 +66,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A fun create(initialState: AccountDataViewState): AccountDataViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: AccountDataViewState): AccountDataViewModel? { From d63e1ecfeacd0f0dc064ad66dc970abe3be93b57 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 17:08:09 +0200 Subject: [PATCH 006/172] Mavericks 2: replacing rx by flow --- .../home/room/detail/RoomDetailViewModel.kt | 12 ++-- .../action/MessageActionsViewModel.kt | 16 ++--- ...leFactory.kt => PowerLevelsFlowFactory.kt} | 19 +++--- .../RoomMemberProfileViewModel.kt | 52 ++++++++-------- .../roomprofile/RoomProfileViewModel.kt | 12 ++-- .../roomprofile/alias/RoomAliasViewModel.kt | 33 +++++----- .../banned/RoomBannedMemberListViewModel.kt | 10 ++-- .../members/RoomMemberListViewModel.kt | 14 ++--- .../permissions/RoomPermissionsViewModel.kt | 13 ++-- .../settings/RoomSettingsViewModel.kt | 60 +++++++++---------- .../app/features/spaces/SpaceMenuViewModel.kt | 13 ++-- .../spaces/explore/SpaceDirectoryViewModel.kt | 11 ++-- .../spaces/share/ShareSpaceViewModel.kt | 12 ++-- 13 files changed, 137 insertions(+), 140 deletions(-) rename vector/src/main/java/im/vector/app/features/powerlevel/{PowerLevelsObservableFactory.kt => PowerLevelsFlowFactory.kt} (73%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index c64fb89f28..b35e7c2607 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -55,7 +55,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.typing.TypingHelper -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences @@ -66,9 +66,10 @@ import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.commonmark.parser.Parser @@ -238,8 +239,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observePowerLevel() { - PowerLevelsObservableFactory(room).createObservable() - .subscribe { + PowerLevelsFlowFactory(room).createFlow() + .onEach { val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE) val canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId) val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId) @@ -252,8 +253,7 @@ class RoomDetailViewModel @AssistedInject constructor( isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall ) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } private fun observeActiveRoomWidgets() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index fd80a56870..1723e893ad 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.detail.timeline.action import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -33,14 +32,14 @@ import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormat import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.VectorHtmlCompressor -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.settings.VectorPreferences -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.switchMap +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState @@ -61,8 +60,6 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import java.util.ArrayList /** * Information related to an event and used to display preview in contextual bottom sheet. @@ -125,8 +122,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (room == null) { return } - PowerLevelsObservableFactory(room).createObservable() - .subscribe { + PowerLevelsFlowFactory(room).createFlow() + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) val canReact = powerLevelsHelper.isUserAllowedToSend(session.myUserId, false, EventType.REACTION) val canRedact = powerLevelsHelper.isUserAbleToRedact(session.myUserId) @@ -135,8 +132,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted setState { copy(actionPermissions = permissions) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } private fun observeEvent() { diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsObservableFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt similarity index 73% rename from vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsObservableFactory.kt rename to vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index a9d4578bf0..767d6f1ba7 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsObservableFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -16,23 +16,24 @@ package im.vector.app.features.powerlevel -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.rx.mapOptional -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap -class PowerLevelsObservableFactory(private val room: Room) { +class PowerLevelsFlowFactory(private val room: Room) { - fun createObservable(): Observable { - return room.rx() + fun createFlow(): Flow { + return room.flow() .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .observeOn(Schedulers.computation()) + .flowOn(Dispatchers.Default) .mapOptional { it.content.toModel() } .unwrap() } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 4b57bdc6aa..ace53c60b7 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roommemberprofile -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -32,10 +31,15 @@ import im.vector.app.R import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import io.reactivex.Observable import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.query.QueryStringValue @@ -54,6 +58,8 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -286,11 +292,11 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } private fun observeRoomSummaryAndPowerLevels(room: Room) { - val roomSummaryLive = room.rx().liveRoomSummary().unwrap() - val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable() + val roomSummaryLive = room.flow().liveRoomSummary().unwrap() + val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .subscribe { + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = ActionPermissions( canKick = powerLevelsHelper.isUserAbleToKick(session.myUserId), @@ -298,30 +304,26 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId), canEditPowerLevel = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_POWER_LEVELS) ) - setState { copy(powerLevelsContent = it, actionPermissions = permissions) } - } - .disposeOnClear() + setState { + copy(powerLevelsContent = it, actionPermissions = permissions) + } + }.launchIn(viewModelScope) roomSummaryLive.execute { copy(isRoomEncrypted = it.invoke()?.isEncrypted == true) } - Observable - .combineLatest( - roomSummaryLive, - powerLevelsContentLive, - BiFunction { roomSummary, powerLevelsContent -> - val roomName = roomSummary.toMatrixItem().getBestName() - val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) - when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { - Role.Admin -> stringProvider.getString(R.string.room_member_power_level_admin_in, roomName) - Role.Moderator -> stringProvider.getString(R.string.room_member_power_level_moderator_in, roomName) - Role.Default -> stringProvider.getString(R.string.room_member_power_level_default_in, roomName) - is Role.Custom -> stringProvider.getString(R.string.room_member_power_level_custom_in, userPowerLevel.value, roomName) - } - } - ).execute { - copy(userPowerLevelString = it) - } + roomSummaryLive.combine(powerLevelsContentLive){roomSummary, powerLevelsContent -> + val roomName = roomSummary.toMatrixItem().getBestName() + val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { + Role.Admin -> stringProvider.getString(R.string.room_member_power_level_admin_in, roomName) + Role.Moderator -> stringProvider.getString(R.string.room_member_power_level_moderator_in, roomName) + Role.Default -> stringProvider.getString(R.string.room_member_power_level_default_in, roomName) + is Role.Custom -> stringProvider.getString(R.string.room_member_power_level_custom_in, userPowerLevel.value, roomName) + } + }.execute { + copy(userPowerLevelString = it) + } } private fun handleIgnoreAction() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index d35e8f3ad5..e6b416bcb6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -29,7 +28,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.ShortcutCreator -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue @@ -109,16 +108,15 @@ class RoomProfileViewModel @AssistedInject constructor( } private fun observePermissions() { - PowerLevelsObservableFactory(room) - .createObservable() - .subscribe { + PowerLevelsFlowFactory(room) + .createFlow() + .setOnEach { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = RoomProfileViewState.ActionPermissions( canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) ) - setState { copy(actionPermissions = permissions) } + copy(actionPermissions = permissions) } - .disposeOnClear() } override fun handle(action: RoomProfileAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index aa9981997c..7ebd82a5c7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.alias -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -29,7 +28,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.query.QueryStringValue @@ -38,7 +39,9 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -138,9 +141,9 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun observePowerLevel() { - PowerLevelsObservableFactory(room) - .createObservable() - .subscribe { + PowerLevelsFlowFactory(room) + .createFlow() + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = RoomAliasViewState.ActionPermissions( canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend( @@ -163,27 +166,23 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo publishManuallyState = newPublishManuallyState ) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } /** * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomCanonicalAlias() { - room.rx() + room.flow() .liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - .subscribe { - setState { - copy( - canonicalAlias = it.canonicalAlias, - alternativeAliases = it.alternativeAliases.orEmpty().sorted() - ) - } + .setOnEach { + copy( + canonicalAlias = it.canonicalAlias, + alternativeAliases = it.alternativeAliases.orEmpty().sorted() + ) } - .disposeOnClear() } override fun handle(action: RoomAliasAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 1b64261fcb..450c4ee545 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.banned -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -27,7 +26,7 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue @@ -70,14 +69,13 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia ) } - val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable() + val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .subscribe { + .setOnEach { val powerLevelsHelper = PowerLevelsHelper(it) - setState { copy(canUserBan = powerLevelsHelper.isUserAbleToBan(session.myUserId)) } + copy(canUserBan = powerLevelsHelper.isUserAbleToBan(session.myUserId)) } - .disposeOnClear() } companion object : MvRxViewModelFactory { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 5c6ff48403..26f2bc4ebe 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.members -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory @@ -27,9 +26,11 @@ import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse @@ -129,8 +130,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observePowerLevel() { - PowerLevelsObservableFactory(room).createObservable() - .subscribe { + PowerLevelsFlowFactory(room).createFlow() + .onEach { val permissions = ActionPermissions( canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId), canRevokeThreePidInvite = PowerLevelsHelper(it).isUserAllowedToSend( @@ -142,8 +143,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState setState { copy(actionsPermissions = permissions) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } private fun observeRoomSummary() { @@ -192,7 +192,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState override fun handle(action: RoomMemberListAction) { when (action) { is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action) - is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action) + is RoomMemberListAction.FilterMemberList -> handleFilterMemberList(action) }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 56c3940523..92df11ff57 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -25,7 +25,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -71,9 +73,9 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } private fun observePowerLevel() { - PowerLevelsObservableFactory(room) - .createObservable() - .subscribe { powerLevelContent -> + PowerLevelsFlowFactory(room) + .createFlow() + .onEach { powerLevelContent -> val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) val permissions = RoomPermissionsViewState.ActionPermissions( canChangePowerLevels = powerLevelsHelper.isUserAllowedToSend( @@ -88,8 +90,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat currentPowerLevelsContent = Success(powerLevelContent) ) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } override fun handle(action: RoomPermissionsAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index e872a04d80..112fe39903 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -26,10 +26,13 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences import io.reactivex.Completable import io.reactivex.Observable +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -41,9 +44,10 @@ import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val vectorPreferences: VectorPreferences, @@ -123,7 +127,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> val roomSummary = async.invoke() @@ -134,10 +138,10 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: ) } - val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable() + val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .subscribe { + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = RoomSettingsViewState.ActionPermissions( canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), @@ -152,62 +156,56 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canAddChildren = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD) ) - setState { copy(actionPermissions = permissions) } - } - .disposeOnClear() + setState { + copy(actionPermissions = permissions) + } + }.launchIn(viewModelScope) } private fun observeRoomHistoryVisibility() { - room.rx() + room.flow() .liveStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - .subscribe { - it.historyVisibility?.let { - setState { copy(currentHistoryVisibility = it) } - } + .mapNotNull { it.historyVisibility } + .setOnEach { + copy(currentHistoryVisibility = it) } - .disposeOnClear() } private fun observeJoinRule() { - room.rx() + room.flow() .liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - .subscribe { - it.joinRules?.let { - setState { copy(currentRoomJoinRules = it) } - } + .mapNotNull { it.joinRules } + .setOnEach { + copy(currentRoomJoinRules = it) } - .disposeOnClear() } private fun observeGuestAccess() { - room.rx() + room.flow() .liveStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - .subscribe { - it.guestAccess?.let { - setState { copy(currentGuestAccess = it) } - } + .mapNotNull { it.guestAccess } + .setOnEach { + copy(currentGuestAccess = it) } - .disposeOnClear() } /** * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomAvatar() { - room.rx() + room.flow() .liveStateEvent(EventType.STATE_ROOM_AVATAR, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - .subscribe { - setState { copy(currentRoomAvatarUrl = it.avatarUrl) } + .setOnEach { + copy(currentRoomAvatarUrl = it.avatarUrl) } - .disposeOnClear() } override fun handle(action: RoomSettingsAction) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index 24ca218942..1627fdacae 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -30,8 +30,10 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.session.Session @@ -87,9 +89,9 @@ class SpaceMenuViewModel @AssistedInject constructor( } }.disposeOnClear() - PowerLevelsObservableFactory(room) - .createObservable() - .subscribe { + PowerLevelsFlowFactory(room) + .createFlow() + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId) @@ -114,8 +116,7 @@ class SpaceMenuViewModel @AssistedInject constructor( isLastAdmin = isLastAdmin ) } - } - .disposeOnClear() + }.launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 1006f5a570..f25241d2dc 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.explore -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -29,8 +28,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -83,17 +84,17 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private fun observePermissions() { val room = session.getRoom(initialState.spaceId) ?: return - val powerLevelsContentLive = PowerLevelsObservableFactory(room).createObservable() + val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive - .subscribe { + .onEach { val powerLevelsHelper = PowerLevelsHelper(it) setState { copy(canAddRooms = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD)) } } - .disposeOnClear() + .launchIn(viewModelScope) } private fun refreshFromApi(rootId: String?) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index dfce534a7a..2cc4fd388c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -26,7 +26,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -63,9 +65,9 @@ class ShareSpaceViewModel @AssistedInject constructor( private fun observePowerLevel() { val room = session.getRoom(initialState.spaceId) ?: return - PowerLevelsObservableFactory(room) - .createObservable() - .subscribe { powerLevelContent -> + PowerLevelsFlowFactory(room) + .createFlow() + .onEach { powerLevelContent -> val powerLevelsHelper = PowerLevelsHelper(powerLevelContent) setState { copy( @@ -73,7 +75,7 @@ class ShareSpaceViewModel @AssistedInject constructor( ) } } - .disposeOnClear() + .launchIn(viewModelScope) } override fun handle(action: ShareSpaceAction) { From 606cddc82683687eafa4f502ffb7c25cc61cbc19 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 17:11:53 +0200 Subject: [PATCH 007/172] Mavericks 2: replace MvRxViewModelFactory by MavericksViewModelFactory --- .../ElementFeature/root/src/app_package/ViewModel.kt.ftl | 4 ++-- .../main/java/im/vector/app/features/auth/ReAuthViewModel.kt | 4 ++-- .../java/im/vector/app/features/call/VectorCallViewModel.kt | 4 ++-- .../vector/app/features/call/conference/JitsiCallViewModel.kt | 4 ++-- .../app/features/call/transfer/CallTransferViewModel.kt | 4 ++-- .../vector/app/features/contactsbook/ContactsBookViewModel.kt | 4 ++-- .../app/features/createdirect/CreateDirectRoomViewModel.kt | 4 ++-- .../crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt | 4 ++-- .../app/features/crypto/quads/SharedSecureStorageViewModel.kt | 4 ++-- .../app/features/crypto/recover/BootstrapSharedViewModel.kt | 4 ++-- .../crypto/verification/VerificationBottomSheetViewModel.kt | 4 ++-- .../verification/choose/VerificationChooseMethodViewModel.kt | 4 ++-- .../conclusion/VerificationConclusionViewModel.kt | 4 ++-- .../verification/emoji/VerificationEmojiCodeViewModel.kt | 4 ++-- .../im/vector/app/features/devtools/RoomDevToolViewModel.kt | 4 ++-- .../app/features/discovery/DiscoverySettingsViewModel.kt | 4 ++-- .../features/discovery/change/SetIdentityServerViewModel.kt | 4 ++-- .../java/im/vector/app/features/home/HomeActivityViewModel.kt | 4 ++-- .../java/im/vector/app/features/home/HomeDetailViewModel.kt | 4 ++-- .../im/vector/app/features/home/PromoteRestrictedViewModel.kt | 4 ++-- .../app/features/home/UnknownDeviceDetectorSharedViewModel.kt | 4 ++-- .../vector/app/features/home/UnreadMessagesSharedViewModel.kt | 4 ++-- .../features/home/room/breadcrumbs/BreadcrumbsViewModel.kt | 4 ++-- .../app/features/home/room/detail/RoomDetailViewModel.kt | 4 ++-- .../app/features/home/room/detail/search/SearchViewModel.kt | 4 ++-- .../room/detail/timeline/action/MessageActionsViewModel.kt | 4 ++-- .../detail/timeline/edithistory/ViewEditHistoryViewModel.kt | 4 ++-- .../room/detail/timeline/reactions/ViewReactionsViewModel.kt | 4 ++-- .../features/home/room/detail/upgrade/MigrateRoomViewModel.kt | 4 ++-- .../features/homeserver/HomeServerCapabilitiesViewModel.kt | 4 ++-- .../main/java/im/vector/app/features/login/LoginViewModel.kt | 4 ++-- .../java/im/vector/app/features/login2/LoginViewModel2.kt | 4 ++-- .../app/features/login2/created/AccountCreatedViewModel.kt | 4 ++-- .../app/features/matrixto/MatrixToBottomSheetViewModel.kt | 4 ++-- .../im/vector/app/features/rageshake/BugReportViewModel.kt | 4 ++-- .../app/features/reactions/EmojiSearchResultViewModel.kt | 4 ++-- .../app/features/room/RequireActiveMembershipViewModel.kt | 4 ++-- .../app/features/roomdirectory/RoomDirectoryViewModel.kt | 4 ++-- .../features/roomdirectory/createroom/CreateRoomViewModel.kt | 4 ++-- .../roomdirectory/picker/RoomDirectoryPickerViewModel.kt | 4 ++-- .../roomdirectory/roompreview/RoomPreviewViewModel.kt | 4 ++-- .../features/roommemberprofile/RoomMemberProfileViewModel.kt | 4 ++-- .../devices/DeviceListBottomSheetViewModel.kt | 4 ++-- .../vector/app/features/roomprofile/RoomProfileViewModel.kt | 4 ++-- .../app/features/roomprofile/alias/RoomAliasViewModel.kt | 4 ++-- .../roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt | 4 ++-- .../roomprofile/banned/RoomBannedMemberListViewModel.kt | 4 ++-- .../features/roomprofile/members/RoomMemberListViewModel.kt | 4 ++-- .../notifications/RoomNotificationSettingsViewModel.kt | 4 ++-- .../roomprofile/permissions/RoomPermissionsViewModel.kt | 4 ++-- .../features/roomprofile/settings/RoomSettingsViewModel.kt | 4 ++-- .../advanced/RoomJoinRuleChooseRestrictedViewModel.kt | 4 ++-- .../app/features/roomprofile/uploads/RoomUploadsViewModel.kt | 4 ++-- .../account/deactivation/DeactivateAccountViewModel.kt | 4 ++-- .../settings/crosssigning/CrossSigningSettingsViewModel.kt | 4 ++-- .../devices/DeviceVerificationInfoBottomSheetViewModel.kt | 4 ++-- .../vector/app/features/settings/devices/DevicesViewModel.kt | 4 ++-- .../app/features/settings/devtools/AccountDataViewModel.kt | 1 - .../settings/devtools/GossipingEventsPaperTrailViewModel.kt | 4 ++-- .../app/features/settings/devtools/KeyRequestListViewModel.kt | 4 ++-- .../app/features/settings/devtools/KeyRequestViewModel.kt | 4 ++-- .../settings/homeserver/HomeserverSettingsViewModel.kt | 4 ++-- .../app/features/settings/ignored/IgnoredUsersViewModel.kt | 4 ++-- .../app/features/settings/locale/LocalePickerViewModel.kt | 4 ++-- .../app/features/settings/push/PushGatewaysViewModel.kt | 4 ++-- .../vector/app/features/settings/push/PushRulesViewModel.kt | 4 ++-- .../features/settings/threepids/ThreePidsSettingsViewModel.kt | 4 ++-- .../im/vector/app/features/share/IncomingShareViewModel.kt | 4 ++-- .../vector/app/features/signout/soft/SoftLogoutViewModel.kt | 4 ++-- .../java/im/vector/app/features/spaces/SpaceMenuViewModel.kt | 4 ++-- .../java/im/vector/app/features/spaces/SpacesListViewModel.kt | 4 ++-- .../vector/app/features/spaces/create/CreateSpaceViewModel.kt | 4 ++-- .../app/features/spaces/explore/SpaceDirectoryViewModel.kt | 4 ++-- .../features/spaces/invite/SpaceInviteBottomSheetViewModel.kt | 4 ++-- .../app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt | 4 ++-- .../app/features/spaces/manage/SpaceAddRoomsViewModel.kt | 4 ++-- .../app/features/spaces/manage/SpaceManageRoomsViewModel.kt | 4 ++-- .../app/features/spaces/manage/SpaceManageSharedViewModel.kt | 4 ++-- .../vector/app/features/spaces/people/SpacePeopleViewModel.kt | 4 ++-- .../app/features/spaces/preview/SpacePreviewViewModel.kt | 4 ++-- .../vector/app/features/spaces/share/ShareSpaceViewModel.kt | 4 ++-- .../java/im/vector/app/features/terms/ReviewTermsViewModel.kt | 4 ++-- .../vector/app/features/usercode/UserCodeSharedViewModel.kt | 4 ++-- .../im/vector/app/features/userdirectory/UserListViewModel.kt | 4 ++-- .../java/im/vector/app/features/widgets/WidgetViewModel.kt | 4 ++-- .../widgets/permissions/RoomWidgetPermissionViewModel.kt | 4 ++-- .../features/workers/signout/ServerBackupStatusViewModel.kt | 4 ++-- .../app/features/workers/signout/SignoutCheckViewModel.kt | 4 ++-- 88 files changed, 174 insertions(+), 175 deletions(-) diff --git a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl index 8c25f9f9a8..64e6a0f83f 100644 --- a/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl +++ b/tools/templates/ElementFeature/root/src/app_package/ViewModel.kt.ftl @@ -2,7 +2,7 @@ package ${escapeKotlinIdentifiers(packageName)} import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -27,7 +27,7 @@ class ${viewModelClass} @AssistedInject constructor(@Assisted initialState: ${vi fun create(initialState: ${viewStateClass}): ${viewModelClass} } - companion object : MvRxViewModelFactory<${viewModelClass}, ${viewStateClass}> { + companion object : MavericksViewModelFactory<${viewModelClass}, ${viewStateClass}> { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ${viewStateClass}): ${viewModelClass}? { diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt index edbceae20f..449270644e 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.auth import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -39,7 +39,7 @@ class ReAuthViewModel @AssistedInject constructor( fun create(initialState: ReAuthState): ReAuthViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: ReAuthState): ReAuthViewModel? { val factory = when (viewModelContext) { diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 90df595f8f..d8aaca9bd8 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.call import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -345,7 +345,7 @@ class VectorCallViewModel @AssistedInject constructor( fun create(initialState: VectorCallViewState): VectorCallViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: VectorCallViewState): VectorCallViewModel { diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt index 0fc85cb58c..b4bb01d374 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.call.conference import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -140,7 +140,7 @@ class JitsiCallViewModel @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { const val ENABLE_VIDEO_OPTION = "ENABLE_VIDEO_OPTION" diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt index 0217551260..8b66ec736c 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.call.transfer import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -45,7 +45,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: fun create(initialState: CallTransferViewState): CallTransferViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CallTransferViewState): CallTransferViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt index cfbdef8ffb..e37fc94b51 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -49,7 +49,7 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted fun create(initialState: ContactsBookViewState): ContactsBookViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: ContactsBookViewState): ContactsBookViewModel? { val factory = when (viewModelContext) { 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 88e8d68155..21bf8016ae 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 @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -49,7 +49,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateDirectRoomViewState): CreateDirectRoomViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt index cb8a6ce4e9..bba472f407 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.crypto.keysbackup.settings import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -45,7 +45,7 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS fun create(initialState: KeysBackupSettingViewState): KeysBackupSettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: KeysBackupSettingViewState): KeysBackupSettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 49059391f0..ecaf89a1e5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -322,7 +322,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index 866a87d7f9..403ba8c62b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -552,7 +552,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( // Companion, view model assisted creation // ====================================== - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? { val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index b2f259772e..1953f8d367 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -223,7 +223,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( _viewEvents.post(VerificationBottomSheetViewEvents.GoToSettings) } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: VerificationBottomSheetViewState): VerificationBottomSheetViewModel? { val fragment: VerificationBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index 79878fcf83..038762cda7 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.crypto.verification.choose import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -94,7 +94,7 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( super.onCleared() } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: VerificationChooseMethodViewState): VerificationChooseMethodViewModel? { val fragment: VerificationChooseMethodFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.verificationChooseMethodViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt index 93133184e9..5e03e276c0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/conclusion/VerificationConclusionViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.crypto.verification.conclusion import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents @@ -38,7 +38,7 @@ enum class ConclusionState { class VerificationConclusionViewModel(initialState: VerificationConclusionViewState) : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): VerificationConclusionViewState? { val args = viewModelContext.args() diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt index c8dd9b34d8..dd084ef718 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/emoji/VerificationEmojiCodeViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -155,7 +155,7 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor( fun create(initialState: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: VerificationEmojiCodeViewState): VerificationEmojiCodeViewModel? { val factory = (viewModelContext as FragmentViewModelContext).fragment().viewModelFactory diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index cf4b54c7fa..4bab65fd5d 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.squareup.moshi.Types @@ -56,7 +56,7 @@ class RoomDevToolViewModel @AssistedInject constructor( fun create(initialState: RoomDevToolViewState): RoomDevToolViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomDevToolViewState): RoomDevToolViewModel { diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 3cd6c31ab2..412a1ab5b4 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -47,7 +47,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( fun create(initialState: DiscoverySettingsState): DiscoverySettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DiscoverySettingsState): DiscoverySettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt index 08632a2bd1..275ff67e89 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.discovery.change import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -46,7 +46,7 @@ class SetIdentityServerViewModel @AssistedInject constructor( fun create(initialState: SetIdentityServerState): SetIdentityServerViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): SetIdentityServerState? { val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() 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 1aa2f59337..297cffd627 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 @@ -18,7 +18,7 @@ package im.vector.app.features.home import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -67,7 +67,7 @@ class HomeActivityViewModel @AssistedInject constructor( fun create(initialState: HomeActivityViewState, args: HomeActivityArgs): HomeActivityViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeActivityViewState): HomeActivityViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 460975c2c2..01eb6e3c31 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -72,7 +72,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho fun create(initialState: HomeDetailViewState): HomeDetailViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): HomeDetailViewState? { val uiStateRepository = (viewModelContext.activity as HasScreenInjector).injector().uiStateRepository() diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt index da29d4c5fc..0c8c9e480c 100644 --- a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -76,7 +76,7 @@ class PromoteRestrictedViewModel @AssistedInject constructor( fun create(initialState: ActiveSpaceViewState): PromoteRestrictedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ActiveSpaceViewState): PromoteRestrictedViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 7b25f20f77..db3317a214 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -70,7 +70,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted fun create(initialState: UnknownDevicesState): UnknownDeviceDetectorSharedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: UnknownDevicesState): UnknownDeviceDetectorSharedViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 1e2b9e542a..4c277ecc01 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -65,7 +65,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia fun create(initialState: UnreadMessagesState): UnreadMessagesSharedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: UnreadMessagesState): UnreadMessagesSharedViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index d3825de4ef..a945e4bbb1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.breadcrumbs import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -41,7 +41,7 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B fun create(initialState: BreadcrumbsViewState): BreadcrumbsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: BreadcrumbsViewState): BreadcrumbsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index b35e7c2607..f565794094 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -165,7 +165,7 @@ class RoomDetailViewModel @AssistedInject constructor( fun create(initialState: RoomDetailViewState): RoomDetailViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { const val PAGINATION_COUNT = 50 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt index 26111e4f2b..e4832b3876 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -50,7 +50,7 @@ class SearchViewModel @AssistedInject constructor( fun create(initialState: SearchViewState): SearchViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SearchViewState): SearchViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1723e893ad..5792187eb5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.action import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.Lazy import dagger.assisted.Assisted @@ -89,7 +89,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted fun create(initialState: MessageActionState): MessageActionsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? { val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index 5732326f2e..699f3cf02d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -19,7 +19,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -51,7 +51,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor( fun create(initialState: ViewEditHistoryViewState): ViewEditHistoryViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 6a78161d28..fde823cb35 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.reactions import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -74,7 +74,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted fun create(initialState: DisplayReactionsViewState): ViewReactionsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt index 3dc5d0e3ba..7b444629fa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.upgrade import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -55,7 +55,7 @@ class MigrateRoomViewModel @AssistedInject constructor( fun create(initialState: MigrateRoomViewState): MigrateRoomViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: MigrateRoomViewState): MigrateRoomViewModel? { val factory = when (viewModelContext) { diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt index 083b843b33..eab5ce569a 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.homeserver import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -48,7 +48,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor( fun create(initialState: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel? { val fragment: UserListFragment = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index d4fd3101aa..24222e9770 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -86,7 +86,7 @@ class LoginViewModel @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: LoginViewState): LoginViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt index e3f97a1c01..e38fabae5f 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewModel2.kt @@ -22,7 +22,7 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -86,7 +86,7 @@ class LoginViewModel2 @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: LoginViewState2): LoginViewModel2? { diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index 1acec968b6..c95434a548 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.login2.created import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -43,7 +43,7 @@ class AccountCreatedViewModel @AssistedInject constructor( fun create(initialState: AccountCreatedViewState): AccountCreatedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: AccountCreatedViewState): AccountCreatedViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 566cf769f2..eeb4814f1b 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -245,7 +245,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( return session.peekRoom(roomIdOrAlias) } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt index c71a89553e..a1ec6b2e76 100644 --- a/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReportViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.rageshake import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -40,7 +40,7 @@ class BugReportViewModel @AssistedInject constructor( fun create(initialState: BugReportState): BugReportViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: BugReportState): BugReportViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt index 04ee95f68e..7f54b96c36 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.reactions import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -44,7 +44,7 @@ class EmojiSearchResultViewModel @AssistedInject constructor( fun create(initialState: EmojiSearchResultViewState): EmojiSearchResultViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index c8b0037311..2b299b014e 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.room import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted @@ -54,7 +54,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( fun create(initialState: RequireActiveMembershipViewState): RequireActiveMembershipViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RequireActiveMembershipViewState): RequireActiveMembershipViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index dc1cbfc58d..8955167e50 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.appendAt @@ -53,7 +53,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( fun create(initialState: PublicRoomsViewState): RoomDirectoryViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { private const val PUBLIC_ROOMS_LIMIT = 20 @JvmStatic diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 9a9812933b..f3d45a1ec5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -21,7 +21,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -116,7 +116,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted private val init } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index 2558715834..a5f5801885 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -50,7 +50,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( fun create(initialState: RoomDirectoryPickerViewState): RoomDirectoryPickerViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomDirectoryPickerViewState): RoomDirectoryPickerViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index a559b0554d..6e70ff2593 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomdirectory.roompreview import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -52,7 +52,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini fun create(initialState: RoomPreviewViewState): RoomPreviewViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomPreviewViewState): RoomPreviewViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index ace53c60b7..ab5337d7cd 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -20,7 +20,7 @@ package im.vector.app.features.roommemberprofile import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -73,7 +73,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v fun create(initialState: RoomMemberProfileViewState): RoomMemberProfileViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomMemberProfileViewState): RoomMemberProfileViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 15c62af35b..2baf27e694 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -108,7 +108,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DeviceListViewState): DeviceListBottomSheetViewModel? { val fragment: DeviceListBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index e6b416bcb6..c8570d67dc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -57,7 +57,7 @@ class RoomProfileViewModel @AssistedInject constructor( fun create(initialState: RoomProfileViewState): RoomProfileViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomProfileViewState): RoomProfileViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 7ebd82a5c7..cb26d6407b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile.alias import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -54,7 +54,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo fun create(initialState: RoomAliasViewState): RoomAliasViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomAliasViewState): RoomAliasViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt index e762b52025..d294706096 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.alias.detail import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -36,7 +36,7 @@ class RoomAliasBottomSheetViewModel @AssistedInject constructor( fun create(initialState: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 450c4ee545..b92d1a4bd3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.banned import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -78,7 +78,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedMemberListViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 26f2bc4ebe..cd6d6f1610 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.members import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -60,7 +60,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState fun create(initialState: RoomMemberListViewState): RoomMemberListViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomMemberListViewState): RoomMemberListViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index dd0535f51b..51cd4a4ecc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.roomprofile.notifications import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -41,7 +41,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( fun create(initialState: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomNotificationSettingsViewState): RoomNotificationSettingsViewModel { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 92df11ff57..71e8a313b5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.roomprofile.permissions import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -46,7 +46,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat fun create(initialState: RoomPermissionsViewState): RoomPermissionsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomPermissionsViewState): RoomPermissionsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 112fe39903..4491ff8606 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -59,7 +59,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: fun create(initialState: RoomSettingsViewState): RoomSettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomSettingsViewState): RoomSettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt index eaa435f853..d6da4be3e9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/advanced/RoomJoinRuleChooseRestrictedViewModel.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -390,7 +390,7 @@ class RoomJoinRuleChooseRestrictedViewModel @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: RoomJoinRuleChooseRestrictedState) : RoomJoinRuleChooseRestrictedViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 1d6b056816..2ae27f7799 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -45,7 +45,7 @@ class RoomUploadsViewModel @AssistedInject constructor( fun create(initialState: RoomUploadsViewState): RoomUploadsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomUploadsViewState): RoomUploadsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt index bc030775e0..5b6b9bdc38 100644 --- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.settings.account.deactivation import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -114,7 +114,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DeactivateAccountViewState): DeactivateAccountViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 8bdf97b6ec..6f1058ec7c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.crosssigning import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -159,7 +159,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( _viewEvents.post(CrossSigningSettingsViewEvents.Failure(Exception(stringProvider.getString(R.string.failed_to_initialize_cross_signing)))) } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CrossSigningSettingsViewState): CrossSigningSettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index ee5b0a6092..975236beb1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.devices import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -88,7 +88,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DeviceVerificationInfoBottomSheetViewState) 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 ec2d6dd53e..04e743b8df 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 @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -97,7 +97,7 @@ class DevicesViewModel @AssistedInject constructor( fun create(initialState: DevicesViewState): DevicesViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DevicesViewState): DevicesViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index d226b14b23..e5739ec446 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -20,7 +20,6 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index 25834e5580..e07da1065c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -63,7 +63,7 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted i fun create(initialState: GossipingEventsPaperTrailState): GossipingEventsPaperTrailViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: GossipingEventsPaperTrailState): GossipingEventsPaperTrailViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index b4e872000e..362f2768fc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -21,7 +21,7 @@ import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -71,7 +71,7 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState fun create(initialState: KeyRequestListViewState): KeyRequestListViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: KeyRequestListViewState): KeyRequestListViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt index e1554cb0a4..8925921b81 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestViewModel.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -60,7 +60,7 @@ class KeyRequestViewModel @AssistedInject constructor( fun create(initialState: KeyRequestViewState): KeyRequestViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: KeyRequestViewState): KeyRequestViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt index 623ac37aa4..91ad34f1b6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -41,7 +41,7 @@ class HomeserverSettingsViewModel @AssistedInject constructor( fun create(initialState: HomeServerSettingsViewState): HomeserverSettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeServerSettingsViewState): HomeserverSettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index 34d2bc4821..1cf150395a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -54,7 +54,7 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: fun create(initialState: IgnoredUsersViewState): IgnoredUsersViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: IgnoredUsersViewState): IgnoredUsersViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt index 2e59b0ef7d..ef15049a8f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/locale/LocalePickerViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.settings.locale import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -53,7 +53,7 @@ class LocalePickerViewModel @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: LocalePickerViewState): LocalePickerViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt index 509e38b0f3..bc159e3fe0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -46,7 +46,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: fun create(initialState: PushGatewayViewState): PushGatewaysViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt index dd21cfb5c9..fbd28630ef 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.push import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import im.vector.app.core.di.HasScreenInjector import im.vector.app.core.platform.EmptyAction @@ -31,7 +31,7 @@ data class PushRulesViewState( class PushRulesViewModel(initialState: PushRulesViewState) : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): PushRulesViewState? { val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index ac565e72a1..4d94389850 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -84,7 +84,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( fun create(initialState: ThreePidsSettingsViewState): ThreePidsSettingsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ThreePidsSettingsViewState): ThreePidsSettingsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 4d211d11de..665d3b28fa 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.share import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted @@ -48,7 +48,7 @@ class IncomingShareViewModel @AssistedInject constructor( fun create(initialState: IncomingShareViewState): IncomingShareViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: IncomingShareViewState): IncomingShareViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index f17847fdfd..4cf20fe551 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.signout.soft import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -51,7 +51,7 @@ class SoftLogoutViewModel @AssistedInject constructor( fun create(initialState: SoftLogoutViewState): SoftLogoutViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun initialState(viewModelContext: ViewModelContext): SoftLogoutViewState? { val activity: SoftLogoutActivity = (viewModelContext as ActivityViewModelContext).activity() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index 1627fdacae..ad764363db 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -56,7 +56,7 @@ class SpaceMenuViewModel @AssistedInject constructor( fun create(initialState: SpaceMenuState): SpaceMenuViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SpaceMenuState): SpaceMenuViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 12c4ddbfc4..dc69fb5ba0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.spaces import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -71,7 +71,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp fun create(initialState: SpaceListViewState): SpacesListViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SpaceListViewState): SpacesListViewModel { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index e6ead2294e..fd385c413a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -93,7 +93,7 @@ class CreateSpaceViewModel @AssistedInject constructor( super.onCleared() } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: CreateSpaceState): CreateSpaceViewModel? { val factory = when (viewModelContext) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index f25241d2dc..d07b486fee 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -20,7 +20,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -55,7 +55,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( fun create(initialState: SpaceDirectoryState): SpaceDirectoryViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceDirectoryState): SpaceDirectoryViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt index 55b265f244..00fb6c4054 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/invite/SpaceInviteBottomSheetViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -97,7 +97,7 @@ class SpaceInviteBottomSheetViewModel @AssistedInject constructor( fun create(initialState: SpaceInviteBottomSheetState): SpaceInviteBottomSheetViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceInviteBottomSheetState): SpaceInviteBottomSheetViewModel? { val factory = when (viewModelContext) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 7461d09b8b..0daa2522e9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -129,7 +129,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( fun create(initialState: SpaceLeaveAdvanceViewState): SpaceLeaveAdvancedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceLeaveAdvanceViewState): SpaceLeaveAdvancedViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt index 9f5cd7d35e..bf062ce0a8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomsViewModel.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -132,7 +132,7 @@ class SpaceAddRoomsViewModel @AssistedInject constructor( } } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceAddRoomsState): SpaceAddRoomsViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt index b1f6d5c3c3..d36e62db13 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -60,7 +60,7 @@ class SpaceManageRoomsViewModel @AssistedInject constructor( fun create(initialState: SpaceManageRoomViewState): SpaceManageRoomsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceManageRoomViewState): SpaceManageRoomsViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt index 8f23788d19..133054236e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageSharedViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.spaces.manage import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -37,7 +37,7 @@ class SpaceManageSharedViewModel @AssistedInject constructor( fun create(initialState: SpaceManageViewState): SpaceManageSharedViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpaceManageViewState): SpaceManageSharedViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt index 95cf5fb461..efa7d97e9c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -48,7 +48,7 @@ class SpacePeopleViewModel @AssistedInject constructor( fun create(initialState: SpacePeopleViewState): SpacePeopleViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpacePeopleViewState): SpacePeopleViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 0f1afd8371..d71a4bef46 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -62,7 +62,7 @@ class SpacePreviewViewModel @AssistedInject constructor( fun create(initialState: SpacePreviewState): SpacePreviewViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: SpacePreviewState): SpacePreviewViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt index 2cc4fd388c..d96cecbfbb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/share/ShareSpaceViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.spaces.share import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -42,7 +42,7 @@ class ShareSpaceViewModel @AssistedInject constructor( fun create(initialState: ShareSpaceViewState): ShareSpaceViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: ShareSpaceViewState): ShareSpaceViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt index 4ecad80876..48d6d7dc45 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.terms import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -41,7 +41,7 @@ class ReviewTermsViewModel @AssistedInject constructor( fun create(initialState: ReviewTermsViewState): ReviewTermsViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ReviewTermsViewState): ReviewTermsViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 071044fc8a..63623d519c 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -19,7 +19,7 @@ package im.vector.app.features.usercode import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -45,7 +45,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( private val directRoomHelper: DirectRoomHelper, private val rawService: RawService) : VectorViewModel(initialState) { - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? { val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index dead957795..7eb7ce95ad 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.userdirectory import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay @@ -63,7 +63,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User fun create(initialState: UserListViewState): UserListViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: UserListViewState): UserListViewModel? { val factory = when (viewModelContext) { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index bf27173d83..ced9d45d92 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -60,7 +60,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi fun create(initialState: WidgetViewState): WidgetViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: WidgetViewState): WidgetViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index 844a6619b4..0ed4e7d771 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.widgets.permissions import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -144,7 +144,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in fun create(initialState: RoomWidgetPermissionViewState): RoomWidgetPermissionViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomWidgetPermissionViewState): RoomWidgetPermissionViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 619f14e559..0076bf580e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -21,7 +21,7 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -75,7 +75,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ServerBackupStatusViewState): ServerBackupStatusViewModel? { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 7916caca66..8190a37056 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext @@ -69,7 +69,7 @@ class SignoutCheckViewModel @AssistedInject constructor( fun create(initialState: SignoutCheckViewState): SignoutCheckViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SignoutCheckViewState): SignoutCheckViewModel? { From 2ef4cd276bdd8e0b00b98df3a953bd771545c0a5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 18:04:06 +0200 Subject: [PATCH 008/172] Mavericks 2: replace selectSubscribe by onEach --- .../app/features/call/VectorCallActivity.kt | 2 +- .../contactsbook/ContactsBookViewModel.kt | 2 +- .../createdirect/CreateDirectRoomActivity.kt | 2 +- .../settings/KeysBackupManageActivity.kt | 2 +- .../app/features/home/HomeDetailFragment.kt | 6 +++--- .../detail/JoinReplacementRoomBottomSheet.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 8 ++++---- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../home/room/detail/RoomDetailViewState.kt | 2 +- .../action/MessageActionsViewModel.kt | 4 ++-- .../home/room/list/RoomListFragment.kt | 2 +- .../picker/RoomDirectoryPickerViewModel.kt | 2 +- .../settings/RoomSettingsViewModel.kt | 2 +- .../settings/joinrule/RoomJoinRuleActivity.kt | 2 +- .../spaces/explore/SpaceDirectoryFragment.kt | 2 +- .../spaces/manage/SpaceAddRoomFragment.kt | 20 +++++++++---------- .../spaces/manage/SpaceManageRoomsFragment.kt | 2 +- .../app/features/usercode/UserCodeActivity.kt | 2 +- .../userdirectory/UserListFragment.kt | 2 +- .../app/features/widgets/WidgetActivity.kt | 8 ++++---- 20 files changed, 38 insertions(+), 38 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 64abdef72a..1a6e8d162a 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -152,7 +152,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } .disposeOnDestroy() - callViewModel.selectSubscribe(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> + callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> if (isVideoCall) { if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_camera_and_audio)) { setupRenderersIfNeeded() diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt index e37fc94b51..4123841ab9 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt @@ -66,7 +66,7 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted init { loadContacts() - selectSubscribe(ContactsBookViewState::searchTerm, ContactsBookViewState::onlyBoundContacts) { _, _ -> + onEach(ContactsBookViewState::searchTerm, ContactsBookViewState::onlyBoundContacts) { _, _ -> updateFilteredMappedContacts() } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 752a7d0d83..32395144f2 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -102,7 +102,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac ) ) } - viewModel.selectSubscribe(CreateDirectRoomViewState::createAndInviteState) { + viewModel.onEach(CreateDirectRoomViewState::createAndInviteState) { renderCreateAndInviteState(it) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt index c03488ab5d..716f02369a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/settings/KeysBackupManageActivity.kt @@ -55,7 +55,7 @@ class KeysBackupManageActivity : SimpleFragmentActivity() { } // Observe the deletion of keys backup - viewModel.selectSubscribe(KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete -> + viewModel.onEach(KeysBackupSettingViewState::deleteBackupRequest) { asyncDelete -> when (asyncDelete) { is Fail -> { updateWaitingView(null) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index b6d628071b..790dd6e466 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -134,7 +134,7 @@ class HomeDetailFragment @Inject constructor( views.bottomNavigationView.selectedItemId = it.currentTab.toMenuId() } - viewModel.selectSubscribe(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> + viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> when (roomGroupingMethod) { is RoomGroupingMethod.ByLegacyGroup -> { onGroupChange(roomGroupingMethod.groupSummary) @@ -145,11 +145,11 @@ class HomeDetailFragment @Inject constructor( } } - viewModel.selectSubscribe(HomeDetailViewState::currentTab) { currentTab -> + viewModel.onEach(HomeDetailViewState::currentTab) { currentTab -> updateUIForTab(currentTab) } - viewModel.selectSubscribe(HomeDetailViewState::showDialPadTab) { showDialPadTab -> + viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab -> updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt index 9cfca9ecd2..7159f7b33a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt @@ -61,7 +61,7 @@ class JoinReplacementRoomBottomSheet : } } - viewModel.selectSubscribe(RoomDetailViewState::joinUpgradedRoomAsync) { joinState -> + viewModel.onEach(RoomDetailViewState::joinUpgradedRoomAsync) { joinState -> when (joinState) { // it should never be Uninitialized Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index c6eda584ad..776b02d814 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -375,13 +375,13 @@ class RoomDetailFragment @Inject constructor( invalidateOptionsMenu() } - roomDetailViewModel.selectSubscribe(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ -> + roomDetailViewModel.onEach(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ -> updateJumpToReadMarkerViewVisibility() } - roomDetailViewModel.selectSubscribe(RoomDetailViewState::sendMode, RoomDetailViewState::canSendMessage) { mode, canSend -> + roomDetailViewModel.onEach(RoomDetailViewState::sendMode, RoomDetailViewState::canSendMessage) { mode, canSend -> if (!canSend) { - return@selectSubscribe + return@onEach } when (mode) { is SendMode.REGULAR -> renderRegularMode(mode.text) @@ -391,7 +391,7 @@ class RoomDetailFragment @Inject constructor( } } - roomDetailViewModel.selectSubscribe( + roomDetailViewModel.onEach( RoomDetailViewState::syncState, RoomDetailViewState::incrementalSyncStatus, RoomDetailViewState::pushCounter diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index f565794094..9e1a362fc6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1576,7 +1576,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeUnreadState() { - selectSubscribe(RoomDetailViewState::unreadState) { + onEach(RoomDetailViewState::unreadState) { Timber.v("Unread state: $it") if (it is UnreadState.HasNoUnread) { startTrackingUnreadMessages() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 6dee6d2b00..a2462c798d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -42,7 +42,7 @@ sealed class SendMode(open val text: String) { data class REGULAR( override val text: String, val fromSharing: Boolean, - // This is necessary for forcing refresh on selectSubscribe + // This is necessary for forcing refresh on onEach private val ts: Long = System.currentTimeMillis() ) : SendMode(text) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 5792187eb5..f63366482b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -163,8 +163,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } private fun observeTimelineEventState() { - selectSubscribe(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions -> - val nonNullTimelineEvent = timelineEvent() ?: return@selectSubscribe + onEach(MessageActionState::timelineEvent, MessageActionState::actionPermissions) { timelineEvent, permissions -> + val nonNullTimelineEvent = timelineEvent() ?: return@onEach eventIdFlow.tryEmit(nonNullTimelineEvent.eventId) setState { copy( 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 2df90a6c16..19ea856da6 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 @@ -123,7 +123,7 @@ class RoomListFragment @Inject constructor( .subscribe { handleQuickActions(it) } .disposeOnDestroyView() - roomListViewModel.selectSubscribe(RoomListViewState::roomMembershipChanges) { ms -> + roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms -> // it's for invites local echo adapterInfosList.filter { it.section.notifyOfLocalEcho } .onEach { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index a5f5801885..3f73b80bc6 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -66,7 +66,7 @@ class RoomDirectoryPickerViewModel @AssistedInject constructor( } private fun observeAndCompute() { - selectSubscribe( + onEach( RoomDirectoryPickerViewState::asyncThirdPartyRequest, RoomDirectoryPickerViewState::customHomeservers ) { async, custom -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 4491ff8606..ea939c153e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -101,7 +101,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeState() { - selectSubscribe( + onEach( RoomSettingsViewState::avatarAction, RoomSettingsViewState::newName, RoomSettingsViewState::newTopic, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index dc0d06ac86..06ebd01532 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -78,7 +78,7 @@ class RoomJoinRuleActivity : VectorBaseActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.selectSubscribe(RoomJoinRuleChooseRestrictedState::updatingStatus) { + viewModel.onEach(RoomJoinRuleChooseRestrictedState::updatingStatus) { when (it) { Uninitialized -> { // nop diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index b03d1e0b14..6cf4f9e0f6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -110,7 +110,7 @@ class SpaceDirectoryFragment @Inject constructor( views.spaceDirectoryList.configureWith(epoxyController) epoxyVisibilityTracker.attach(views.spaceDirectoryList) - viewModel.selectSubscribe(SpaceDirectoryState::canAddRooms) { + viewModel.onEach(SpaceDirectoryState::canAddRooms) { invalidateOptionsMenu() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index cea4d0059e..0512a478a1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -91,35 +91,35 @@ class SpaceAddRoomFragment @Inject constructor( invalidateOptionsMenu() } - viewModel.selectSubscribe(SpaceAddRoomsState::spaceName) { + viewModel.onEach(SpaceAddRoomsState::spaceName) { views.appBarSpaceInfo.text = it - }.disposeOnDestroyView() + } - viewModel.selectSubscribe(SpaceAddRoomsState::ignoreRooms) { + viewModel.onEach(SpaceAddRoomsState::ignoreRooms) { spaceEpoxyController.ignoreRooms = it roomEpoxyController.ignoreRooms = it dmEpoxyController.ignoreRooms = it - }.disposeOnDestroyView() + } - viewModel.selectSubscribe(SpaceAddRoomsState::isSaving) { + viewModel.onEach(SpaceAddRoomsState::isSaving) { if (it is Loading) { sharedViewModel.handle(SpaceManagedSharedAction.ShowLoading) } else { sharedViewModel.handle(SpaceManagedSharedAction.HideLoading) } - }.disposeOnDestroyView() + } - viewModel.selectSubscribe(SpaceAddRoomsState::shouldShowDMs) { + viewModel.onEach(SpaceAddRoomsState::shouldShowDMs) { dmEpoxyController.disabled = !it - }.disposeOnDestroyView() + } - viewModel.selectSubscribe(SpaceAddRoomsState::onlyShowSpaces) { + viewModel.onEach(SpaceAddRoomsState::onlyShowSpaces) { spaceEpoxyController.disabled = !it roomEpoxyController.disabled = it views.createNewRoom.text = if (it) getString(R.string.create_space) else getString(R.string.create_new_room) val title = if (it) getString(R.string.space_add_existing_spaces) else getString(R.string.space_add_existing_rooms_only) views.appBarTitle.text = title - }.disposeOnDestroyView() + } views.createNewRoom.debouncedClicks { withState(viewModel) { state -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt index 186d733982..8e16784a6d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageRoomsFragment.kt @@ -80,7 +80,7 @@ class SpaceManageRoomsFragment @Inject constructor( } .disposeOnDestroyView() - viewModel.selectSubscribe(SpaceManageRoomViewState::actionState) { actionState -> + viewModel.onEach(SpaceManageRoomViewState::actionState) { actionState -> when (actionState) { is Loading -> { sharedViewModel.handle(SpaceManagedSharedAction.ShowLoading) diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 064905f188..d1a666546e 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -68,7 +68,7 @@ class UserCodeActivity : VectorBaseActivity(), showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) } - sharedViewModel.selectSubscribe(UserCodeState::mode) { mode -> + sharedViewModel.onEach(UserCodeState::mode) { mode -> when (mode) { UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 843ead0eb4..6a9dc4e47d 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -85,7 +85,7 @@ class UserListFragment @Inject constructor( views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } - viewModel.selectSubscribe(UserListViewState::pendingSelections) { + viewModel.onEach(UserListViewState::pendingSelections) { renderSelectedUsers(it) } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 31895dda16..82d426999b 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -102,14 +102,14 @@ class WidgetActivity : VectorBaseActivity(), } } - viewModel.selectSubscribe(WidgetViewState::status) { ws -> + viewModel.onEach(WidgetViewState::status) { ws -> when (ws) { WidgetStatus.UNKNOWN -> { } WidgetStatus.WIDGET_NOT_ALLOWED -> { val dFrag = supportFragmentManager.findFragmentByTag(WIDGET_PERMISSION_FRAGMENT_TAG) as? RoomWidgetPermissionBottomSheet if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { - return@selectSubscribe + return@onEach } else { RoomWidgetPermissionBottomSheet .newInstance(widgetArgs) @@ -124,11 +124,11 @@ class WidgetActivity : VectorBaseActivity(), } } - viewModel.selectSubscribe(WidgetViewState::widgetName) { name -> + viewModel.onEach(WidgetViewState::widgetName) { name -> supportActionBar?.title = name } - viewModel.selectSubscribe(WidgetViewState::canManageWidgets) { + viewModel.onEach(WidgetViewState::canManageWidgets) { invalidateOptionsMenu() } } From 96b5d1c96bda1ec222f0791a27ae3de14db2289f Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 18:11:14 +0200 Subject: [PATCH 009/172] Mavericks 2: initialize with debug instead of context --- vector/src/main/java/im/vector/app/VectorApplication.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 3e4cf3ff85..8a8efed567 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -138,7 +138,7 @@ class VectorApplication : } logInfo() LazyThreeTen.init(this) - Mavericks.initialize(this) + Mavericks.initialize(debugMode = false) EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager)) From 43c75bdae7647ab590a34020c2010d4fc1155f7d Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 18:11:41 +0200 Subject: [PATCH 010/172] Mavericks 2: replace asyncSubscribe by onAsync --- .../java/im/vector/app/features/call/VectorCallActivity.kt | 2 +- .../app/features/home/room/detail/RoomDetailViewModel.kt | 4 ++-- .../home/room/detail/widget/RoomWidgetsBottomSheet.kt | 2 +- .../java/im/vector/app/features/widgets/WidgetViewModel.kt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 1a6e8d162a..e41cb261ca 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -138,7 +138,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro renderState(it) } - callViewModel.asyncSubscribe(VectorCallViewState::callState) { + callViewModel.onAsync(VectorCallViewState::callState) { if (it is CallState.Ended) { handleCallEnded(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 9e1a362fc6..1f6fec0410 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -269,7 +269,7 @@ class RoomDetailViewModel @AssistedInject constructor( copy(activeRoomWidgets = widgets) } - asyncSubscribe(RoomDetailViewState::activeRoomWidgets) { widgets -> + onAsync(RoomDetailViewState::activeRoomWidgets) { widgets -> setState { val jitsiWidget = widgets.firstOrNull { it.type == WidgetType.Jitsi } val jitsiConfId = jitsiWidget?.let { @@ -1597,7 +1597,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeSummaryState() { - asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> + onAsync(RoomDetailViewState::asyncRoomSummary) { summary -> setState { val typingMessage = typingHelper.getTypingMessage(summary.typingUsers) copy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt index 3b305abbe4..cba2635ede 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt @@ -65,7 +65,7 @@ class RoomWidgetsBottomSheet : views.bottomSheetTitle.textSize = 20f views.bottomSheetTitle.setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) epoxyController.listener = this - roomDetailViewModel.asyncSubscribe(RoomDetailViewState::activeRoomWidgets) { + roomDetailViewModel.onAsync(RoomDetailViewState::activeRoomWidgets) { epoxyController.setData(it) } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index ced9d45d92..44d246885a 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -101,7 +101,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi } private fun subscribeToWidget() { - asyncSubscribe(WidgetViewState::asyncWidget) { + onAsync(WidgetViewState::asyncWidget) { setState { copy(widgetName = it.name) } } } From 0e01c64f69020504015d6a2fe93db3d0306968db Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 1 Oct 2021 18:27:36 +0200 Subject: [PATCH 011/172] Mavericks: continue removing reference to old MvRx API --- .../vector/app/core/extensions/Parcelable.kt | 1 - .../VectorBaseBottomSheetDialogFragment.kt | 9 +++--- .../app/core/platform/VectorBaseFragment.kt | 5 +-- .../vector/app/core/services/CallService.kt | 4 +-- .../app/features/auth/ReAuthActivity.kt | 6 ++-- .../app/features/call/VectorCallActivity.kt | 10 +++--- .../call/conference/VectorJitsiActivity.kt | 8 ++--- .../call/transfer/CallTransferActivity.kt | 4 +-- .../quads/SharedSecureStorageActivity.kt | 4 +-- .../quads/SharedSecureStorageViewModel.kt | 4 +-- .../verification/VerificationBottomSheet.kt | 32 +++++++++---------- .../VerificationQRWaitingFragment.kt | 4 +-- .../features/devtools/RoomDevToolActivity.kt | 4 +-- .../vector/app/features/home/HomeActivity.kt | 8 ++--- .../features/home/HomeActivityViewModel.kt | 4 +-- .../home/room/detail/RoomDetailFragment.kt | 4 +-- .../DisplayReadReceiptsBottomSheet.kt | 4 +-- .../home/room/detail/search/SearchActivity.kt | 6 ++-- .../edithistory/ViewEditHistoryBottomSheet.kt | 4 +-- .../reactions/ViewReactionsBottomSheet.kt | 4 +-- .../invite/InviteUsersToRoomActivity.kt | 4 +-- .../features/matrixto/MatrixToBottomSheet.kt | 4 +-- .../im/vector/app/features/pin/PinActivity.kt | 6 ++-- .../RoomMemberProfileActivity.kt | 6 ++-- .../devices/DeviceListBottomSheet.kt | 4 +-- .../roomprofile/RoomProfileActivity.kt | 6 ++-- .../settings/joinrule/RoomJoinRuleActivity.kt | 6 ++-- .../DeviceVerificationInfoBottomSheet.kt | 4 +-- .../features/spaces/SpaceCreationActivity.kt | 2 +- .../features/spaces/SpaceExploreActivity.kt | 8 ++--- .../features/spaces/SpacePreviewActivity.kt | 8 ++--- .../leave/SpaceLeaveAdvancedActivity.kt | 8 ++--- .../spaces/manage/SpaceManageActivity.kt | 10 +++--- .../spaces/people/SpacePeopleActivity.kt | 8 ++--- .../app/features/usercode/UserCodeActivity.kt | 4 +-- .../app/features/widgets/WidgetActivity.kt | 6 ++-- .../RoomWidgetPermissionBottomSheet.kt | 4 +-- 37 files changed, 114 insertions(+), 113 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt b/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt index 87f5ac84f7..65f59f7b73 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Parcelable.kt @@ -19,7 +19,6 @@ package im.vector.app.core.extensions import android.os.Bundle import android.os.Parcelable import com.airbnb.mvrx.Mavericks -import com.airbnb.mvrx.MvRx fun Parcelable?.toMvRxBundle(): Bundle? { return this?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 9adaed288a..d13e446ed4 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -27,7 +27,8 @@ import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.MavericksView +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MvRxView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -43,9 +44,9 @@ import timber.log.Timber import java.util.concurrent.TimeUnit /** - * Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment) + * Add Mavericks capabilities, handle DI and bindings. */ -abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView { +abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MavericksView { private lateinit var screenComponent: ScreenComponent @@ -166,7 +167,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe } protected fun setArguments(args: Parcelable? = null) { - arguments = args?.let { Bundle().apply { putParcelable(MvRx.KEY_ARG, it) } } + arguments = args?.let { Bundle().apply { putParcelable(Mavericks.KEY_ARG, it) } } } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 2c9452c4fd..5077982460 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -27,9 +27,10 @@ import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.BaseMvRxFragment +import com.airbnb.mvrx.MvRxView import com.bumptech.glide.util.Util.assertMainThread import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -49,7 +50,7 @@ import io.reactivex.disposables.Disposable import timber.log.Timber import java.util.concurrent.TimeUnit -abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { +abstract class VectorBaseFragment : Fragment(), MvRxView, HasScreenInjector { protected val vectorBaseActivity: VectorBaseActivity<*> by lazy { activity as VectorBaseActivity<*> diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt index cd3845f41b..3c7cef5ce1 100644 --- a/vector/src/main/java/im/vector/app/core/services/CallService.kt +++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt @@ -25,7 +25,7 @@ import android.view.KeyEvent import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import im.vector.app.core.extensions.vectorComponent import im.vector.app.features.call.CallArgs import im.vector.app.features.call.VectorCallActivity @@ -165,7 +165,7 @@ class CallService : VectorService() { val incomingCallAlert = IncomingCallAlert(callId, shouldBeDisplayedIn = { activity -> if (activity is VectorCallActivity) { - activity.intent.getParcelableExtra(MvRx.KEY_ARG)?.callId != call.callId + activity.intent.getParcelableExtra(Mavericks.KEY_ARG)?.callId != call.callId } else true } ).apply { diff --git a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt index ce23111a95..b7f570672b 100644 --- a/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt +++ b/vector/src/main/java/im/vector/app/features/auth/ReAuthActivity.kt @@ -26,7 +26,7 @@ import androidx.browser.customtabs.CustomTabsCallback import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection import androidx.browser.customtabs.CustomTabsSession -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -78,7 +78,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory { val title = intent.extras?.getString(EXTRA_REASON_TITLE) ?: getString(R.string.re_authentication_activity_title) supportActionBar?.setTitle(title) ?: run { setTitle(title) } -// val authArgs = intent.getParcelableExtra(MvRx.KEY_ARG) +// val authArgs = intent.getParcelableExtra(Mavericks.KEY_ARG) // For the sso flow we can for now only rely on the fallback flow, that handles all // the UI, due to the sandbox nature of CCT (chrome custom tab) we cannot get much information @@ -221,7 +221,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory { } } return Intent(context, ReAuthActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, Args(authType, reasonTitle, fromError.session, lastErrorCode, resultKeyStoreAlias)) + putExtra(Mavericks.KEY_ARG, Args(authType, reasonTitle, fromError.session, lastErrorCode, resultKeyStoreAlias)) } } } diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index e41cb261ca..5222ee842b 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -36,7 +36,7 @@ import androidx.core.content.getSystemService import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import com.google.android.material.card.MaterialCardView @@ -167,8 +167,8 @@ class VectorCallActivity : VectorBaseActivity(), CallContro override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - intent?.takeIf { it.hasExtra(MvRx.KEY_ARG) } - ?.let { intent.getParcelableExtra(MvRx.KEY_ARG) } + intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) } + ?.let { intent.getParcelableExtra(Mavericks.KEY_ARG) } ?.let { callViewModel.handle(VectorCallViewActions.SwitchCall(it)) } @@ -633,7 +633,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro return Intent(context, VectorCallActivity::class.java).apply { // what could be the best flags? flags = Intent.FLAG_ACTIVITY_NEW_TASK - putExtra(MvRx.KEY_ARG, CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall)) + putExtra(Mavericks.KEY_ARG, CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall)) putExtra(EXTRA_MODE, mode) } } @@ -648,7 +648,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro return Intent(context, VectorCallActivity::class.java).apply { // what could be the best flags? flags = Intent.FLAG_ACTIVITY_NEW_TASK - putExtra(MvRx.KEY_ARG, CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall)) + putExtra(Mavericks.KEY_ARG, CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall)) putExtra(EXTRA_MODE, mode) } } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index e7fd541f3d..62d017467f 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -28,7 +28,7 @@ import android.widget.Toast import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Success import com.airbnb.mvrx.viewModel import com.facebook.react.modules.core.PermissionListener @@ -205,8 +205,8 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee JitsiMeetActivityDelegate.onNewIntent(intent) // Is it a switch to another conf? - intent?.takeIf { it.hasExtra(MvRx.KEY_ARG) } - ?.let { intent.getParcelableExtra(MvRx.KEY_ARG) } + intent?.takeIf { it.hasExtra(Mavericks.KEY_ARG) } + ?.let { intent.getParcelableExtra(Mavericks.KEY_ARG) } ?.let { jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(it, true)) } @@ -242,7 +242,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee companion object { fun newIntent(context: Context, roomId: String, widgetId: String, enableVideo: Boolean): Intent { return Intent(context, VectorJitsiActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, Args(roomId, widgetId, enableVideo)) + putExtra(Mavericks.KEY_ARG, Args(roomId, widgetId, enableVideo)) } } } diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt index c80b21334a..2a50dc85f9 100644 --- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt @@ -20,7 +20,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.os.Parcelable -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.tabs.TabLayoutMediator import im.vector.app.R @@ -121,7 +121,7 @@ class CallTransferActivity : VectorBaseActivity(), fun newIntent(context: Context, callId: String): Intent { return Intent(context, CallTransferActivity::class.java).also { - it.putExtra(MvRx.KEY_ARG, CallTransferArgs(callId)) + it.putExtra(Mavericks.KEY_ARG, CallTransferArgs(callId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index 51159e62bf..bd7195ad1e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -25,7 +25,7 @@ import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentOnAttachListener -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R @@ -159,7 +159,7 @@ class SharedSecureStorageActivity : resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent { require(requestedSecrets.isNotEmpty()) return Intent(context, SharedSecureStorageActivity::class.java).also { - it.putExtra(MvRx.KEY_ARG, Args( + it.putExtra(Mavericks.KEY_ARG, Args( keyId, requestedSecrets, resultKeyStoreAlias diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index ecaf89a1e5..151b73ff32 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success @@ -327,7 +327,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( @JvmStatic override fun create(viewModelContext: ViewModelContext, state: SharedSecureStorageViewState): SharedSecureStorageViewModel? { val activity: SharedSecureStorageActivity = viewModelContext.activity() - val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(MvRx.KEY_ARG) ?: error("Missing args") + val args: SharedSecureStorageActivity.Args = activity.intent.getParcelableExtra(Mavericks.KEY_ARG) ?: error("Missing args") return activity.viewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 47033199f4..dd6cf6cbd6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -24,7 +24,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -184,7 +184,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment { showFragment(VerificationEmojiCodeFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationArgs( + putParcelable(Mavericks.KEY_ARG, VerificationArgs( state.otherUserMxItem?.id ?: "", // If it was outgoing it.transaction id would be null, but the pending request // would be updated (from localId to txId) @@ -244,12 +244,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment { showFragment(VerificationConclusionFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) + putParcelable(Mavericks.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) } is VerificationTxState.Cancelled -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.sasTransactionState.cancelCode.value, state.isMe)) + putParcelable(Mavericks.KEY_ARG, VerificationConclusionFragment.Args(false, state.sasTransactionState.cancelCode.value, state.isMe)) }) } } @@ -265,7 +265,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment { showFragment(VerificationQRWaitingFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationQRWaitingFragment.Args( + putParcelable(Mavericks.KEY_ARG, VerificationQRWaitingFragment.Args( isMe = state.isMe, otherUserName = state.otherUserMxItem?.getBestName() ?: "" )) @@ -274,13 +274,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment { showFragment(VerificationConclusionFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) + putParcelable(Mavericks.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) return@withState } is VerificationTxState.Cancelled -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe)) + putParcelable(Mavericks.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe)) }) return@withState } @@ -294,7 +294,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment(MvRx.KEY_ARG) + val args = intent.getParcelableExtra(Mavericks.KEY_ARG) if (args?.clearNotification == true) { notificationDrawerManager.clearAllEvents() @@ -433,7 +433,7 @@ class HomeActivity : override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - val parcelableExtra = intent?.getParcelableExtra(MvRx.KEY_ARG) + val parcelableExtra = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (parcelableExtra?.clearNotification == true) { notificationDrawerManager.clearAllEvents() } @@ -580,7 +580,7 @@ class HomeActivity : return Intent(context, HomeActivity::class.java) .apply { - putExtra(MvRx.KEY_ARG, args) + putExtra(Mavericks.KEY_ARG, args) } } } 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 297cffd627..fb2401edc1 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 @@ -17,7 +17,7 @@ package im.vector.app.features.home import androidx.lifecycle.viewModelScope -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted @@ -72,7 +72,7 @@ class HomeActivityViewModel @AssistedInject constructor( @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeActivityViewState): HomeActivityViewModel? { val activity: HomeActivity = viewModelContext.activity() - val args: HomeActivityArgs? = activity.intent.getParcelableExtra(MvRx.KEY_ARG) + val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG) return activity.viewModelFactory.create(state, args ?: HomeActivityArgs(clearNotification = false, accountCreation = false)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 776b02d814..5da00b7e2f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -61,7 +61,7 @@ import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader import com.airbnb.epoxy.glidePreloader -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -1582,7 +1582,7 @@ class RoomDetailFragment @Inject constructor( val otherUserId = data.otherUserId ?: return VerificationBottomSheet().apply { arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, VerificationBottomSheet.VerificationArgs( + putParcelable(Mavericks.KEY_ARG, VerificationBottomSheet.VerificationArgs( otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) } }.show(parentFragmentManager, "REQ") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt index 95b2333b43..9f98d655cc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/readreceipts/DisplayReadReceiptsBottomSheet.kt @@ -21,7 +21,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.args import im.vector.app.R import im.vector.app.core.di.ScreenComponent @@ -88,7 +88,7 @@ class DisplayReadReceiptsBottomSheet : val parcelableArgs = DisplayReadReceiptArgs( readReceipts ) - args.putParcelable(MvRx.KEY_ARG, parcelableArgs) + args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return DisplayReadReceiptsBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt index d9a2f98e32..88ed101252 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchActivity.kt @@ -20,7 +20,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.widget.SearchView -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment @@ -49,7 +49,7 @@ class SearchActivity : VectorBaseActivity() { override fun initUiAndData() { if (isFirstCreation()) { - val fragmentArgs: SearchArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + val fragmentArgs: SearchArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return addFragment(R.id.searchFragmentContainer, SearchFragment::class.java, fragmentArgs, FRAGMENT_TAG) } views.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { @@ -73,7 +73,7 @@ class SearchActivity : VectorBaseActivity() { return Intent(context, SearchActivity::class.java).apply { // If we do that we will have the same room two times on the stack. Let's allow infinite stack for the moment. // flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT - putExtra(MvRx.KEY_ARG, args) + putExtra(Mavericks.KEY_ARG, args) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt index 7be4be4b57..d9fd80f02f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryBottomSheet.kt @@ -19,7 +19,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -79,7 +79,7 @@ class ViewEditHistoryBottomSheet: roomId, informationData ) - args.putParcelable(MvRx.KEY_ARG, parcelableArgs) + args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return ViewEditHistoryBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index 9cc3be9ac1..552ef9552d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -20,7 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -93,7 +93,7 @@ class ViewReactionsBottomSheet : roomId, informationData ) - args.putParcelable(MvRx.KEY_ARG, parcelableArgs) + args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return ViewReactionsBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 88998861bc..dd07319e9f 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -21,7 +21,7 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.View -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R @@ -165,7 +165,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa fun getIntent(context: Context, roomId: String): Intent { return Intent(context, InviteUsersToRoomActivity::class.java).also { - it.putExtra(MvRx.KEY_ARG, InviteUsersToRoomArgs(roomId)) + it.putExtra(Mavericks.KEY_ARG, InviteUsersToRoomArgs(roomId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 3e75b96c32..e53cf1da98 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -24,7 +24,7 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -134,7 +134,7 @@ class MatrixToBottomSheet : fun withLink(matrixToLink: String, listener: InteractionListener?): MatrixToBottomSheet { return MatrixToBottomSheet().apply { arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, MatrixToArgs( + putParcelable(Mavericks.KEY_ARG, MatrixToArgs( matrixToLink = matrixToLink )) } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt index 6866afa0a6..a335f1a1f2 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt @@ -19,7 +19,7 @@ package im.vector.app.features.pin import android.content.Context import android.content.Intent import com.google.android.material.appbar.MaterialToolbar -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.ToolbarConfigurable @@ -31,7 +31,7 @@ class PinActivity : VectorBaseActivity(), ToolbarConfigur companion object { fun newIntent(context: Context, args: PinArgs): Intent { return Intent(context, PinActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, args) + putExtra(Mavericks.KEY_ARG, args) } } } @@ -42,7 +42,7 @@ class PinActivity : VectorBaseActivity(), ToolbarConfigur override fun initUiAndData() { if (isFirstCreation()) { - val fragmentArgs: PinArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + val fragmentArgs: PinArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return addFragment(R.id.simpleFragmentContainer, PinFragment::class.java, fragmentArgs) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt index 37ad2741ca..02082f7751 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileActivity.kt @@ -21,7 +21,7 @@ import android.content.Context import android.content.Intent import android.widget.Toast import com.google.android.material.appbar.MaterialToolbar -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import im.vector.app.R import im.vector.app.core.di.ScreenComponent @@ -42,7 +42,7 @@ class RoomMemberProfileActivity : companion object { fun newIntent(context: Context, args: RoomMemberProfileArgs): Intent { return Intent(context, RoomMemberProfileActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, args) + putExtra(Mavericks.KEY_ARG, args) } } } @@ -66,7 +66,7 @@ class RoomMemberProfileActivity : override fun initUiAndData() { if (isFirstCreation()) { - val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + val fragmentArgs: RoomMemberProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return addFragment(R.id.simpleFragmentContainer, RoomMemberProfileFragment::class.java, fragmentArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt index f0cdb1cdd1..05ccc57b10 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -24,7 +24,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -122,7 +122,7 @@ class DeviceListBottomSheet : companion object { fun newInstance(userId: String, allowDeviceAction: Boolean = true): DeviceListBottomSheet { val args = Bundle() - args.putParcelable(MvRx.KEY_ARG, Args(userId, allowDeviceAction)) + args.putParcelable(Mavericks.KEY_ARG, Args(userId, allowDeviceAction)) return DeviceListBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index d28878283f..d93d278614 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -21,7 +21,7 @@ import android.content.Context import android.content.Intent import android.widget.Toast import com.google.android.material.appbar.MaterialToolbar -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import im.vector.app.R import im.vector.app.core.di.ScreenComponent @@ -60,7 +60,7 @@ class RoomProfileActivity : fun newIntent(context: Context, roomId: String, directAccess: Int?): Intent { val roomProfileArgs = RoomProfileArgs(roomId) return Intent(context, RoomProfileActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, roomProfileArgs) + putExtra(Mavericks.KEY_ARG, roomProfileArgs) putExtra(EXTRA_DIRECT_ACCESS, directAccess) } } @@ -91,7 +91,7 @@ class RoomProfileActivity : override fun initUiAndData() { sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) - roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + roomProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return if (isFirstCreation()) { when (intent?.extras?.getInt(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOM_ROOT)) { EXTRA_DIRECT_ACCESS_ROOM_SETTINGS -> { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt index 06ebd01532..dcce7b2384 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleActivity.kt @@ -22,7 +22,7 @@ import android.os.Bundle import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.viewModel @@ -66,7 +66,7 @@ class RoomJoinRuleActivity : VectorBaseActivity(), } override fun initUiAndData() { - roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + roomProfileArgs = intent?.extras?.getParcelable(Mavericks.KEY_ARG) ?: return if (isFirstCreation()) { addFragment( R.id.simpleFragmentContainer, @@ -142,7 +142,7 @@ class RoomJoinRuleActivity : VectorBaseActivity(), fun newIntent(context: Context, roomId: String): Intent { val roomProfileArgs = RoomProfileArgs(roomId) return Intent(context, RoomJoinRuleActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, roomProfileArgs) + putExtra(Mavericks.KEY_ARG, roomProfileArgs) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheet.kt index 9bfd2df0d6..7ba6042027 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheet.kt @@ -21,7 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState @@ -83,7 +83,7 @@ class DeviceVerificationInfoBottomSheet : fun newInstance(userId: String, deviceId: String): DeviceVerificationInfoBottomSheet { val args = Bundle() val parcelableArgs = DeviceVerificationInfoArgs(userId, deviceId) - args.putParcelable(MvRx.KEY_ARG, parcelableArgs) + args.putParcelable(Mavericks.KEY_ARG, parcelableArgs) return DeviceVerificationInfoBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index 344559bc81..1844e8e9ca 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -171,7 +171,7 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac fun newIntent(context: Context): Intent { return Intent(context, SpaceCreationActivity::class.java).apply { - // putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId)) + // putExtra(Mavericks.KEY_ARG, SpaceDirectoryArgs(spaceId)) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt index dbe92d4d93..45c520e37e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt @@ -21,7 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import im.vector.app.R import im.vector.app.core.di.ScreenComponent @@ -72,12 +72,12 @@ class SpaceExploreActivity : VectorBaseActivity(), SpaceD if (isFirstCreation()) { val simpleName = SpaceDirectoryFragment::class.java.simpleName - val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (supportFragmentManager.findFragmentByTag(simpleName) == null) { supportFragmentManager.commitTransaction { replace(R.id.simpleFragmentContainer, SpaceDirectoryFragment::class.java, - Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + Bundle().apply { this.putParcelable(Mavericks.KEY_ARG, args) }, simpleName ) } @@ -107,7 +107,7 @@ class SpaceExploreActivity : VectorBaseActivity(), SpaceD companion object { fun newIntent(context: Context, spaceId: String): Intent { return Intent(context, SpaceExploreActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId)) + putExtra(Mavericks.KEY_ARG, SpaceDirectoryArgs(spaceId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt index 0dcaf9d754..59166529b9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt @@ -19,7 +19,7 @@ package im.vector.app.features.spaces import android.content.Context import android.content.Intent import android.os.Bundle -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.platform.VectorBaseActivity @@ -50,12 +50,12 @@ class SpacePreviewActivity : VectorBaseActivity() { if (isFirstCreation()) { val simpleName = SpacePreviewFragment::class.java.simpleName - val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (supportFragmentManager.findFragmentByTag(simpleName) == null) { supportFragmentManager.commitTransaction { replace(R.id.simpleFragmentContainer, SpacePreviewFragment::class.java, - Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + Bundle().apply { this.putParcelable(Mavericks.KEY_ARG, args) }, simpleName ) } @@ -66,7 +66,7 @@ class SpacePreviewActivity : VectorBaseActivity() { companion object { fun newIntent(context: Context, spaceIdOrAlias: String): Intent { return Intent(context, SpacePreviewActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, SpacePreviewArgs(spaceIdOrAlias)) + putExtra(Mavericks.KEY_ARG, SpacePreviewArgs(spaceIdOrAlias)) } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt index cb66708324..762abf10cb 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt @@ -23,7 +23,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Success import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -74,7 +74,7 @@ class SpaceLeaveAdvancedActivity : VectorBaseActivity(MvRx.KEY_ARG) + val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (isFirstCreation()) { val simpleName = SpaceLeaveAdvancedFragment::class.java.simpleName @@ -83,7 +83,7 @@ class SpaceLeaveAdvancedActivity : VectorBaseActivity(), } .disposeOnDestroy() - val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (isFirstCreation()) { withState(sharedViewModel) { when (it.manageType) { @@ -106,7 +106,7 @@ class SpaceManageActivity : VectorBaseActivity(), supportFragmentManager.commitTransaction { replace(R.id.simpleFragmentContainer, SpaceAddRoomFragment::class.java, - Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + Bundle().apply { this.putParcelable(Mavericks.KEY_ARG, args) }, simpleName ) } @@ -118,7 +118,7 @@ class SpaceManageActivity : VectorBaseActivity(), supportFragmentManager.commitTransaction { replace(R.id.simpleFragmentContainer, SpaceSettingsFragment::class.java, - Bundle().apply { this.putParcelable(MvRx.KEY_ARG, RoomProfileArgs(args.spaceId)) }, + Bundle().apply { this.putParcelable(Mavericks.KEY_ARG, RoomProfileArgs(args.spaceId)) }, simpleName ) } @@ -189,7 +189,7 @@ class SpaceManageActivity : VectorBaseActivity(), companion object { fun newIntent(context: Context, spaceId: String, manageType: ManageType): Intent { return Intent(context, SpaceManageActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, SpaceManageArgs(spaceId, manageType)) + putExtra(Mavericks.KEY_ARG, SpaceManageArgs(spaceId, manageType)) } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index b4b48d7710..3b84a12bc1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -21,7 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.core.view.isGone import androidx.core.view.isVisible -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.hideKeyboard @@ -57,14 +57,14 @@ class SpacePeopleActivity : VectorBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (isFirstCreation()) { val simpleName = SpacePeopleFragment::class.java.simpleName if (supportFragmentManager.findFragmentByTag(simpleName) == null) { supportFragmentManager.commitTransaction { replace(R.id.simpleFragmentContainer, SpacePeopleFragment::class.java, - Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + Bundle().apply { this.putParcelable(Mavericks.KEY_ARG, args) }, simpleName ) } @@ -97,7 +97,7 @@ class SpacePeopleActivity : VectorBaseActivity() { companion object { fun newIntent(context: Context, spaceId: String): Intent { return Intent(context, SpacePeopleActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, GenericIdArgs(spaceId)) + putExtra(Mavericks.KEY_ARG, GenericIdArgs(spaceId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index d1a666546e..359fa04093 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -24,7 +24,7 @@ import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -128,7 +128,7 @@ class UserCodeActivity : VectorBaseActivity(), companion object { fun newIntent(context: Context, userId: String): Intent { return Intent(context, UserCodeActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, Args(userId)) + putExtra(Mavericks.KEY_ARG, Args(userId)) } } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt index 82d426999b..23f1cfe119 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetActivity.kt @@ -20,7 +20,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import androidx.core.view.isVisible -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar import im.vector.app.R @@ -50,7 +50,7 @@ class WidgetActivity : VectorBaseActivity(), fun newIntent(context: Context, args: WidgetArgs): Intent { return Intent(context, WidgetActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, args) + putExtra(Mavericks.KEY_ARG, args) } } @@ -83,7 +83,7 @@ class WidgetActivity : VectorBaseActivity(), } override fun initUiAndData() { - val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(MvRx.KEY_ARG) + val widgetArgs: WidgetArgs? = intent?.extras?.getParcelable(Mavericks.KEY_ARG) if (widgetArgs == null) { finish() return diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt index 4036195b65..38c3b914d0 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionBottomSheet.kt @@ -23,7 +23,7 @@ import android.text.style.BulletSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -116,7 +116,7 @@ class RoomWidgetPermissionBottomSheet : companion object { fun newInstance(widgetArgs: WidgetArgs) = RoomWidgetPermissionBottomSheet().withArgs { - putParcelable(MvRx.KEY_ARG, widgetArgs) + putParcelable(Mavericks.KEY_ARG, widgetArgs) } } } From 724bd7dd1a6bb83ac3fd072c23aa886ad827a3e1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 Oct 2021 10:12:03 +0200 Subject: [PATCH 012/172] Update docs/design.md --- docs/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index 959ca40864..2e27f00ebf 100644 --- a/docs/design.md +++ b/docs/design.md @@ -34,7 +34,7 @@ Some of them depend on the theme, so ensure to use theme attributes and not colo - ensure that the correct layer is selected. Sometimes the parent layer has to be selected on the left panel - on the right panel, click on "export" - select SVG - - you cqn check the preview of what will be exported + - you can check the preview of what will be exported - click on "export" and save the file locally - unzip the file if necessary From f72a34ed0877b5b2de3db8db43f713456709d422 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 4 Oct 2021 14:09:21 +0200 Subject: [PATCH 013/172] Mavericks 2: continue replacing Rx --- .../app/features/home/HomeDetailViewModel.kt | 4 +- .../UnknownDeviceDetectorSharedViewModel.kt | 70 ++++++++-------- .../home/room/detail/RoomDetailViewModel.kt | 30 +++---- .../RoomMemberProfileViewModel.kt | 21 ++--- .../devices/DeviceListBottomSheetViewModel.kt | 5 +- .../roomprofile/alias/RoomAliasViewModel.kt | 2 +- .../banned/RoomBannedMemberListViewModel.kt | 7 +- .../members/RoomMemberListViewModel.kt | 52 ++++++------ .../RoomNotificationSettingsViewModel.kt | 4 +- .../uploads/RoomUploadsViewModel.kt | 9 +-- .../CrossSigningSettingsViewModel.kt | 50 ++++++------ ...iceVerificationInfoBottomSheetViewModel.kt | 10 ++- .../settings/devices/DevicesViewModel.kt | 54 +++++++------ .../threepids/ThreePidsSettingsViewModel.kt | 25 +++--- .../features/share/IncomingShareViewModel.kt | 3 +- .../app/features/spaces/SpaceMenuViewModel.kt | 5 +- .../leave/SpaceLeaveAdvancedViewModel.kt | 26 +++--- .../app/features/widgets/WidgetViewModel.kt | 16 ++-- .../signout/ServerBackupStatusViewModel.kt | 79 ++++++++----------- .../workers/signout/SignoutCheckViewModel.kt | 7 +- 20 files changed, 236 insertions(+), 243 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 01eb6e3c31..0f50b82aa8 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -47,6 +46,7 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -95,7 +95,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho updateShowDialPadTab() observeDataStore() callManager.addProtocolsCheckerListener(this) - session.rx().liveUser(session.myUserId).execute { + session.flow().liveUser(session.myUserId).execute { copy( myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() ) diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index db3317a214..143f843954 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -25,25 +25,25 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.settings.VectorPreferences -import io.reactivex.Observable +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.rx.rx import timber.log.Timber -import java.util.concurrent.TimeUnit data class UnknownDevicesState( val myMatrixItem: MatrixItem.UserItem? = null, @@ -98,31 +98,31 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted } ) - Observable.combineLatest, List, Optional, List>( - session.rx().liveUserCryptoDevices(session.myUserId), - session.rx().liveMyDevicesInfo(), - session.rx().liveCrossSigningPrivateKeys(), - { cryptoList, infoList, pInfo -> - // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") -// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") - infoList - .filter { info -> - // filter verified session, by checking the crypto device info - cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() - } - // filter out ignored devices - .filter { !ignoredDeviceList.contains(it.deviceId) } - .sortedByDescending { it.lastSeenTs } - .map { deviceInfo -> - val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 - DeviceDetectionInfo( - deviceInfo, - deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, - pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change - ) - } - } + combine( + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningPrivateKeys() ) + { cryptoList, infoList, pInfo -> + // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") +// Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") + infoList + .filter { info -> + // filter verified session, by checking the crypto device info + cryptoList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not().orFalse() + } + // filter out ignored devices + .filter { !ignoredDeviceList.contains(it.deviceId) } + .sortedByDescending { it.lastSeenTs } + .map { deviceInfo -> + val deviceKnownSince = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }?.firstTimeSeenLocalTs ?: 0 + DeviceDetectionInfo( + deviceInfo, + deviceKnownSince > currentSessionTs + 60_000, // short window to avoid false positive, + pInfo.getOrNull()?.selfSigned != null // adding this to pass distinct when cross sign change + ) + } + } .distinctUntilChanged() .execute { async -> // Timber.v("## Detector trigger passed distinct") @@ -132,14 +132,14 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .distinctUntilChanged() - .throttleLast(5_000, TimeUnit.MILLISECONDS) - .subscribe { + .sample(5_000) + .onEach { // If we have a new crypto device change, we might want to trigger refresh of device info session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) // trigger a refresh of lastSeen / last Ip session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1f6fec0410..ddb1c51b5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -28,7 +28,6 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay -import com.jakewharton.rxrelay2.PublishRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -60,11 +59,11 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper -import io.reactivex.Observable import io.reactivex.rxkotlin.subscribeBy -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn @@ -112,8 +111,6 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -143,7 +140,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val eventId = initialState.eventId private val invisibleEventsObservable = BehaviorRelay.create() private val visibleEventsObservable = BehaviorRelay.create() - private var timelineEvents = PublishRelay.create>() + private var timelineEvents = MutableSharedFlow>(0) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) // Same lifecycle than the ViewModel (survive to screen rotation) @@ -1533,14 +1530,12 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun getUnreadState() { - Observable - .combineLatest, RoomSummary, UnreadState>( - timelineEvents.observeOn(Schedulers.computation()), - room.rx().liveRoomSummary().unwrap(), - { timelineEvents, roomSummary -> - computeUnreadState(timelineEvents, roomSummary) - } - ) + combine( + timelineEvents, + room.flow().liveRoomSummary().unwrap() + ) { timelineEvents, roomSummary -> + computeUnreadState(timelineEvents, roomSummary) + } // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread .distinctUntilChanged { previous, current -> when { @@ -1549,10 +1544,9 @@ class RoomDetailViewModel @AssistedInject constructor( else -> false } } - .subscribe { - setState { copy(unreadState = it) } + .setOnEach { + copy(unreadState = it) } - .disposeOnClear() } private fun computeUnreadState(events: List, roomSummary: RoomSummary): UnreadState { @@ -1619,7 +1613,7 @@ class RoomDetailViewModel @AssistedInject constructor( } override fun onTimelineUpdated(snapshot: List) { - timelineEvents.accept(snapshot) + timelineEvents.tryEmit(snapshot) // PreviewUrl if (vectorPreferences.showUrlPreviews()) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index ab5337d7cd..14624b800e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -25,20 +25,17 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.powerlevel.PowerLevelsFlowFactory -import io.reactivex.Observable -import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.combineLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -50,8 +47,6 @@ import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role @@ -60,8 +55,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, private val stringProvider: StringProvider, @@ -114,7 +107,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - session.rx().liveUserCryptoDevices(initialState.userId) + session.flow().liveUserCryptoDevices(initialState.userId) .map { Pair( it.fold(true, { prev, dev -> prev && dev.isVerified }), @@ -128,14 +121,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v ) } - session.rx().liveCrossSigningInfo(initialState.userId) + session.flow().liveCrossSigningInfo(initialState.userId) .execute { copy(userMXCrossSigningInfo = it.invoke()?.getOrNull()) } } private fun observeIgnoredState() { - session.rx().liveIgnoredUsers() + session.flow().liveIgnoredUsers() .map { ignored -> ignored.find { it.userId == initialState.userId @@ -252,7 +245,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } - room.rx().liveRoomMembers(queryParams) + room.flow().liveRoomMembers(queryParams) .map { it.firstOrNull().toOptional() } .unwrap() .execute { @@ -312,7 +305,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v roomSummaryLive.execute { copy(isRoomEncrypted = it.invoke()?.isEncrypted == true) } - roomSummaryLive.combine(powerLevelsContentLive){roomSummary, powerLevelsContent -> + roomSummaryLive.combine(powerLevelsContentLive) { roomSummary, powerLevelsContent -> val roomName = roomSummary.toMatrixItem().getBestName() val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) when (val userPowerLevel = powerLevelsHelper.getUserRole(initialState.userId)) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 2baf27e694..b638d84181 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.rx.rx @@ -55,14 +56,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } init { - session.rx().liveUserCryptoDevices(args.userId) + session.flow().liveUserCryptoDevices(args.userId) .execute { copy(cryptoDevices = it).also { refreshSelectedId() } } - session.rx().liveCrossSigningInfo(args.userId) + session.flow().liveCrossSigningInfo(args.userId) .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index cb26d6407b..28a1804fab 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -131,7 +131,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index b92d1a4bd3..813d50c6bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -38,6 +38,8 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -54,15 +56,14 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia private val room = session.getRoom(initialState.roomId)!! init { - val rxRoom = room.rx() - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) } - rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + room.flow().liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) .execute { copy( bannedMemberSummaries = it diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index cd6d6f1610..2873b20400 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.members +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -27,10 +28,16 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory -import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse @@ -44,10 +51,9 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role -import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.mapOptional -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -87,28 +93,28 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState memberships = Membership.activeMemberships() } - Observable - .combineLatest, PowerLevelsContent, RoomMemberSummaries>( - room.rx().liveRoomMembers(roomMemberQueryParams), - room.rx() - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .mapOptional { it.content.toModel() } - .unwrap(), - { roomMembers, powerLevelsContent -> - buildRoomMemberSummaries(powerLevelsContent, roomMembers) - } - ) + combine( + room.flow().liveRoomMembers(roomMemberQueryParams), + room.flow() + .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + ) + { roomMembers, powerLevelsContent -> + buildRoomMemberSummaries(powerLevelsContent, roomMembers) + } + .execute { async -> copy(roomMemberSummaries = async) } if (room.isEncrypted()) { - room.rx().liveRoomMembers(roomMemberQueryParams) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { membersSummary -> + room.flow().liveRoomMembers(roomMemberQueryParams) + .flowOn(Dispatchers.Main) + .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) - .asObservable() - .doOnError { Timber.e(it) } + .asFlow() + .catch { Timber.e(it) } .map { deviceList -> // If any key change, emit the userIds list deviceList.groupBy { it.userId }.mapValues { @@ -147,7 +153,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) @@ -155,7 +161,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeThirdPartyInvites() { - room.rx().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) + room.flow().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) .execute { async -> copy(threePidInvites = async) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index 51cd4a4ecc..d944b77f7d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -28,6 +28,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -64,7 +66,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 2ae27f7799..4526024143 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.uploads -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -25,15 +24,15 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -66,7 +65,7 @@ class RoomUploadsViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.rx().liveRoomSummary() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 6f1058ec7c..a8fafb096a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.settings.crosssigning -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -28,8 +27,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.login.ReAuthHelper -import io.reactivex.Observable import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -38,14 +37,11 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage 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.Optional +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.util.awaitCallback -import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -59,26 +55,26 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( ) : VectorViewModel(initialState) { init { - Observable.combineLatest, Optional, Pair, Optional>>( - session.rx().liveMyDevicesInfo(), - session.rx().liveCrossSigningInfo(session.myUserId), - { myDevicesInfo, mxCrossSigningInfo -> - myDevicesInfo to mxCrossSigningInfo - } + combine( + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningInfo(session.myUserId) ) - .execute { data -> - val crossSigningKeys = data.invoke()?.second?.getOrNull() - val xSigningIsEnableInAccount = crossSigningKeys != null - val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() - val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() + { myDevicesInfo, mxCrossSigningInfo -> + myDevicesInfo to mxCrossSigningInfo + } + .execute { data -> + val crossSigningKeys = data.invoke()?.second?.getOrNull() + val xSigningIsEnableInAccount = crossSigningKeys != null + val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() + val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() - copy( - crossSigningInfo = crossSigningKeys, - xSigningIsEnableInAccount = xSigningIsEnableInAccount, - xSigningKeysAreTrusted = xSigningKeysAreTrusted, - xSigningKeyCanSign = xSigningKeyCanSign - ) - } + copy( + crossSigningInfo = crossSigningKeys, + xSigningIsEnableInAccount = xSigningIsEnableInAccount, + xSigningKeysAreTrusted = xSigningKeysAreTrusted, + xSigningKeyCanSign = xSigningKeyCanSign + ) + } } var uiaContinuation: Continuation? = null @@ -126,7 +122,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( } Unit } - is CrossSigningSettingsAction.SsoAuthDone -> { + is CrossSigningSettingsAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) @@ -134,7 +130,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( uiaContinuation?.resumeWithException(IllegalArgumentException()) } } - is CrossSigningSettingsAction.PasswordAuthDone -> { + is CrossSigningSettingsAction.PasswordAuthDone -> { val decryptedPass = session.loadSecureSecret(action.password.fromBase64().inputStream(), ReAuthActivity.DEFAULT_RESULT_KEYSTORE_ALIAS) uiaContinuation?.resume( UserPasswordAuth( @@ -144,7 +140,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( ) ) } - CrossSigningSettingsAction.ReAuthCancelled -> { + CrossSigningSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") _viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView) uiaContinuation?.resumeWithException(Exception()) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 975236beb1..38342efc46 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -25,7 +25,9 @@ import dagger.assisted.AssistedFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.rx.rx @@ -48,7 +50,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() ) } - session.rx().liveCrossSigningInfo(session.myUserId) + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -56,7 +58,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { list -> list.firstOrNull { it.deviceId == deviceId } } @@ -67,7 +69,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .execute { copy( @@ -79,7 +81,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy(deviceInfo = Loading()) } - session.rx().liveMyDevicesInfo() + session.flow().liveMyDevicesInfo() .map { devices -> devices.firstOrNull { it.deviceId == deviceId } ?: DeviceInfo(deviceId = deviceId) } 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 04e743b8df..b2b4c0c396 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 @@ -16,7 +16,6 @@ package im.vector.app.features.settings.devices -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -34,31 +33,36 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.login.ReAuthHelper -import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.data.LoginFlowTypes +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse +import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod 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 org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.internal.util.awaitCallback -import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection @@ -118,18 +122,18 @@ class DevicesViewModel @AssistedInject constructor( ) } - Observable.combineLatest, List, List>( - session.rx().liveUserCryptoDevices(session.myUserId), - session.rx().liveMyDevicesInfo(), - { cryptoList, infoList -> - infoList - .sortedByDescending { it.lastSeenTs } - .map { deviceInfo -> - val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId } - DeviceFullInfo(deviceInfo, cryptoDeviceInfo) - } - } + combine( + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo() ) + { cryptoList, infoList -> + infoList + .sortedByDescending { it.lastSeenTs } + .map { deviceInfo -> + val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId } + DeviceFullInfo(deviceInfo, cryptoDeviceInfo) + } + } .distinctUntilChanged() .execute { async -> copy( @@ -137,7 +141,7 @@ class DevicesViewModel @AssistedInject constructor( ) } - session.rx().liveCrossSigningInfo(session.myUserId) + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -146,24 +150,24 @@ class DevicesViewModel @AssistedInject constructor( } session.cryptoService().verificationService().addListener(this) -// session.rx().liveMyDeviceInfo() +// session.flow().liveMyDeviceInfo() // .execute { // copy( // devices = it // ) // } - session.rx().liveUserCryptoDevices(session.myUserId) + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .distinctUntilChanged() - .throttleLast(5_000, TimeUnit.MILLISECONDS) - .subscribe { + .sample(5_000) + .onEach { // If we have a new crypto device change, we might want to trigger refresh of device info session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) -// session.rx().liveUserCryptoDevices(session.myUserId) +// session.flow().liveUserCryptoDevices(session.myUserId) // .execute { // copy( // cryptoDevices = it diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 4d94389850..cd0d74a288 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.threepids -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -33,15 +32,15 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ReadOnceTrue import im.vector.app.features.auth.ReAuthActivity import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserPasswordAuth -import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -102,7 +101,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.rx() + session.flow() .liveThreePIds(true) .execute { copy( @@ -112,7 +111,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observePendingThreePids() { - session.rx() + session.flow() .livePendingThreePIds() .execute { copy( @@ -131,13 +130,13 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( override fun handle(action: ThreePidsSettingsAction) { when (action) { - is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) + is ThreePidsSettingsAction.AddThreePid -> handleAddThreePid(action) is ThreePidsSettingsAction.ContinueThreePid -> handleContinueThreePid(action) - is ThreePidsSettingsAction.SubmitCode -> handleSubmitCode(action) - is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) - is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) - is ThreePidsSettingsAction.ChangeUiState -> handleChangeUiState(action) - ThreePidsSettingsAction.SsoAuthDone -> { + is ThreePidsSettingsAction.SubmitCode -> handleSubmitCode(action) + is ThreePidsSettingsAction.CancelThreePid -> handleCancelThreePid(action) + is ThreePidsSettingsAction.DeleteThreePid -> handleDeleteThreePid(action) + is ThreePidsSettingsAction.ChangeUiState -> handleChangeUiState(action) + ThreePidsSettingsAction.SsoAuthDone -> { Timber.d("## UIA - FallBack success") if (pendingAuth != null) { uiaContinuation?.resume(pendingAuth!!) @@ -155,7 +154,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( ) ) } - ThreePidsSettingsAction.ReAuthCancelled -> { + ThreePidsSettingsAction.ReAuthCancelled -> { Timber.d("## UIA - Reauth cancelled") uiaContinuation?.resumeWithException(Exception()) uiaContinuation = null diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 665d3b28fa..5fdd10e742 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx import java.util.concurrent.TimeUnit @@ -68,7 +69,7 @@ class IncomingShareViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) } session - .rx().liveRoomSummaries(queryParams) + .flow().liveRoomSummaries(queryParams) .execute { copy(roomSummaries = it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index ad764363db..bb30670da9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -77,7 +78,7 @@ class SpaceMenuViewModel @AssistedInject constructor( session.getRoom(initialState.spaceId)?.let { room -> - room.rx().liveRoomSummary().subscribe { + room.flow().liveRoomSummary().onEach { it.getOrNull()?.let { if (it.membership == Membership.LEAVE) { setState { copy(leavingState = Success(Unit)) } @@ -87,7 +88,7 @@ class SpaceMenuViewModel @AssistedInject constructor( } } } - }.disposeOnClear() + }.launchIn(viewModelScope) PowerLevelsFlowFactory(room) .createFlow() diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 0daa2522e9..3d24cf6225 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.leave -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -31,6 +30,8 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import okhttp3.internal.toImmutableList import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -38,7 +39,8 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class SpaceLeaveAdvancedViewModel @AssistedInject constructor( @@ -95,17 +97,17 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( val spaceSummary = session.getRoomSummary(initialState.spaceId) setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId)?.let { room -> - room.rx().liveRoomSummary().subscribe { - it.getOrNull()?.let { - if (it.membership == Membership.LEAVE) { - setState { copy(leaveState = Success(Unit)) } - if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { - // switch to home? - appStateHandler.setCurrentSpace(null, session) + room.flow().liveRoomSummary() + .unwrap() + .onEach { + if (it.membership == Membership.LEAVE) { + setState { copy(leaveState = Success(Unit)) } + if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { + // switch to home? + appStateHandler.setCurrentSpace(null, session) + } } - } - } - } + }.launchIn(viewModelScope) } viewModelScope.launch { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 44d246885a..f88bf6ef56 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.widgets import android.net.Uri -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -26,11 +25,12 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -41,9 +41,10 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure -import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import javax.net.ssl.HttpsURLConnection @@ -118,16 +119,15 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi if (room == null) { return } - room.rx().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .map { PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, true, null) } - .subscribe { - setState { copy(canManageWidgets = it) } + .setOnEach { + copy(canManageWidgets = it) } - .disposeOnClear() } private fun observeWidgetIfNeeded() { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index 0076bf580e..c3719ffd8e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -25,27 +25,23 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import io.reactivex.Observable -import io.reactivex.functions.Function4 -import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit +import org.matrix.android.sdk.flow.flow data class ServerBackupStatusViewState( val bannerState: Async = Uninitialized @@ -91,43 +87,38 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS val keysExportedToFile = MutableLiveData() val keysBackupState = MutableLiveData() - private val keyBackupPublishSubject: PublishSubject = PublishSubject.create() + private val keyBackupFlow = MutableSharedFlow(0) init { session.cryptoService().keysBackupService().addListener(this) - keysBackupState.value = session.cryptoService().keysBackupService().state - - Observable.combineLatest, Optional, KeysBackupState, Optional, BannerState>( - session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)), - session.rx().liveCrossSigningInfo(session.myUserId), - keyBackupPublishSubject, - session.rx().liveCrossSigningPrivateKeys(), - Function4 { _, crossSigningInfo, keyBackupState, pInfo -> - // first check if 4S is already setup - if (session.sharedSecretStorageService.isRecoverySetup()) { - // 4S is already setup sp we should not display anything - return@Function4 when (keyBackupState) { - KeysBackupState.BackingUp -> BannerState.BackingUp - else -> BannerState.Hidden - } - } - - // So recovery is not setup - // Check if cross signing is enabled and local secrets known - if ( - crossSigningInfo.getOrNull() == null - || (crossSigningInfo.getOrNull()?.isTrusted() == true - && pInfo.getOrNull()?.allKnown().orFalse()) - ) { - // So 4S is not setup and we have local secrets, - return@Function4 BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) - } - - BannerState.Hidden + val liveUserAccountData = session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + val liveCrossSigningInfo = session.flow().liveCrossSigningInfo(session.myUserId) + val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() + combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> + // first check if 4S is already setup + if (session.sharedSecretStorageService.isRecoverySetup()) { + // 4S is already setup sp we should not display anything + return@combine when (keyBackupState) { + KeysBackupState.BackingUp -> BannerState.BackingUp + else -> BannerState.Hidden } - ) - .throttleLast(1000, TimeUnit.MILLISECONDS) // we don't want to flicker or catch transient states + } + + // So recovery is not setup + // Check if cross signing is enabled and local secrets known + if ( + crossSigningInfo.getOrNull() == null + || (crossSigningInfo.getOrNull()?.isTrusted() == true + && pInfo.getOrNull()?.allKnown().orFalse()) + ) { + // So 4S is not setup and we have local secrets, + return@combine BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup()) + } + BannerState.Hidden + + } + .sample(1000) // we don't want to flicker or catch transient states .distinctUntilChanged() .execute { async -> copy( @@ -135,7 +126,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS ) } - keyBackupPublishSubject.onNext(session.cryptoService().keysBackupService().state) + keyBackupFlow.tryEmit(session.cryptoService().keysBackupService().state) } /** @@ -165,7 +156,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS } override fun onStateChange(newState: KeysBackupState) { - keyBackupPublishSubject.onNext(session.cryptoService().keysBackupService().state) + keyBackupFlow.tryEmit(session.cryptoService().keysBackupService().state) keysBackupState.value = newState } diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 8190a37056..057d9e31f8 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.workers.signout import android.net.Uri -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -35,6 +34,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.crypto.keys.KeysExporter +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -42,7 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber data class SignoutCheckViewState( @@ -97,7 +98,7 @@ class SignoutCheckViewModel @AssistedInject constructor( ) } - session.rx().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) .map { session.sharedSecretStorageService.isRecoverySetup() } From fadbb60f90901ded9fe6e4e950a172be0208090b Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 4 Oct 2021 17:50:45 +0200 Subject: [PATCH 014/172] Mavericks 2: continue replacing Rx --- .../discovery/DiscoverySettingsViewModel.kt | 13 ++- .../vector/app/features/home/HomeActivity.kt | 39 ++++--- .../features/home/HomeActivityViewModel.kt | 16 ++- .../app/features/home/HomeDetailViewModel.kt | 24 ++-- .../room/breadcrumbs/BreadcrumbsViewModel.kt | 4 +- .../home/room/detail/RoomDetailFragment.kt | 105 +++++++++--------- .../app/features/invite/InvitesAcceptor.kt | 55 ++++----- .../features/permalink/PermalinkHandler.kt | 81 ++++++-------- .../room/RequireActiveMembershipViewModel.kt | 41 ++++--- .../roomdirectory/PublicRoomsFragment.kt | 30 ++--- .../roomdirectory/RoomDirectoryViewModel.kt | 30 +++-- .../roompreview/RoomPreviewViewModel.kt | 15 ++- .../roomprofile/RoomProfileViewModel.kt | 28 ++--- .../settings/RoomSettingsViewModel.kt | 66 +++++------ .../settings/SecretsSynchronisationInfo.kt | 71 ++++++++++++ .../VectorSettingsSecurityPrivacyFragment.kt | 14 ++- .../settings/ignored/IgnoredUsersViewModel.kt | 7 +- .../features/share/IncomingShareViewModel.kt | 19 ++-- .../features/spaces/SpacesListViewModel.kt | 30 ++--- .../spaces/explore/SpaceDirectoryFragment.kt | 52 ++++----- .../spaces/explore/SpaceDirectoryViewModel.kt | 12 +- .../userdirectory/UserListViewModel.kt | 7 +- .../app/features/widgets/WidgetViewModel.kt | 4 +- .../RoomWidgetPermissionViewModel.kt | 9 +- 24 files changed, 417 insertions(+), 355 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 412a1ab5b4..b248bcd065 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.discovery -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -25,17 +24,19 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow class DiscoverySettingsViewModel @AssistedInject constructor( @Assisted initialState: DiscoverySettingsState, @@ -84,12 +85,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.rx() + session.flow() .liveThreePIds(true) - .subscribe { + .onEach { retrieveBinding(it) } - .disposeOnClear() + .launchIn(viewModelScope) } override fun onCleared() { 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 942ada9982..7e02c043dc 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 @@ -27,6 +27,7 @@ import android.view.MenuItem import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -72,6 +73,7 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import im.vector.app.push.fcm.FcmHelper import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.permalinks.PermalinkService @@ -288,26 +290,23 @@ class HomeActivity : } else -> deepLink } - permalinkHandler.launch( - context = this, - deepLink = resolvedLink, - navigationInterceptor = this, - buildTask = true - ) - // .delay(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { isHandled -> - if (!isHandled) { - val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) - || deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) - MaterialAlertDialogBuilder(this) - .setTitle(R.string.dialog_title_error) - .setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed) - .setPositiveButton(R.string.ok, null) - .show() - } - } - .disposeOnDestroy() + lifecycleScope.launch { + val isHandled = permalinkHandler.launch( + context = this@HomeActivity, + deepLink = resolvedLink, + navigationInterceptor = this@HomeActivity, + buildTask = true + ) + if (!isHandled) { + val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) + || deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) + MaterialAlertDialogBuilder(this@HomeActivity) + .setTitle(R.string.dialog_title_error) + .setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed) + .setPositiveButton(R.string.ok, null) + .show() + } + } } } 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 fb2401edc1..fa3df1ca97 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 @@ -16,6 +16,7 @@ package im.vector.app.features.home +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksViewModelFactory @@ -31,6 +32,8 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor @@ -44,6 +47,7 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.util.awaitCallback @@ -100,9 +104,9 @@ class HomeActivityViewModel @AssistedInject constructor( .crossSigningService().allPrivateKeysKnown() safeActiveSession - .rx() + .flow() .liveCrossSigningInfo(safeActiveSession.myUserId) - .subscribe { + .onEach { val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { // cross signing keys have been reset @@ -116,15 +120,15 @@ class HomeActivityViewModel @AssistedInject constructor( } onceTrusted = isVerified } - .disposeOnClear() + .launchIn(viewModelScope) } private fun observeInitialSync() { val session = activeSessionHolder.getSafeActiveSession() ?: return session.getSyncStatusLive() - .asObservable() - .subscribe { status -> + .asFlow() + .onEach { status -> when (status) { is SyncStatusService.Status.Progressing -> { // Schedule a check of the bootstrap when the init sync will be finished @@ -145,7 +149,7 @@ class HomeActivityViewModel @AssistedInject constructor( ) } } - .disposeOnClear() + .launchIn(viewModelScope) } /** diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 0f50b82aa8..316c1791fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -37,6 +38,7 @@ import im.vector.app.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -48,7 +50,6 @@ import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.util.concurrent.TimeUnit @@ -182,25 +183,18 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeSyncState() { - session.rx() + session.flow() .liveSyncState() - .subscribe { syncState -> - setState { - copy(syncState = syncState) - } + .setOnEach { syncState -> + copy(syncState = syncState) } - .disposeOnClear() session.getSyncStatusLive() - .asObservable() - .subscribe { - if (it is SyncStatusService.Status.IncrementalSyncStatus) { - setState { - copy(incrementalSyncStatus = it) - } - } + .asFlow() + .filterIsInstance() + .setOnEach { + copy(incrementalSyncStatus = it) } - .disposeOnClear() } private fun observeRoomGroupingMethod() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index a945e4bbb1..f32f132fe9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, @@ -61,12 +62,11 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B // PRIVATE METHODS ***************************************************************************** private fun observeBreadcrumbs() { - session.rx() + session.flow() .liveBreadcrumbs(roomSummaryQueryParams { displayName = QueryStringValue.NoCondition memberships = listOf(Membership.JOIN) }) - .observeOn(Schedulers.computation()) .execute { asyncBreadcrumbs -> copy(asyncBreadcrumbs = asyncBreadcrumbs) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 5da00b7e2f..3420b52de1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1590,57 +1590,54 @@ class RoomDetailFragment @Inject constructor( } } -// TimelineEventController.Callback ************************************************************ + // TimelineEventController.Callback ************************************************************ override fun onUrlClicked(url: String, title: String): Boolean { - permalinkHandler - .launch(requireActivity(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { - // Same room? - if (roomId == roomDetailArgs.roomId) { - // Navigation to same room - if (eventId == null) { - showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room)) - } else { - // Highlight and scroll to this event - roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true)) + viewLifecycleOwner.lifecycleScope.launch { + val isManaged = permalinkHandler + .launch(requireActivity(), url, object : NavigationInterceptor { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + // Same room? + if (roomId == roomDetailArgs.roomId) { + // Navigation to same room + if (eventId == null) { + showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room)) + } else { + // Highlight and scroll to this event + roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true)) + } + return true } + // Not handled + return false + } + + override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { + openRoomMemberProfile(userId) return true } - // Not handled - return false - } - - override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { - openRoomMemberProfile(userId) - return true - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { managed -> - if (!managed) { - if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { - MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive) - .setTitle(R.string.external_link_confirmation_title) - .setMessage( - getString(R.string.external_link_confirmation_message, title, url) - .toSpannable() - .colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) - .colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) - ) - .setPositiveButton(R.string._continue) { _, _ -> - openUrlInExternalBrowser(requireContext(), url) - } - .setNegativeButton(R.string.cancel, null) - .show() - } else { - // Open in external browser, in a new Tab - openUrlInExternalBrowser(requireContext(), url) - } - } + }) + if (!isManaged) { + if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { + MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_NegativeDestructive) + .setTitle(R.string.external_link_confirmation_title) + .setMessage( + getString(R.string.external_link_confirmation_message, title, url) + .toSpannable() + .colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) + .colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) + ) + .setPositiveButton(R.string._continue) { _, _ -> + openUrlInExternalBrowser(requireContext(), url) + } + .setNegativeButton(R.string.cancel, null) + .show() + } else { + // Open in external browser, in a new Tab + openUrlInExternalBrowser(requireContext(), url) } - .disposeOnDestroyView() + } + } // In fact it is always managed return true } @@ -1799,15 +1796,15 @@ class RoomDetailFragment @Inject constructor( } override fun onRoomCreateLinkClicked(url: String) { - permalinkHandler - .launch(requireContext(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { - requireActivity().finish() - return false - } - }) - .subscribe() - .disposeOnDestroyView() + viewLifecycleOwner.lifecycleScope.launchWhenResumed { + permalinkHandler + .launch(requireContext(), url, object : NavigationInterceptor { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + requireActivity().finish() + return false + } + }) + } } override fun onReadReceiptsClicked(readReceipts: List) { diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index 6e7de1c35b..09eff756d5 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -18,10 +18,14 @@ package im.vector.app.features.invite import im.vector.app.ActiveSessionDataSource import im.vector.app.features.session.coroutineScope -import io.reactivex.Observable import io.reactivex.disposables.Disposable import kotlinx.coroutines.async -import kotlinx.coroutines.launch +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import org.matrix.android.sdk.api.extensions.orFalse @@ -31,9 +35,8 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -50,7 +53,7 @@ class InvitesAcceptor @Inject constructor( private lateinit var activeSessionDisposable: Disposable private val shouldRejectRoomIds = mutableSetOf() - private val invitedRoomDisposables = HashMap() + private val activeSessionIds = mutableSetOf() private val semaphore = Semaphore(1) fun initialize() { @@ -71,34 +74,32 @@ class InvitesAcceptor @Inject constructor( if (!autoAcceptInvites.isEnabled) { return } - if (invitedRoomDisposables.containsKey(session.sessionId)) { + if (activeSessionIds.contains(session.sessionId)) { return } + activeSessionIds.add(session.sessionId) session.addListener(this) val roomQueryParams = roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - val rxSession = session.rx() - Observable - .combineLatest( - rxSession.liveRoomSummaries(roomQueryParams), - rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), - { invitedRooms, _ -> invitedRooms.map { it.roomId } } - ) + val flowSession = session.flow() + combine( + flowSession.liveRoomSummaries(roomQueryParams), + flowSession.liveRoomChangeMembershipState().debounce(1000) + ) { invitedRooms, _ -> invitedRooms.map { it.roomId } } .filter { it.isNotEmpty() } - .subscribe { invitedRoomIds -> - session.coroutineScope.launch { - semaphore.withPermit { - Timber.v("Invited roomIds: $invitedRoomIds") - for (roomId in invitedRoomIds) { - async { session.joinRoomSafely(roomId) }.start() - } - } - } - } - .also { - invitedRoomDisposables[session.sessionId] = it - } + .onEach { invitedRoomIds -> + joinInvitedRooms(session, invitedRoomIds) + }.launchIn(session.coroutineScope) + } + + private suspend fun joinInvitedRooms(session: Session, invitedRoomIds: List) = coroutineScope { + semaphore.withPermit { + Timber.v("Invited roomIds: $invitedRoomIds") + for (roomId in invitedRoomIds) { + async { session.joinRoomSafely(roomId) }.start() + } + } } private suspend fun Session.joinRoomSafely(roomId: String) { @@ -138,6 +139,6 @@ class InvitesAcceptor @Inject constructor( override fun onSessionStopped(session: Session) { session.removeListener(this) - invitedRoomDisposables.remove(session.sessionId)?.dispose() + activeSessionIds.remove(session.sessionId) } } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index fd5fea0fe8..f41abeff08 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -23,10 +23,10 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.toast import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService @@ -34,80 +34,71 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.rx.rx import javax.inject.Inject class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val navigator: Navigator) { - fun launch( + suspend fun launch( context: Context, deepLink: String?, navigationInterceptor: NavigationInterceptor? = null, buildTask: Boolean = false - ): Single { + ): Boolean { val uri = deepLink?.let { Uri.parse(it) } return launch(context, uri, navigationInterceptor, buildTask) } - fun launch( + suspend fun launch( context: Context, deepLink: Uri?, navigationInterceptor: NavigationInterceptor? = null, buildTask: Boolean = false - ): Single { + ): Boolean { if (deepLink == null || !isPermalinkSupported(context, deepLink.toString())) { - return Single.just(false) + return false } - return Single - .fromCallable { - PermalinkParser.parse(deepLink) - } - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .flatMap { permalinkData -> - handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask) - } - .onErrorReturnItem(false) + return tryOrNull { + withContext(Dispatchers.Default) { + val permalinkData = PermalinkParser.parse(deepLink) + handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask) + } + } ?: false } - private fun handlePermalink( + private suspend fun handlePermalink( permalinkData: PermalinkData, rawLink: Uri, context: Context, navigationInterceptor: NavigationInterceptor?, buildTask: Boolean - ): Single { + ): Boolean { return when (permalinkData) { is PermalinkData.RoomLink -> { - permalinkData.getRoomId() - .observeOn(AndroidSchedulers.mainThread()) - .map { - val roomId = it.getOrNull() - if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) { - openRoom( - context = context, - roomId = roomId, - permalinkData = permalinkData, - rawLink = rawLink, - buildTask = buildTask - ) - } - true - } + val roomId = permalinkData.getRoomId() + if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) { + openRoom( + context = context, + roomId = roomId, + permalinkData = permalinkData, + rawLink = rawLink, + buildTask = buildTask + ) + } + true } is PermalinkData.GroupLink -> { navigator.openGroupDetail(permalinkData.groupId, context, buildTask) - Single.just(true) + true } is PermalinkData.UserLink -> { if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) { navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) } - Single.just(true) + true } is PermalinkData.FallbackLink -> { - Single.just(false) + false } is PermalinkData.RoomEmailInviteLink -> { val data = RoomPreviewData( @@ -118,7 +109,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti roomType = permalinkData.roomType ) navigator.openRoomPreview(context, data) - Single.just(true) + true } } } @@ -130,15 +121,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti } } - private fun PermalinkData.RoomLink.getRoomId(): Single> { + private suspend fun PermalinkData.RoomLink.getRoomId(): String? { val session = activeSessionHolder.getSafeActiveSession() return if (isRoomAlias && session != null) { - session.rx() - .getRoomIdByAlias(roomIdOrAlias, true) - .map { it.getOrNull()?.roomId.toOptional() } - .subscribeOn(Schedulers.io()) + val roomIdByAlias = session.getRoomIdByAlias(roomIdOrAlias, true) + roomIdByAlias.getOrNull()?.roomId } else { - Single.just(Optional.from(roomIdOrAlias)) + roomIdOrAlias } } diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 2b299b014e..62519336f5 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -20,16 +20,26 @@ import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asFlow +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 +import kotlinx.coroutines.flow.subscribe +import kotlinx.coroutines.flow.switchMap import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -37,8 +47,8 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap /** * This ViewModel observe a room summary and notify when the room is left @@ -66,28 +76,31 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( } } - private val roomIdObservable = BehaviorRelay.createDefault(Optional.from(initialState.roomId)) + private val roomIdFlow = MutableStateFlow(Optional.from(initialState.roomId)) init { observeRoomSummary() } private fun observeRoomSummary() { - roomIdObservable + roomIdFlow .unwrap() - .switchMap { roomId -> - val room = session.getRoom(roomId) ?: return@switchMap Observable.just(Optional.empty()) - room.rx() + .flatMapLatest { roomId -> + val room = session.getRoom(roomId) ?: return@flatMapLatest flow{ + val emptyResult = Optional.empty() + emit(emptyResult) + } + room.flow() .liveRoomSummary() .unwrap() - .observeOn(Schedulers.computation()) + .flowOn(Dispatchers.Default) .map { mapToLeftViewEvent(room, it) } } .unwrap() - .subscribe { event -> + .onEach { event -> _viewEvents.post(event) } - .disposeOnClear() + .launchIn(viewModelScope) } private fun mapToLeftViewEvent(room: Room, roomSummary: RoomSummary): Optional { @@ -128,7 +141,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( setState { copy(roomId = action.roomId) } - roomIdObservable.accept(Optional.from(action.roomId)) + roomIdFlow.tryEmit(Optional.from(action.roomId)) } }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index 8214b26fea..d34dacf045 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.appcompat.queryTextChanges @@ -37,6 +38,7 @@ import im.vector.app.databinding.FragmentPublicRoomsBinding import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler import io.reactivex.rxkotlin.subscribeBy +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom @@ -125,20 +127,20 @@ class PublicRoomsFragment @Inject constructor( } override fun onUnknownRoomClicked(roomIdOrAlias: String) { - val permalink = session.permalinkService().createPermalink(roomIdOrAlias) - permalinkHandler - .launch(requireContext(), permalink, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { - requireActivity().finish() - return false - } - }) - .subscribe { isSuccessful -> - if (!isSuccessful) { - requireContext().toast(R.string.room_error_not_found) - } - } - .disposeOnDestroyView() + viewLifecycleOwner.lifecycleScope.launch { + val permalink = session.permalinkService().createPermalink(roomIdOrAlias) + val isHandled = permalinkHandler + .launch(requireContext(), permalink, object : NavigationInterceptor { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + requireActivity().finish() + return false + } + }) + + if (!isHandled) { + requireContext().toast(R.string.room_error_not_found) + } + } } override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 8955167e50..a2089e6cd5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomdirectory -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -31,6 +30,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session @@ -38,7 +38,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber class RoomDirectoryViewModel @AssistedInject constructor( @@ -80,28 +80,24 @@ class RoomDirectoryViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) } session - .rx() + .flow() .liveRoomSummaries(queryParams) - .subscribe { list -> - val joinedRoomIds = list - ?.map { it.roomId } - ?.toSet() - .orEmpty() - - setState { - copy(joinedRoomsIds = joinedRoomIds) - } + .map { roomSummaries -> + roomSummaries + .map { it.roomId } + .toSet() + } + .setOnEach { + copy(joinedRoomsIds = it) } - .disposeOnClear() } private fun observeMembershipChanges() { - session.rx() + session.flow() .liveRoomChangeMembershipState() - .subscribe { - setState { copy(changeMembershipStates = it) } + .setOnEach { + copy(changeMembershipStates = it) } - .disposeOnClear() } override fun handle(action: RoomDirectoryAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 6e70ff2593..2635307e95 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -30,6 +30,8 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.roomdirectory.JoinState import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue @@ -40,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -165,9 +168,9 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini excludeType = null } session - .rx() + .flow() .liveRoomSummaries(queryParams) - .subscribe { list -> + .onEach { list -> val isRoomJoined = list.any { it.membership == Membership.JOIN } @@ -180,13 +183,13 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini setState { copy(roomJoinState = JoinState.JOINED) } } } - .disposeOnClear() + .launchIn(viewModelScope) } private fun observeMembershipChanges() { - session.rx() + session.flow() .liveRoomChangeMembershipState() - .subscribe { + .onEach { val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown val joinState = when (changeMembership) { is ChangeMembershipState.Joining -> JoinState.JOINING @@ -198,7 +201,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini setState { copy(roomJoinState = joinState) } } } - .disposeOnClear() + .launchIn(viewModelScope) } override fun handle(action: RoomPreviewAction) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index c8570d67dc..c4fc2bc7bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -40,10 +40,10 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.rx.RxRoom -import org.matrix.android.sdk.rx.mapOptional -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.FlowRoom +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap class RoomProfileViewModel @AssistedInject constructor( @Assisted private val initialState: RoomProfileViewState, @@ -69,15 +69,15 @@ class RoomProfileViewModel @AssistedInject constructor( private val room = session.getRoom(initialState.roomId)!! init { - val rxRoom = room.rx() - observeRoomSummary(rxRoom) - observeRoomCreateContent(rxRoom) - observeBannedRoomMembers(rxRoom) + val flowRoom = room.flow() + observeRoomSummary(flowRoom) + observeRoomCreateContent(flowRoom) + observeBannedRoomMembers(flowRoom) observePermissions() } - private fun observeRoomCreateContent(rxRoom: RxRoom) { - rxRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition) + private fun observeRoomCreateContent(flowRoom: FlowRoom) { + flowRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .execute { async -> @@ -92,16 +92,16 @@ class RoomProfileViewModel @AssistedInject constructor( } } - private fun observeRoomSummary(rxRoom: RxRoom) { - rxRoom.liveRoomSummary() + private fun observeRoomSummary(flowRoom: FlowRoom) { + flowRoom.liveRoomSummary() .unwrap() .execute { copy(roomSummary = it) } } - private fun observeBannedRoomMembers(rxRoom: RxRoom) { - rxRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + private fun observeBannedRoomMembers(flowRoom: FlowRoom) { + flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) .execute { copy(bannedMembership = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index ea939c153e..7b28ced130 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -28,11 +28,10 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences -import io.reactivex.Completable -import io.reactivex.Observable import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -47,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val vectorPreferences: VectorPreferences, @@ -259,61 +257,57 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun saveSettings() = withState { state -> - postLoading(true) - - val operationList = mutableListOf() + val operationList = mutableListOf Unit>() val summary = state.roomSummary.invoke() when (val avatarAction = state.avatarAction) { RoomSettingsViewState.AvatarAction.None -> Unit RoomSettingsViewState.AvatarAction.DeleteAvatar -> { - operationList.add(room.rx().deleteAvatar()) + operationList.add { room.deleteAvatar() } } is RoomSettingsViewState.AvatarAction.UpdateAvatar -> { - operationList.add(room.rx().updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName)) + operationList.add { room.updateAvatar(avatarAction.newAvatarUri, avatarAction.newAvatarFileName) } } } if (summary?.name != state.newName) { - operationList.add(room.rx().updateName(state.newName ?: "")) + operationList.add { room.updateName(state.newName ?: "") } } if (summary?.topic != state.newTopic) { - operationList.add(room.rx().updateTopic(state.newTopic ?: "")) + operationList.add { room.updateTopic(state.newTopic ?: "") } } if (state.newHistoryVisibility != null) { - operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility)) + operationList.add { room.updateHistoryReadability(state.newHistoryVisibility) } } if (state.newRoomJoinRules.hasChanged()) { - operationList.add(room.rx().updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess)) + operationList.add { room.updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess) } + } + viewModelScope.launch { + updateLoadingState(isLoading = true) + try { + for (operation in operationList) { + operation.invoke() + } + setState { + deletePendingAvatar(this) + copy( + avatarAction = RoomSettingsViewState.AvatarAction.None, + newHistoryVisibility = null, + newRoomJoinRules = RoomSettingsViewState.NewJoinRule() + ) + } + _viewEvents.post(RoomSettingsViewEvents.Success) + } catch (failure: Throwable) { + _viewEvents.post(RoomSettingsViewEvents.Failure(failure)) + }finally { + updateLoadingState(isLoading = false) + } } - - Observable - .fromIterable(operationList) - .concatMapCompletable { it } - .subscribe( - { - postLoading(false) - setState { - deletePendingAvatar(this) - copy( - avatarAction = RoomSettingsViewState.AvatarAction.None, - newHistoryVisibility = null, - newRoomJoinRules = RoomSettingsViewState.NewJoinRule() - ) - } - _viewEvents.post(RoomSettingsViewEvents.Success) - }, - { - postLoading(false) - _viewEvents.post(RoomSettingsViewEvents.Failure(it)) - } - ) - .disposeOnClear() } - private fun postLoading(isLoading: Boolean) { + private fun updateLoadingState(isLoading: Boolean) { setState { copy(isLoading = isLoading) } diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt new file mode 100644 index 0000000000..5afcb77587 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.rx.SecretsSynchronisationInfo + +data class SecretsSynchronisationInfo( + val isBackupSetup: Boolean, + val isCrossSigningEnabled: Boolean, + val isCrossSigningTrusted: Boolean, + val allPrivateKeysKnown: Boolean, + val megolmBackupAvailable: Boolean, + val megolmSecretKnown: Boolean, + val isMegolmKeyIn4S: Boolean +) + +fun Session.liveSecretSynchronisationInfo(): Flow { + val sessionFlow = flow() + return combine( + sessionFlow.liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), + sessionFlow.liveCrossSigningInfo(myUserId), + sessionFlow.liveCrossSigningPrivateKeys() + ) { _, crossSigningInfo, pInfo -> + // first check if 4S is already setup + val is4SSetup = sharedSecretStorageService.isRecoverySetup() + val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null + val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true + val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse() + + val keysBackupService = cryptoService().keysBackupService() + val currentBackupVersion = keysBackupService.currentBackupVersion + val megolmBackupAvailable = currentBackupVersion != null + val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo() + + val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion + SecretsSynchronisationInfo( + isBackupSetup = is4SSetup, + isCrossSigningEnabled = isCrossSigningEnabled, + isCrossSigningTrusted = isCrossSigningTrusted, + allPrivateKeysKnown = allPrivateKeysKnown, + megolmBackupAvailable = megolmBackupAvailable, + megolmSecretKnown = megolmKeyKnown, + isMegolmKeyIn4S = sharedSecretStorageService.isMegolmKeyInBackup() + ) + } + .distinctUntilChanged() +} 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 0075be6e25..103c4ab06d 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 @@ -60,6 +60,10 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.themes.ThemeUtils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixCallback @@ -144,14 +148,12 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( // My device name may have been updated refreshMyDevice() refreshXSigningStatus() - session.rx().liveSecretSynchronisationInfo() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + session.liveSecretSynchronisationInfo() + .flowOn(Dispatchers.Main) + .onEach { refresh4SSection(it) refreshXSigningStatus() - }.also { - disposables.add(it) - } + }.launchIn(viewLifecycleOwner.lifecycleScope) lifecycleScope.launchWhenResumed { findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index 1cf150395a..7b7b5d0570 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.ignored -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -27,14 +26,14 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow data class IgnoredUsersViewState( val ignoredUsers: List = emptyList(), @@ -68,7 +67,7 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeIgnoredUsers() { - session.rx() + session.flow() .liveIgnoredUsers() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 5fdd10e742..44e5ca39f9 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -19,24 +19,25 @@ package im.vector.app.features.share import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.attachments.isPreviewable import im.vector.app.features.attachments.toGroupedContentAttachmentData import im.vector.app.features.home.room.list.BreadcrumbsRoomComparator +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit class IncomingShareViewModel @AssistedInject constructor( @Assisted initialState: IncomingShareViewState, @@ -58,7 +59,7 @@ class IncomingShareViewModel @AssistedInject constructor( } } - private val filterStream: BehaviorRelay = BehaviorRelay.createDefault("") + private val filterStream = MutableStateFlow("") init { observeRoomSummaries() @@ -75,7 +76,7 @@ class IncomingShareViewModel @AssistedInject constructor( } filterStream - .switchMap { filter -> + .flatMapLatest { filter -> val displayNameQuery = if (filter.isEmpty()) { QueryStringValue.NoCondition } else { @@ -85,9 +86,9 @@ class IncomingShareViewModel @AssistedInject constructor( displayName = displayNameQuery memberships = listOf(Membership.JOIN) } - session.rx().liveRoomSummaries(filterQueryParams) + session.flow().liveRoomSummaries(filterQueryParams) } - .throttleLast(300, TimeUnit.MILLISECONDS) + .sample(300) .map { it.sortedWith(breadcrumbsRoomComparator) } .execute { copy(filteredRoomSummaries = it) @@ -110,7 +111,7 @@ class IncomingShareViewModel @AssistedInject constructor( } private fun handleFilter(action: IncomingShareAction.FilterWith) { - filterStream.accept(action.filter) + filterStream.tryEmit(action.filter) } private fun handleShareToSelectedRooms() = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index dc69fb5ba0..46293da209 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.spaces -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory @@ -33,8 +33,9 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space -import io.reactivex.Observable import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -44,19 +45,16 @@ 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.group.groupSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSortOrder -import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes 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.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.space.SpaceOrderUtils import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.rx import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, @@ -286,21 +284,23 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp null) } - val rxSession = session.rx() + val flowSession = session.flow() - Observable.combineLatest, List, List>( - rxSession + combine( + flowSession .liveUser(session.myUserId) .map { it.getOrNull() }, - rxSession + flowSession .liveSpaceSummaries(spaceSummaryQueryParams), - session.accountDataService().getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)).asObservable(), - { _, communityGroups, _ -> - communityGroups - } - ) + session + .accountDataService() + .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) + .asFlow() + ) { _, communityGroups, _ -> + communityGroups + } .execute { async -> val rootSpaces = session.spaceService().getRootSpaceSummaries() val orders = rootSpaces.map { diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 6cf4f9e0f6..cd7d6a379a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -25,6 +25,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.text.toSpannable import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState @@ -46,8 +47,7 @@ import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.spaces.manage.ManageType import im.vector.app.features.spaces.manage.SpaceAddRoomSpaceChooserBottomSheet import im.vector.app.features.spaces.manage.SpaceManageActivity -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import java.net.URL @@ -200,33 +200,29 @@ class SpaceDirectoryFragment @Inject constructor( } override fun onUrlClicked(url: String, title: String): Boolean { - permalinkHandler - .launch(requireActivity(), url, null) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { managed -> - if (!managed) { - if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { - MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) - .setTitle(R.string.external_link_confirmation_title) - .setMessage( - getString(R.string.external_link_confirmation_message, title, url) - .toSpannable() - .colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) - .colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) - ) - .setPositiveButton(R.string._continue) { _, _ -> - openUrlInExternalBrowser(requireContext(), url) - } - .setNegativeButton(R.string.cancel, null) - .show() - } else { - // Open in external browser, in a new Tab - openUrlInExternalBrowser(requireContext(), url) - } - } + viewLifecycleOwner.lifecycleScope.launch { + val isHandled = permalinkHandler.launch(requireActivity(), url, null) + if (!isHandled) { + if (title.isValidUrl() && url.isValidUrl() && URL(title).host != URL(url).host) { + MaterialAlertDialogBuilder(requireActivity(), R.style.ThemeOverlay_Vector_MaterialAlertDialog_Destructive) + .setTitle(R.string.external_link_confirmation_title) + .setMessage( + getString(R.string.external_link_confirmation_message, title, url) + .toSpannable() + .colorizeMatchingText(url, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) + .colorizeMatchingText(title, colorProvider.getColorFromAttribute(R.attr.vctr_content_tertiary)) + ) + .setPositiveButton(R.string._continue) { _, _ -> + openUrlInExternalBrowser(requireContext(), url) + } + .setNegativeButton(R.string.cancel, null) + .show() + } else { + // Open in external browser, in a new Tab + openUrlInExternalBrowser(requireContext(), url) } - .disposeOnDestroyView() + } + } // In fact it is always managed return true } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index d07b486fee..5e2537f587 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session @@ -42,7 +43,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( @@ -147,7 +148,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( excludeType = null } session - .rx() + .flow() .liveRoomSummaries(queryParams) .map { it.map { it.roomId }.toSet() @@ -158,12 +159,11 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.rx() + session.flow() .liveRoomChangeMembershipState() - .subscribe { - setState { copy(changeMembershipStates = it) } + .setOnEach { + copy(changeMembershipStates = it) } - .disposeOnClear() } override fun handle(action: SpaceDirectoryViewAction) { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 7eb7ce95ad..69b98200c1 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -160,16 +160,15 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } private fun observeUsers() = withState { state -> - identityServerUsersSearch .filter { it.isEmail() } .throttleLast(300, TimeUnit.MILLISECONDS) .switchMapSingle { search -> - val rx = session.rx() + val flowSession = session.rx() val stream = - rx.lookupThreePid(ThreePid.Email(search)).flatMap { + flowSession.lookupThreePid(ThreePid.Email(search)).flatMap { it.getOrNull()?.let { foundThreePid -> - rx.getProfileInfo(foundThreePid.matrixId) + flowSession.getProfileInfo(foundThreePid.matrixId) .map { json -> ThreePidUser( email = search, diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index f88bf6ef56..c88750e6e1 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -30,6 +30,7 @@ import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue @@ -44,7 +45,6 @@ import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx import timber.log.Timber import javax.net.ssl.HttpsURLConnection @@ -135,7 +135,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi return } val widgetId = initialState.widgetId ?: return - session.rx() + session.flow() .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) .filter { it.isNotEmpty() } .map { it.first() } diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index 0ed4e7d771..bbfeea6a76 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -15,23 +15,24 @@ */ package im.vector.app.features.widgets.permissions -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch 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.session.Session import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.flow.flow import timber.log.Timber import java.net.URL @@ -48,7 +49,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in private fun observeWidget() { val widgetId = initialState.widgetId ?: return - session.rx() + session.flow() .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) .filter { it.isNotEmpty() } .map { From 214deaa474855ec8ecfcaba0af4212c28106666a Mon Sep 17 00:00:00 2001 From: Philipp Neumann Date: Tue, 5 Oct 2021 12:54:22 +0200 Subject: [PATCH 015/172] added dynamic shortcut for priority conversations for Android 11+ --- newsfragment/3313.feature | 1 + .../notifications/NotificationDrawerManager.kt | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 newsfragment/3313.feature diff --git a/newsfragment/3313.feature b/newsfragment/3313.feature new file mode 100644 index 0000000000..3d243fa1ef --- /dev/null +++ b/newsfragment/3313.feature @@ -0,0 +1 @@ +Priority conversations for Android 11+ diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 37de7b1a75..ac5f698750 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -17,11 +17,15 @@ package im.vector.app.features.notifications import android.content.Context import android.graphics.Bitmap +import android.os.Build import android.os.Handler import android.os.HandlerThread import androidx.annotation.WorkerThread import androidx.core.app.NotificationCompat import androidx.core.app.Person +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat import im.vector.app.ActiveSessionDataSource import im.vector.app.BuildConfig import im.vector.app.R @@ -29,6 +33,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstThrottler import im.vector.app.features.displayname.getBestName import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -331,6 +336,19 @@ class NotificationDrawerManager @Inject constructor(private val context: Context .setKey(event.senderId) .build() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val openRoomIntent = RoomDetailActivity.shortcutIntent(context, roomId) + + val shortcut = ShortcutInfoCompat.Builder(context, roomId) + .setLongLived(true) + .setIntent(openRoomIntent) + .setShortLabel(roomName) + .setIcon(largeBitmap?.let { IconCompat.createWithAdaptiveBitmap(it) } ?: iconLoader.getUserIcon(event.senderAvatarPath)) + .build() + + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + } + if (event.outGoingMessage && event.outGoingMessageFailed) { style.addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson) roomEventGroupInfo.hasSmartReplyError = true From 95247f8b102fa8bada8d02ada9bbd4cf5f1edf9c Mon Sep 17 00:00:00 2001 From: Philipp Neumann Date: Tue, 5 Oct 2021 13:22:31 +0200 Subject: [PATCH 016/172] remove also LongLivedShortcuts --- .../java/im/vector/app/features/home/ShortcutsHandler.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index c3249f5b26..2fbd90ace7 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -75,7 +75,11 @@ class ShortcutsHandler @Inject constructor( return } - ShortcutManagerCompat.removeAllDynamicShortcuts(context) + // according to https://developer.android.com/reference/androidx/core/content/pm/ShortcutManagerCompat#removeLongLivedShortcuts(android.content.Context,%20java.util.List%3Cjava.lang.String%3E) + // removeLongLivedShortcuts for API 29 and lower should behave like removeDynamicShortcuts(Context, List) + // getDynamicShortcuts: returns all dynamic shortcuts from the app. + val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context).map { it.id } + ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts) // We can only disabled pinned shortcuts with the API, but at least it will prevent the crash if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { From 2223e95f3371f1319c8e54d78381102040b4ef9b Mon Sep 17 00:00:00 2001 From: Philipp Neumann Date: Tue, 5 Oct 2021 14:59:21 +0200 Subject: [PATCH 017/172] fixed ordering and url linting error --- .../main/java/im/vector/app/features/home/ShortcutsHandler.kt | 2 +- .../app/features/notifications/NotificationDrawerManager.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 2fbd90ace7..7514d455aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -75,7 +75,7 @@ class ShortcutsHandler @Inject constructor( return } - // according to https://developer.android.com/reference/androidx/core/content/pm/ShortcutManagerCompat#removeLongLivedShortcuts(android.content.Context,%20java.util.List%3Cjava.lang.String%3E) + // according to Android documentation // removeLongLivedShortcuts for API 29 and lower should behave like removeDynamicShortcuts(Context, List) // getDynamicShortcuts: returns all dynamic shortcuts from the app. val shortcuts = ShortcutManagerCompat.getDynamicShortcuts(context).map { it.id } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index ac5f698750..ecb1268892 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -32,8 +32,8 @@ import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstThrottler import im.vector.app.features.displayname.getBestName -import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.home.room.detail.RoomDetailActivity +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session From d9b02a20d84840a8549c48ee2dff8188c3ce5dcf Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 5 Oct 2021 18:57:34 +0200 Subject: [PATCH 018/172] Mavericks 2: remove matrix-sdk-android-flow as it will be easier when entirely migrating to flow --- matrix-sdk-android-flow/.gitignore | 1 - matrix-sdk-android-flow/build.gradle | 49 ----- matrix-sdk-android-flow/consumer-rules.pro | 0 matrix-sdk-android-flow/proguard-rules.pro | 21 -- .../sdk/flow/ExampleInstrumentedTest.kt | 40 ---- .../src/main/AndroidManifest.xml | 5 - .../org/matrix/android/sdk/flow/FlowRoom.kt | 83 ------- .../matrix/android/sdk/flow/FlowSession.kt | 138 ------------ .../android/sdk/flow/ExampleUnitTest.kt | 33 --- settings.gradle | 1 - vector/build.gradle | 1 - .../app/core/platform/VectorViewModel.kt | 32 --- .../im/vector/app/core/utils}/OptionalFlow.kt | 2 +- .../quads/SharedSecureStorageViewModel.kt | 8 +- .../features/devtools/RoomDevToolViewModel.kt | 7 +- .../discovery/DiscoverySettingsViewModel.kt | 7 +- .../features/home/HomeActivityViewModel.kt | 9 +- .../app/features/home/HomeDetailViewModel.kt | 17 +- .../UnknownDeviceDetectorSharedViewModel.kt | 10 +- .../room/breadcrumbs/BreadcrumbsViewModel.kt | 8 +- .../home/room/detail/RoomDetailViewModel.kt | 26 +-- .../action/MessageActionsViewModel.kt | 12 +- .../home/room/list/RoomListViewModel.kt | 9 +- .../app/features/invite/InvitesAcceptor.kt | 7 +- .../login2/created/AccountCreatedViewModel.kt | 10 +- .../powerlevel/PowerLevelsFlowFactory.kt | 11 +- .../room/RequireActiveMembershipViewModel.kt | 8 +- .../roomdirectory/RoomDirectoryViewModel.kt | 10 +- .../roompreview/RoomPreviewViewModel.kt | 11 +- .../RoomMemberProfileViewModel.kt | 14 +- .../devices/DeviceListBottomSheetViewModel.kt | 10 +- .../roomprofile/RoomProfileViewModel.kt | 26 ++- .../roomprofile/alias/RoomAliasViewModel.kt | 15 +- .../banned/RoomBannedMemberListViewModel.kt | 12 +- .../members/RoomMemberListViewModel.kt | 20 +- .../RoomNotificationSettingsViewModel.kt | 14 +- .../permissions/RoomPermissionsViewModel.kt | 7 +- .../settings/RoomSettingsViewModel.kt | 25 +-- .../uploads/RoomUploadsViewModel.kt | 7 +- .../settings/SecretsSynchronisationInfo.kt | 11 +- .../settings/VectorSettingsGeneralFragment.kt | 12 +- .../CrossSigningSettingsViewModel.kt | 6 +- ...iceVerificationInfoBottomSheetViewModel.kt | 14 +- .../settings/devices/DevicesViewModel.kt | 18 +- .../settings/devtools/AccountDataViewModel.kt | 6 +- .../settings/ignored/IgnoredUsersViewModel.kt | 6 +- .../threepids/ThreePidsSettingsViewModel.kt | 10 +- .../features/share/IncomingShareViewModel.kt | 8 +- .../app/features/spaces/SpaceMenuViewModel.kt | 25 +-- .../features/spaces/SpacesListViewModel.kt | 28 ++- .../spaces/explore/SpaceDirectoryViewModel.kt | 11 +- .../leave/SpaceLeaveAdvancedViewModel.kt | 7 +- .../userdirectory/UserListViewModel.kt | 204 ++++++++---------- .../app/features/widgets/WidgetViewModel.kt | 14 +- .../RoomWidgetPermissionViewModel.kt | 7 +- .../signout/ServerBackupStatusViewModel.kt | 18 +- .../workers/signout/SignoutCheckViewModel.kt | 6 +- 57 files changed, 370 insertions(+), 767 deletions(-) delete mode 100644 matrix-sdk-android-flow/.gitignore delete mode 100644 matrix-sdk-android-flow/build.gradle delete mode 100644 matrix-sdk-android-flow/consumer-rules.pro delete mode 100644 matrix-sdk-android-flow/proguard-rules.pro delete mode 100644 matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt delete mode 100644 matrix-sdk-android-flow/src/main/AndroidManifest.xml delete mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt delete mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt delete mode 100644 matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt rename {matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow => vector/src/main/java/im/vector/app/core/utils}/OptionalFlow.kt (96%) diff --git a/matrix-sdk-android-flow/.gitignore b/matrix-sdk-android-flow/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/matrix-sdk-android-flow/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle deleted file mode 100644 index 4aecec169b..0000000000 --- a/matrix-sdk-android-flow/build.gradle +++ /dev/null @@ -1,49 +0,0 @@ - -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' -} - -android { - compileSdk 31 - - defaultConfig { - minSdk 21 - targetSdk 31 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - - implementation project(":matrix-sdk-android") - implementation libs.androidx.appCompat - - implementation libs.jetbrains.kotlinStdlibJdk7 - implementation libs.jetbrains.coroutinesCore - implementation libs.jetbrains.coroutinesAndroid - implementation libs.androidx.lifecycleLivedata - - // Paging - implementation libs.androidx.pagingRuntimeKtx - - // Logging - implementation libs.jakewharton.timber - -} \ No newline at end of file diff --git a/matrix-sdk-android-flow/consumer-rules.pro b/matrix-sdk-android-flow/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/matrix-sdk-android-flow/proguard-rules.pro b/matrix-sdk-android-flow/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/matrix-sdk-android-flow/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt deleted file mode 100644 index 41799955a4..0000000000 --- a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.matrix.android.sdk.flow.test", appContext.packageName) - } -} diff --git a/matrix-sdk-android-flow/src/main/AndroidManifest.xml b/matrix-sdk-android-flow/src/main/AndroidManifest.xml deleted file mode 100644 index 2392c0bfcb..0000000000 --- a/matrix-sdk-android-flow/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file 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 deleted file mode 100644 index a3e476ce08..0000000000 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import androidx.lifecycle.asFlow -import kotlinx.coroutines.flow.Flow -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams -import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary -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 -import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState -import org.matrix.android.sdk.api.session.room.send.UserDraft -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.util.Optional - -class FlowRoom(private val room: Room) { - - fun liveRoomSummary(): Flow> { - return room.getRoomSummaryLive().asFlow() - } - - fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { - return room.getRoomMembersLive(queryParams).asFlow() - } - - fun liveAnnotationSummary(eventId: String): Flow> { - return room.getEventAnnotationsSummaryLive(eventId).asFlow() - } - - fun liveTimelineEvent(eventId: String): Flow> { - return room.getTimeLineEventLive(eventId).asFlow() - } - - fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { - return room.getStateEventLive(eventType, stateKey).asFlow() - } - - fun liveStateEvents(eventTypes: Set): Flow> { - return room.getStateEventsLive(eventTypes).asFlow() - } - - fun liveReadMarker(): Flow> { - return room.getReadMarkerLive().asFlow() - } - - fun liveReadReceipt(): Flow> { - return room.getMyReadReceiptLive().asFlow() - } - - fun liveEventReadReceipts(eventId: String): Flow> { - return room.getEventReadReceiptsLive(eventId).asFlow() - } - - fun liveDraft(): Flow> { - return room.getDraftLive().asFlow() - } - - fun liveNotificationState(): Flow { - return room.getLiveRoomNotificationState().asFlow() - } -} - -fun Room.flow(): FlowRoom { - return FlowRoom(this) -} 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 deleted file mode 100644 index affcd4a65d..0000000000 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2020 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import androidx.lifecycle.asFlow -import androidx.paging.PagedList -import kotlinx.coroutines.flow.Flow -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo -import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.session.pushers.Pusher -import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams -import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent -import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams -import org.matrix.android.sdk.api.session.sync.SyncState -import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo - -class RxFlow(private val session: Session) { - - fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { - return session.getRoomSummariesLive(queryParams).asFlow() - } - - fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { - return session.getGroupSummariesLive(queryParams).asFlow() - } - - fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow> { - return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() - } - - fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { - return session.getBreadcrumbsLive(queryParams).asFlow() - } - - fun liveMyDevicesInfo(): Flow> { - return session.cryptoService().getLiveMyDevicesInfo().asFlow() - } - - fun liveSyncState(): Flow { - return session.getSyncStateLive().asFlow() - } - - fun livePushers(): Flow> { - return session.getPushersLive().asFlow() - } - - fun liveUser(userId: String): Flow> { - return session.getUserLive(userId).asFlow() - } - - fun liveRoomMember(userId: String, roomId: String): Flow> { - return session.getRoomMemberLive(userId, roomId).asFlow() - } - - fun liveUsers(): Flow> { - return session.getUsersLive().asFlow() - } - - fun liveIgnoredUsers(): Flow> { - return session.getIgnoredUsersLive().asFlow() - } - - fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Flow> { - return session.getPagedUsersLive(filter, excludedUserIds).asFlow() - } - - fun liveThreePIds(refreshData: Boolean): Flow> { - return session.getThreePidsLive(refreshData).asFlow() - } - - fun livePendingThreePIds(): Flow> { - return session.getPendingThreePidsLive().asFlow() - } - - fun liveUserCryptoDevices(userId: String): Flow> { - return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() - } - - fun liveCrossSigningInfo(userId: String): Flow> { - return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() - } - - fun liveCrossSigningPrivateKeys(): Flow> { - return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() - } - - fun liveUserAccountData(types: Set): Flow> { - return session.accountDataService().getLiveUserAccountDataEvents(types).asFlow() - } - - fun liveRoomAccountData(types: Set): Flow> { - return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() - } - - fun liveRoomWidgets( - roomId: String, - widgetId: QueryStringValue, - widgetTypes: Set? = null, - excludedTypes: Set? = null - ): Flow> { - return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asFlow() - } - - fun liveRoomChangeMembershipState(): Flow> { - return session.getChangeMembershipsLive().asFlow() - } -} - -fun Session.flow(): RxFlow { - return RxFlow(this) -} diff --git a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt deleted file mode 100644 index bd627b2041..0000000000 --- a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/settings.gradle b/settings.gradle index e3b84b4733..b88ea99b05 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,4 +5,3 @@ include ':diff-match-patch' include ':attachment-viewer' include ':multipicker' include ':library:ui-styles' -include ':matrix-sdk-android-flow' diff --git a/vector/build.gradle b/vector/build.gradle index 76bc71b2d4..a9c6a407d8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -323,7 +323,6 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") - implementation project(":matrix-sdk-android-flow") implementation project(":diff-match-patch") implementation project(":multipicker") implementation project(":attachment-viewer") diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 1a77a00fab..01700049c1 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -16,16 +16,10 @@ package im.vector.app.core.platform -import com.airbnb.mvrx.Async import com.airbnb.mvrx.BaseMvRxViewModel -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.Success import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.PublishDataSource -import io.reactivex.Observable -import io.reactivex.Single abstract class VectorViewModel(initialState: S) : BaseMvRxViewModel(initialState) { @@ -38,31 +32,5 @@ abstract class VectorViewModel() val viewEvents: DataSource = _viewEvents - /** - * This method does the same thing as the execute function, but it doesn't subscribe to the stream - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Single.toAsync(stateReducer: S.(Async) -> S): Single> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnSuccess { setState { stateReducer(it) } } - } - - /** - * This method does the same thing as the execute function, but it doesn't subscribe to the stream - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Observable.toAsync(stateReducer: S.(Async) -> S): Observable> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnNext { setState { stateReducer(it) } } - } - abstract fun handle(action: VA) } diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt b/vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt similarity index 96% rename from matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt rename to vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt index a9f062f379..8e37ccfe8d 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt +++ b/vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.flow +package im.vector.app.core.utils import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 151b73ff32..2a0ce1da2b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.crypto.quads +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -43,9 +44,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream @@ -116,8 +115,9 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } } - session.flow() - .liveUserCryptoDevices(session.myUserId) + session.cryptoService() + .getLiveCryptoDeviceInfo(session.myUserId) + .asFlow() .distinctUntilChanged() .execute { copy( diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index 4bab65fd5d..0aed35fd55 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.devtools +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail @@ -40,9 +41,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.rx.rx class RoomDevToolViewModel @AssistedInject constructor( @Assisted val initialState: RoomDevToolViewState, @@ -70,8 +69,8 @@ class RoomDevToolViewModel @AssistedInject constructor( init { session.getRoom(initialState.roomId) - ?.flow() - ?.liveStateEvents(emptySet()) + ?.getStateEventsLive(emptySet()) + ?.asFlow() ?.execute { async -> copy(stateEvents = async) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index b248bcd065..ddbe2e151a 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.discovery +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -36,7 +37,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.flow.flow class DiscoverySettingsViewModel @AssistedInject constructor( @Assisted initialState: DiscoverySettingsState, @@ -85,8 +85,9 @@ class DiscoverySettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.flow() - .liveThreePIds(true) + session + .getThreePidsLive(true) + .asFlow() .onEach { retrieveBinding(it) } 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 fa3df1ca97..52ea898367 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 @@ -47,12 +47,9 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.util.awaitCallback -import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -104,8 +101,10 @@ class HomeActivityViewModel @AssistedInject constructor( .crossSigningService().allPrivateKeysKnown() safeActiveSession - .flow() - .liveCrossSigningInfo(safeActiveSession.myUserId) + .cryptoService() + .crossSigningService() + .getLiveCrossSigningKeys(safeActiveSession.myUserId) + .asFlow() .onEach { val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 316c1791fe..85a0716a77 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -48,7 +48,6 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import timber.log.Timber import java.util.concurrent.TimeUnit @@ -96,11 +95,13 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho updateShowDialPadTab() observeDataStore() callManager.addProtocolsCheckerListener(this) - session.flow().liveUser(session.myUserId).execute { - copy( - myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() - ) - } + session.getUserLive(session.myUserId) + .asFlow() + .execute { + copy( + myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() + ) + } } private fun observeDataStore() { @@ -183,8 +184,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeSyncState() { - session.flow() - .liveSyncState() + session.getSyncStateLive() + .asFlow() .setOnEach { syncState -> copy(syncState = syncState) } diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 143f843954..ef22875eaa 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -41,7 +42,6 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import timber.log.Timber @@ -99,9 +99,9 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) combine( - session.flow().liveUserCryptoDevices(session.myUserId), - session.flow().liveMyDevicesInfo(), - session.flow().liveCrossSigningPrivateKeys() + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow(), + session.cryptoService().getLiveMyDevicesInfo().asFlow(), + session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() ) { cryptoList, infoList, pInfo -> // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") @@ -132,7 +132,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) } - session.flow().liveUserCryptoDevices(session.myUserId) + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() .distinctUntilChanged() .sample(5_000) .onEach { diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index f32f132fe9..0e838db525 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.breadcrumbs +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -25,13 +26,10 @@ import dagger.assisted.AssistedFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import io.reactivex.schedulers.Schedulers import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, private val session: Session) @@ -62,11 +60,11 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B // PRIVATE METHODS ***************************************************************************** private fun observeBreadcrumbs() { - session.flow() - .liveBreadcrumbs(roomSummaryQueryParams { + session.getBreadcrumbsLive(roomSummaryQueryParams { displayName = QueryStringValue.NoCondition memberships = listOf(Membership.JOIN) }) + .asFlow() .execute { asyncBreadcrumbs -> copy(asyncBreadcrumbs = asyncBreadcrumbs) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index ddb1c51b5b..b1d18d7df4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -37,6 +37,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.unwrap import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder @@ -108,8 +109,6 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber import java.util.concurrent.TimeUnit @@ -254,11 +253,12 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeActiveRoomWidgets() { - session.flow() - .liveRoomWidgets( + session.widgetService() + .getRoomWidgetsLive( roomId = initialState.roomId, widgetId = QueryStringValue.NoCondition ) + .asFlow() .map { widgets -> widgets.filter { it.isActive } } @@ -287,8 +287,9 @@ class RoomDetailViewModel @AssistedInject constructor( val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE) } - room.flow() - .liveRoomMembers(queryParams) + room + .getRoomMembersLive(queryParams) + .asFlow() .map { it.firstOrNull().toOptional() } @@ -1505,8 +1506,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeSyncState() { - session.flow() - .liveSyncState() + session.getSyncStateLive() + .asFlow() .setOnEach { syncState -> copy(syncState = syncState) } @@ -1520,7 +1521,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy( @@ -1532,7 +1534,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun getUnreadState() { combine( timelineEvents, - room.flow().liveRoomSummary().unwrap() + room.getRoomSummaryLive().asFlow().unwrap() ) { timelineEvents, roomSummary -> computeUnreadState(timelineEvents, roomSummary) } @@ -1579,8 +1581,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.flow() - .liveRoomChangeMembershipState() + session.getChangeMembershipsLive() + .asFlow() .map { it[initialState.roomId] ?: ChangeMembershipState.Unknown } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index f63366482b..b04fa98ab1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.home.room.detail.timeline.action +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -28,6 +29,7 @@ import im.vector.app.core.extensions.canReact import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.unwrap import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor @@ -58,8 +60,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap /** * Information related to an event and used to display preview in contextual bottom sheet. @@ -137,8 +137,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeEvent() { if (room == null) return - room.flow() - .liveTimelineEvent(initialState.eventId) + room.getTimeLineEventLive(initialState.eventId) + .asFlow() .unwrap() .execute { copy(timelineEvent = it) @@ -149,8 +149,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (room == null) return eventIdFlow .flatMapLatest { eventId -> - room.flow() - .liveAnnotationSummary(eventId) + room.getEventAnnotationsSummaryLive(eventId) + .asFlow() .map { annotations -> EmojiDataSource.quickEmojis.map { emoji -> ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe ?: false) 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 b54d2b1b4f..59e73462bc 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 @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.list import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -42,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject @@ -95,7 +95,8 @@ class RoomListViewModel @Inject constructor( ) } - session.flow().liveUser(session.myUserId) + session.getUserLive(session.myUserId) + .asFlow() .map { it.getOrNull()?.getBestName() } .distinctUntilChanged() .execute { @@ -106,8 +107,8 @@ class RoomListViewModel @Inject constructor( } private fun observeMembershipChanges() { - session.flow() - .liveRoomChangeMembershipState() + session.getChangeMembershipsLive() + .asFlow() .setOnEach { copy(roomMembershipChanges = it) } diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index 09eff756d5..b22390c4d5 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -16,6 +16,7 @@ package im.vector.app.features.invite +import androidx.lifecycle.asFlow import im.vector.app.ActiveSessionDataSource import im.vector.app.features.session.coroutineScope import io.reactivex.disposables.Disposable @@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -82,10 +82,9 @@ class InvitesAcceptor @Inject constructor( val roomQueryParams = roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - val flowSession = session.flow() combine( - flowSession.liveRoomSummaries(roomQueryParams), - flowSession.liveRoomChangeMembershipState().debounce(1000) + session.getRoomSummariesLive(roomQueryParams).asFlow(), + session.getChangeMembershipsLive().asFlow().debounce(1000) ) { invitedRooms, _ -> invitedRooms.map { it.roomId } } .filter { it.isNotEmpty() } .onEach { invitedRoomIds -> diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index c95434a548..01ee3a7a23 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.login2.created -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -24,13 +24,13 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.unwrap +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap import timber.log.Timber class AccountCreatedViewModel @AssistedInject constructor( @@ -62,8 +62,8 @@ class AccountCreatedViewModel @AssistedInject constructor( } private fun observeUser() { - session.rx() - .liveUser(session.myUserId) + session.getUserLive(session.myUserId) + .asFlow() .unwrap() .map { if (MatrixPatterns.isUserId(it.userId)) { diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index 767d6f1ba7..e1992ec572 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -16,6 +16,9 @@ package im.vector.app.features.powerlevel +import androidx.lifecycle.asFlow +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn @@ -24,15 +27,13 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap class PowerLevelsFlowFactory(private val room: Room) { fun createFlow(): Flow { - return room.flow() - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + return room + .getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + .asFlow() .flowOn(Dispatchers.Default) .mapOptional { it.content.toModel() } .unwrap() diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 62519336f5..a24d2df72c 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.room +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -27,6 +28,7 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.unwrap import io.reactivex.Observable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -47,8 +49,6 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap /** * This ViewModel observe a room summary and notify when the room is left @@ -90,8 +90,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( val emptyResult = Optional.empty() emit(emptyResult) } - room.flow() - .liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .flowOn(Dispatchers.Default) .map { mapToLeftViewEvent(room, it) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index a2089e6cd5..ff5e3ac3f5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomdirectory +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -38,7 +39,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow import timber.log.Timber class RoomDirectoryViewModel @AssistedInject constructor( @@ -80,8 +80,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) } session - .flow() - .liveRoomSummaries(queryParams) + .getRoomSummariesLive(queryParams) + .asFlow() .map { roomSummaries -> roomSummaries .map { it.roomId } @@ -93,8 +93,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.flow() - .liveRoomChangeMembershipState() + session.getChangeMembershipsLive() + .asFlow() .setOnEach { copy(changeMembershipStates = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 2635307e95..5fa7f34aca 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomdirectory.roompreview +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -42,8 +43,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx import timber.log.Timber class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState, @@ -168,8 +167,8 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini excludeType = null } session - .flow() - .liveRoomSummaries(queryParams) + .getRoomSummariesLive(queryParams) + .asFlow() .onEach { list -> val isRoomJoined = list.any { it.membership == Membership.JOIN @@ -187,8 +186,8 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini } private fun observeMembershipChanges() { - session.flow() - .liveRoomChangeMembershipState() + session.getChangeMembershipsLive() + .asFlow() .onEach { val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown val joinState = when (changeMembership) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 14624b800e..b424d62b1c 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roommemberprofile +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -31,6 +32,7 @@ import im.vector.app.R import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -53,8 +55,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, private val stringProvider: StringProvider, @@ -107,7 +107,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - session.flow().liveUserCryptoDevices(initialState.userId) + session.cryptoService().getLiveCryptoDeviceInfo(initialState.userId).asFlow() .map { Pair( it.fold(true, { prev, dev -> prev && dev.isVerified }), @@ -121,14 +121,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v ) } - session.flow().liveCrossSigningInfo(initialState.userId) + session.cryptoService().crossSigningService().getLiveCrossSigningKeys(initialState.userId).asFlow() .execute { copy(userMXCrossSigningInfo = it.invoke()?.getOrNull()) } } private fun observeIgnoredState() { - session.flow().liveIgnoredUsers() + session.getIgnoredUsersLive().asFlow() .map { ignored -> ignored.find { it.userId == initialState.userId @@ -245,7 +245,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } - room.flow().liveRoomMembers(queryParams) + room.getRoomMembersLive(queryParams).asFlow() .map { it.firstOrNull().toOptional() } .unwrap() .execute { @@ -285,7 +285,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } private fun observeRoomSummaryAndPowerLevels(room: Room) { - val roomSummaryLive = room.flow().liveRoomSummary().unwrap() + val roomSummaryLive = room.getRoomSummaryLive().asFlow().unwrap() val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index b638d84181..1de96a9ef4 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -16,6 +16,7 @@ */ package im.vector.app.features.roommemberprofile.devices +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -33,9 +34,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.rx.rx data class DeviceListViewState( val userItem: MatrixItem? = null, @@ -56,14 +55,17 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } init { - session.flow().liveUserCryptoDevices(args.userId) + session.cryptoService().getLiveCryptoDeviceInfo(args.userId) + .asFlow() .execute { copy(cryptoDevices = it).also { refreshSelectedId() } } - session.flow().liveCrossSigningInfo(args.userId) + session.cryptoService().crossSigningService() + .getLiveCrossSigningKeys(args.userId) + .asFlow() .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index c4fc2bc7bb..d08d78e64b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -27,6 +28,8 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers @@ -40,10 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.flow.FlowRoom -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap class RoomProfileViewModel @AssistedInject constructor( @Assisted private val initialState: RoomProfileViewState, @@ -69,15 +68,14 @@ class RoomProfileViewModel @AssistedInject constructor( private val room = session.getRoom(initialState.roomId)!! init { - val flowRoom = room.flow() - observeRoomSummary(flowRoom) - observeRoomCreateContent(flowRoom) - observeBannedRoomMembers(flowRoom) + observeRoomSummary() + observeRoomCreateContent() + observeBannedRoomMembers() observePermissions() } - private fun observeRoomCreateContent(flowRoom: FlowRoom) { - flowRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition) + private fun observeRoomCreateContent() { + room.getStateEventLive(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition).asFlow() .mapOptional { it.content.toModel() } .unwrap() .execute { async -> @@ -92,16 +90,16 @@ class RoomProfileViewModel @AssistedInject constructor( } } - private fun observeRoomSummary(flowRoom: FlowRoom) { - flowRoom.liveRoomSummary() + private fun observeRoomSummary() { + room.getRoomSummaryLive().asFlow() .unwrap() .execute { copy(roomSummary = it) } } - private fun observeBannedRoomMembers(flowRoom: FlowRoom) { - flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + private fun observeBannedRoomMembers() { + room.getRoomMembersLive(roomMemberQueryParams { memberships = listOf(Membership.BAN) }).asFlow() .execute { copy(bannedMembership = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 28a1804fab..086f279655 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.alias +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -28,6 +29,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -39,11 +42,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState, private val session: Session) @@ -131,7 +129,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy( @@ -173,8 +172,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomCanonicalAlias() { - room.flow() - .liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .setOnEach { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 813d50c6bb..4e244b747b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.banned +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -26,6 +27,7 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -38,10 +40,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, private val stringProvider: StringProvider, @@ -57,13 +55,15 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia init { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy(roomSummary = async) } - room.flow().liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + room.getRoomMembersLive(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) + .asFlow() .execute { copy( bannedMemberSummaries = it diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 2873b20400..c5ed085bff 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -27,6 +27,8 @@ import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers @@ -51,9 +53,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -94,9 +93,9 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } combine( - room.flow().liveRoomMembers(roomMemberQueryParams), - room.flow() - .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + room.getRoomMembersLive(roomMemberQueryParams).asFlow(), + room.getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() ) @@ -109,7 +108,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } if (room.isEncrypted()) { - room.flow().liveRoomMembers(roomMemberQueryParams) + room.getRoomMembersLive(roomMemberQueryParams) + .asFlow() .flowOn(Dispatchers.Main) .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) @@ -153,7 +153,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy(roomSummary = async) @@ -161,7 +162,8 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeThirdPartyInvites() { - room.flow().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) + room.getStateEventsLive(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) + .asFlow() .execute { async -> copy(threePidInvites = async) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index d944b77f7d..498a70a1cb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.notifications -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success @@ -25,13 +25,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.unwrap import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomNotificationSettingsViewModel @AssistedInject constructor( @Assisted initialState: RoomNotificationSettingsViewState, @@ -66,7 +63,8 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy(roomSummary = async) @@ -74,8 +72,8 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeNotificationState() { - room.rx() - .liveNotificationState() + room.getLiveRoomNotificationState() + .asFlow() .execute { copy(notificationState = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index 71e8a313b5..e3dd76f44c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.permissions +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success @@ -25,6 +26,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -34,8 +36,6 @@ 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.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState, private val session: Session) @@ -63,7 +63,8 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 7b28ced130..f9d64dfb56 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -26,6 +27,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.flow.launchIn @@ -43,9 +46,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val vectorPreferences: VectorPreferences, @@ -125,7 +125,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> val roomSummary = async.invoke() @@ -161,8 +162,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomHistoryVisibility() { - room.flow() - .liveStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.historyVisibility } @@ -172,8 +173,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeJoinRule() { - room.flow() - .liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.joinRules } @@ -183,8 +184,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeGuestAccess() { - room.flow() - .liveStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.guestAccess } @@ -197,8 +198,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomAvatar() { - room.flow() - .liveStateEvent(EventType.STATE_ROOM_AVATAR, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_AVATAR, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .setOnEach { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 4526024143..c9d9b6b23c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.uploads +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -28,11 +29,10 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.unwrap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -65,7 +65,8 @@ class RoomUploadsViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index 5afcb77587..bb0330e6f6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings +import androidx.lifecycle.asFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -25,7 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.SecretsSynchronisationInfo data class SecretsSynchronisationInfo( @@ -39,11 +39,12 @@ data class SecretsSynchronisationInfo( ) fun Session.liveSecretSynchronisationInfo(): Flow { - val sessionFlow = flow() return combine( - sessionFlow.liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), - sessionFlow.liveCrossSigningInfo(myUserId), - sessionFlow.liveCrossSigningPrivateKeys() + accountDataService() + .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)) + .asFlow(), + cryptoService().crossSigningService().getLiveCrossSigningKeys(myUserId).asFlow(), + cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() ) { _, crossSigningInfo, pInfo -> // first check if 4S is already setup val is4SSetup = sharedSecretStorageService.isRecoverySetup() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 8eb9e2cdf3..f539f46b3a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible +import androidx.lifecycle.asFlow import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreference import androidx.preference.Preference @@ -47,6 +48,7 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.toast +import im.vector.app.core.utils.unwrap import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -62,8 +64,6 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap import java.io.File import java.util.UUID import javax.inject.Inject @@ -122,8 +122,8 @@ class VectorSettingsGeneralFragment @Inject constructor( } private fun observeUserAvatar() { - session.flow() - .liveUser(session.myUserId) + session.getUserLive(session.myUserId) + .asFlow() .unwrap() .distinctUntilChangedBy { user -> user.avatarUrl } .onEach { @@ -133,8 +133,8 @@ class VectorSettingsGeneralFragment @Inject constructor( } private fun observeUserDisplayName() { - session.flow() - .liveUser(session.myUserId) + session.getUserLive(session.myUserId) + .asFlow() .unwrap() .map { it.displayName ?: "" } .distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index a8fafb096a..47010f180a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.settings.crosssigning +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -37,7 +38,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth @@ -56,8 +56,8 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( init { combine( - session.flow().liveMyDevicesInfo(), - session.flow().liveCrossSigningInfo(session.myUserId) + session.cryptoService().getLiveMyDevicesInfo().asFlow(), + session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId).asFlow() ) { myDevicesInfo, mxCrossSigningInfo -> myDevicesInfo to mxCrossSigningInfo diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 38342efc46..8c0c077472 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.settings.devices +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory @@ -27,9 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.rx.rx class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState, @Assisted val deviceId: String, @@ -50,7 +49,8 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() ) } - session.flow().liveCrossSigningInfo(session.myUserId) + session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId) + .asFlow() .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -58,7 +58,8 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.flow().liveUserCryptoDevices(session.myUserId) + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId) + .asFlow() .map { list -> list.firstOrNull { it.deviceId == deviceId } } @@ -69,7 +70,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.flow().liveUserCryptoDevices(session.myUserId) + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() .map { it.size } .execute { copy( @@ -81,7 +82,8 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy(deviceInfo = Loading()) } - session.flow().liveMyDevicesInfo() + session.cryptoService().getLiveMyDevicesInfo() + .asFlow() .map { devices -> devices.firstOrNull { it.deviceId == deviceId } ?: DeviceInfo(deviceId = deviceId) } 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 b2b4c0c396..d58ad4a99a 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 @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devices +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -56,7 +57,6 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod 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 org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo @@ -123,8 +123,14 @@ class DevicesViewModel @AssistedInject constructor( } combine( - session.flow().liveUserCryptoDevices(session.myUserId), - session.flow().liveMyDevicesInfo() + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() + .onEach { + Timber.v("getLiveCryptoDeviceInfo") + }, + session.cryptoService().getLiveMyDevicesInfo().asFlow() + .onEach { + Timber.v("getLiveMyDevicesInfo") + } ) { cryptoList, infoList -> infoList @@ -141,7 +147,8 @@ class DevicesViewModel @AssistedInject constructor( ) } - session.flow().liveCrossSigningInfo(session.myUserId) + session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId) + .asFlow() .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -157,7 +164,8 @@ class DevicesViewModel @AssistedInject constructor( // ) // } - session.flow().liveUserCryptoDevices(session.myUserId) + session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId) + .asFlow() .map { it.size } .distinctUntilChanged() .sample(5_000) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index e5739ec446..69ee9165a7 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devtools +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState @@ -31,7 +32,6 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.flow.flow data class AccountDataViewState( val accountData: Async> = Uninitialized @@ -42,7 +42,9 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A : VectorViewModel(initialState) { init { - session.flow().liveUserAccountData(emptySet()) + session.accountDataService() + .getLiveUserAccountDataEvents(emptySet()) + .asFlow() .execute { copy(accountData = it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index 7b7b5d0570..702a31f22c 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.ignored +import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -33,7 +34,6 @@ import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.flow.flow data class IgnoredUsersViewState( val ignoredUsers: List = emptyList(), @@ -67,8 +67,8 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeIgnoredUsers() { - session.flow() - .liveIgnoredUsers() + session.getIgnoredUsersLive() + .asFlow() .execute { async -> copy( ignoredUsers = async.invoke().orEmpty() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index cd0d74a288..0d00c72062 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.threepids +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -38,7 +39,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import timber.log.Timber @@ -101,8 +101,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.flow() - .liveThreePIds(true) + session.getThreePidsLive(true) + .asFlow() .execute { copy( threePids = it @@ -111,8 +111,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observePendingThreePids() { - session.flow() - .livePendingThreePIds() + session.getPendingThreePidsLive() + .asFlow() .execute { copy( pendingThreePids = it, diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 44e5ca39f9..39dc3f2344 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.share +import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -37,7 +38,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow class IncomingShareViewModel @AssistedInject constructor( @Assisted initialState: IncomingShareViewState, @@ -69,8 +69,8 @@ class IncomingShareViewModel @AssistedInject constructor( val queryParams = roomSummaryQueryParams { memberships = listOf(Membership.JOIN) } - session - .flow().liveRoomSummaries(queryParams) + session.getRoomSummariesLive(queryParams) + .asFlow() .execute { copy(roomSummaries = it) } @@ -86,7 +86,7 @@ class IncomingShareViewModel @AssistedInject constructor( displayName = displayNameQuery memberships = listOf(Membership.JOIN) } - session.flow().liveRoomSummaries(filterQueryParams) + session.getRoomSummariesLive(filterQueryParams).asFlow() } .sample(300) .map { it.sortedWith(breadcrumbsRoomComparator) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index bb30670da9..a75f773a4c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -42,8 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx import timber.log.Timber class SpaceMenuViewModel @AssistedInject constructor( @@ -78,17 +77,19 @@ class SpaceMenuViewModel @AssistedInject constructor( session.getRoom(initialState.spaceId)?.let { room -> - room.flow().liveRoomSummary().onEach { - it.getOrNull()?.let { - if (it.membership == Membership.LEAVE) { - setState { copy(leavingState = Success(Unit)) } - if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { - // switch to home? - appStateHandler.setCurrentSpace(null, session) + room.getRoomSummaryLive() + .asFlow() + .onEach { + it.getOrNull()?.let { + if (it.membership == Membership.LEAVE) { + setState { copy(leavingState = Success(Unit)) } + if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { + // switch to home? + appStateHandler.setCurrentSpace(null, session) + } + } } - } - } - }.launchIn(viewModelScope) + }.launchIn(viewModelScope) PowerLevelsFlowFactory(room) .createFlow() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 46293da209..e902240a2e 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.space.SpaceOrderUtils import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import java.util.concurrent.TimeUnit @@ -82,14 +81,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp init { - session.getUserLive(session.myUserId).asObservable() - .subscribe { - setState { - copy( - myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() - ) - } - }.disposeOnClear() + session.getUserLive(session.myUserId) + .asFlow() + .setOnEach { + copy( + myMxItem = it.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() + ) + } observeSpaceSummaries() // observeSelectionState() @@ -284,16 +282,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp null) } - val flowSession = session.flow() - combine( - flowSession - .liveUser(session.myUserId) + session.getUserLive(session.myUserId) + .asFlow() .map { it.getOrNull() }, - flowSession - .liveSpaceSummaries(spaceSummaryQueryParams), + session.spaceService().getSpaceSummariesLive(spaceSummaryQueryParams).asFlow(), session .accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) @@ -319,7 +314,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // clear local echos on update session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) - .asObservable().execute { + .asFlow() + .execute { copy( spaceOrderLocalEchos = emptyMap() ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 5e2537f587..127ef5d6bf 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces.explore +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -43,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( @@ -147,9 +147,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) excludeType = null } - session - .flow() - .liveRoomSummaries(queryParams) + session.getRoomSummariesLive(queryParams) + .asFlow() .map { it.map { it.roomId }.toSet() } @@ -159,8 +158,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.flow() - .liveRoomChangeMembershipState() + session.getChangeMembershipsLive() + .asFlow() .setOnEach { copy(changeMembershipStates = it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 3d24cf6225..5f4731e9f4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces.leave +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -30,6 +31,7 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.utils.unwrap import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -39,8 +41,6 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class SpaceLeaveAdvancedViewModel @AssistedInject constructor( @@ -97,7 +97,8 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( val spaceSummary = session.getRoomSummary(initialState.spaceId) setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId)?.let { room -> - room.flow().liveRoomSummary() + room.getRoomSummaryLive() + .asFlow() .unwrap() .onEach { if (it.membership == Membership.LEAVE) { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 69b98200c1..cae8540f8b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -16,12 +16,12 @@ package im.vector.app.features.userdirectory +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -29,21 +29,23 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit - -private typealias KnownUsersSearch = String -private typealias DirectoryUsersSearch = String data class ThreePidUser( val email: String, @@ -54,9 +56,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val session: Session) : VectorViewModel(initialState) { - private val knownUsersSearch = BehaviorRelay.create() - private val directoryUsersSearch = BehaviorRelay.create() - private val identityServerUsersSearch = BehaviorRelay.create() + private val knownUsersSearch = MutableStateFlow("") + private val directoryUsersSearch = MutableStateFlow("") + private val identityServerUsersSearch = MutableStateFlow("") @AssistedFactory interface Factory { @@ -77,11 +79,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val identityServerListener = object : IdentityServiceListener { override fun onIdentityServerChange() { withState { - identityServerUsersSearch.accept(it.searchTerm) + identityServerUsersSearch.tryEmit(it.searchTerm) + val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) setState { - copy( - configuredIdentityServer = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) - ) + copy(configuredIdentityServer = identityServerURL) } } } @@ -120,7 +121,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { session.identityService().setUserConsent(action.consent) withState { - identityServerUsersSearch.accept(it.searchTerm) + identityServerUsersSearch.tryEmit(it.searchTerm) } } @@ -139,9 +140,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User ) } } - identityServerUsersSearch.accept(searchTerm) - knownUsersSearch.accept(searchTerm) - directoryUsersSearch.accept(searchTerm) + identityServerUsersSearch.tryEmit(searchTerm) + knownUsersSearch.tryEmit(searchTerm) + directoryUsersSearch.tryEmit(searchTerm) } private fun handleShareMyMatrixToLink() { @@ -151,9 +152,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } private fun handleClearSearchUsers() { - knownUsersSearch.accept("") - directoryUsersSearch.accept("") - identityServerUsersSearch.accept("") + knownUsersSearch.tryEmit("") + directoryUsersSearch.tryEmit("") + identityServerUsersSearch.tryEmit("") setState { copy(searchTerm = "") } @@ -162,103 +163,82 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun observeUsers() = withState { state -> identityServerUsersSearch .filter { it.isEmail() } - .throttleLast(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val flowSession = session.rx() - val stream = - flowSession.lookupThreePid(ThreePid.Email(search)).flatMap { - it.getOrNull()?.let { foundThreePid -> - flowSession.getProfileInfo(foundThreePid.matrixId) - .map { json -> - ThreePidUser( - email = search, - user = User( - userId = foundThreePid.matrixId, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ) - ) - } - .onErrorResumeNext { - Single.just(ThreePidUser(email = search, user = User(foundThreePid.matrixId))) - } - } ?: Single.just(ThreePidUser(email = search, user = null)) - } - stream.toAsync { - copy(matchingEmail = it) - } - } - .subscribe() - .disposeOnClear() + .sample(300) + .onEach { search -> + executeSearchEmail(search) + }.launchIn(viewModelScope) knownUsersSearch - .throttleLast(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - session.rx().livePagedUsers(it, state.excludedUserIds) - } - .execute { async -> - copy(knownUsers = async) + .sample(300) + .flowOn(Dispatchers.Main) + .flatMapLatest { search -> + session.getPagedUsersLive(search, state.excludedUserIds).asFlow() + }.execute { + copy(knownUsers = it) } directoryUsersSearch - .debounce(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val stream = if (search.isBlank()) { - Single.just(emptyList()) - } else { - val searchObservable = session.rx() - .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) - .map { users -> - users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } - } - // If it's a valid user id try to use Profile API - // because directory only returns users that are in public rooms or share a room with you, where as - // profile will work other federations - if (!MatrixPatterns.isUserId(search)) { - searchObservable - } else { - val profileObservable = session.rx().getProfileInfo(search) - .map { json -> - User( - userId = search, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ).toOptional() - } - .onErrorResumeNext { - // Profile API can be restricted and doesn't have to return result. - // In this case allow inviting valid user ids. - Single.just( - User( - userId = search, - displayName = null, - avatarUrl = null - ).toOptional() - ) - } + .debounce(300) + .onEach { search -> + executeSearchDirectory(state, search) + }.launchIn(viewModelScope) + } - Single.zip( - searchObservable, - profileObservable, - { searchResults, optionalProfile -> - val profile = optionalProfile.getOrNull() ?: return@zip searchResults - val searchContainsProfile = searchResults.any { it.userId == profile.userId } - if (searchContainsProfile) { - searchResults - } else { - listOf(profile) + searchResults - } - } + private suspend fun executeSearchEmail(search: String) { + suspend { + val params = listOf(ThreePid.Email(search)) + val foundThreePid = tryOrNull { + session.identityService().lookUp(params).firstOrNull() + } + if (foundThreePid == null) { + null + } else { + try { + val json = session.getProfile(foundThreePid.matrixId) + ThreePidUser( + email = search, + user = User( + userId = foundThreePid.matrixId, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String ) - } - } - stream.toAsync { - copy(directoryUsers = it) - } + ) + } catch (failure: Throwable) { + ThreePidUser(email = search, user = User(foundThreePid.matrixId)) } - .subscribe() - .disposeOnClear() + } + }.execute { + copy(matchingEmail = it) + } + } + + private suspend fun executeSearchDirectory(state: UserListViewState, search: String) { + suspend { + if (search.isBlank()) { + emptyList() + } else { + val searchResult = session + .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) + .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + val userProfile = if (MatrixPatterns.isUserId(search)) { + val json = tryOrNull { session.getProfile(search) } + User( + userId = search, + displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String, + avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String + ) + } else { + null + } + if (userProfile == null || searchResult.any { it.userId == userProfile.userId }) { + searchResult + } else { + listOf(userProfile) + searchResult + } + } + }.execute { + copy(directoryUsers = it) + } } private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index c88750e6e1..05f6157d11 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.widgets import android.net.Uri +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -29,6 +30,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.mapOptional +import im.vector.app.core.utils.unwrap import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -42,9 +45,6 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure -import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.flow.mapOptional -import org.matrix.android.sdk.flow.unwrap import timber.log.Timber import javax.net.ssl.HttpsURLConnection @@ -119,7 +119,8 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi if (room == null) { return } - room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + room.getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + .asFlow() .mapOptional { it.content.toModel() } .unwrap() .map { @@ -135,8 +136,9 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi return } val widgetId = initialState.widgetId ?: return - session.flow() - .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) + session.widgetService() + .getRoomWidgetsLive(initialState.roomId, QueryStringValue.Equals(widgetId)) + .asFlow() .filter { it.isNotEmpty() } .map { it.first() } .execute { diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index bbfeea6a76..ee1ffd62c6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -15,6 +15,7 @@ */ package im.vector.app.features.widgets.permissions +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -32,7 +33,6 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.flow.flow import timber.log.Timber import java.net.URL @@ -49,8 +49,9 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in private fun observeWidget() { val widgetId = initialState.widgetId ?: return - session.flow() - .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) + session.widgetService() + .getRoomWidgetsLive(initialState.roomId, QueryStringValue.Equals(widgetId)) + .asFlow() .filter { it.isNotEmpty() } .map { val widget = it.first() diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index c3719ffd8e..f0caac9b97 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.workers.signout import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -41,7 +42,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.flow.flow data class ServerBackupStatusViewState( val bannerState: Async = Uninitialized @@ -92,9 +92,19 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS init { session.cryptoService().keysBackupService().addListener(this) keysBackupState.value = session.cryptoService().keysBackupService().state - val liveUserAccountData = session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) - val liveCrossSigningInfo = session.flow().liveCrossSigningInfo(session.myUserId) - val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() + val liveUserAccountData = session.accountDataService() + .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + .asFlow() + val liveCrossSigningInfo = session.cryptoService() + .crossSigningService() + .getLiveCrossSigningKeys(session.myUserId) + .asFlow() + val liveCrossSigningPrivateKeys = session.cryptoService() + .crossSigningService() + .getLiveCrossSigningPrivateKeys() + .asFlow() + + combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> // first check if 4S is already setup if (session.sharedSecretStorageService.isRecoverySetup()) { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index 057d9e31f8..e6429b6263 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.workers.signout import android.net.Uri +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -43,7 +44,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener -import org.matrix.android.sdk.flow.flow import timber.log.Timber data class SignoutCheckViewState( @@ -98,7 +98,9 @@ class SignoutCheckViewModel @AssistedInject constructor( ) } - session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + session.accountDataService() + .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + .asFlow() .map { session.sharedSecretStorageService.isRecoverySetup() } From 9ab59a543d15483d19a1b0720e7a9e4e2b5c1a4d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 17 Sep 2021 17:14:11 +0300 Subject: [PATCH 019/172] * Implement Presence Service: - Get Presence Status - Set Presence Status * Integrate presence in room details screen * Integrate presence in room people's view * Update UI to support presence * Fix bug when insertOrUpdate was called on RoomMemberEventHandler and override the correct presence value in RoomMemberSummaryEntity * Improve performance on updateUserPresence in RoomMemberSummaryEntity entity * Remarks & linter fixes * Disable presence when there is no m.presence events. In some servers like matrix.org is disabled atm. * Enhance UI Presence on DM room lists to support dark/light theme * Restore missing lines in gradle.properties to speed up debugging --- gradle.properties | 3 + .../ui-styles/src/main/res/values/colors.xml | 4 ++ .../src/main/res/values/theme_dark.xml | 3 + .../src/main/res/values/theme_light.xml | 3 + .../matrix/android/sdk/api/session/Session.kt | 2 + .../sdk/api/session/events/model/Event.kt | 5 ++ .../api/session/presence/PresenceService.kt | 41 +++++++++++++ .../session/room/model/RoomMemberSummary.kt | 3 + .../sdk/api/session/room/model/RoomSummary.kt | 2 + .../database/RealmSessionStoreMigration.kt | 27 ++++++++- .../mapper/RoomMemberSummaryMapper.kt | 2 + .../database/mapper/RoomSummaryMapper.kt | 2 + .../database/model/RoomMemberSummaryEntity.kt | 7 +++ .../database/model/RoomSummaryEntity.kt | 6 ++ .../database/model/SessionRealmModule.kt | 4 +- .../model/presence/UserPresenceEntity.kt | 49 +++++++++++++++ .../database/query/RoomMemberEntityQueries.kt | 11 ++++ .../query/RoomSummaryEntityQueries.kt | 9 +++ .../query/UserPresenceEntityQueries.kt | 29 +++++++++ .../sdk/internal/session/DefaultSession.kt | 3 + .../sdk/internal/session/SessionComponent.kt | 4 +- .../internal/session/presence/PresenceAPI.kt | 43 +++++++++++++ .../session/presence/di/PresenceModule.kt | 53 ++++++++++++++++ .../presence/model/GetPresenceResponse.kt | 32 ++++++++++ .../session/presence/model/PresenceContent.kt | 32 ++++++++++ .../session/presence/model/PresenceEnum.kt | 41 +++++++++++++ .../session/presence/model/SetPresenceBody.kt | 27 +++++++++ .../session/presence/model/UserPresence.kt | 23 +++++++ .../service/DefaultPresenceService.kt | 36 +++++++++++ .../presence/service/task/GetPresenceTask.kt | 42 +++++++++++++ .../presence/service/task/SetPresenceTask.kt | 47 +++++++++++++++ .../membership/RoomMemberEntityFactory.kt | 4 +- .../room/membership/RoomMemberEventHandler.kt | 21 ++++++- .../session/room/read/SetReadMarkersTask.kt | 6 +- .../session/room/timeline/DefaultTimeline.kt | 2 +- .../room/timeline/DefaultTimelineService.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 17 +++++- .../SyncResponsePostTreatmentAggregator.kt | 4 +- .../sync/{ => handler}/CryptoSyncHandler.kt | 2 +- .../sync/{ => handler}/GroupSyncHandler.kt | 2 +- .../sync/handler/PresenceSyncHandler.kt | 60 +++++++++++++++++++ ...cResponsePostTreatmentAggregatorHandler.kt | 8 ++- .../UserAccountDataSyncHandler.kt | 2 +- .../{ => handler/room}/ReadReceiptHandler.kt | 4 +- .../room}/RoomFullyReadHandler.kt | 2 +- .../{ => handler/room}/RoomSyncHandler.kt | 5 +- .../sync/{ => handler/room}/RoomTagHandler.kt | 2 +- .../room}/RoomTypingUsersHandler.kt | 2 +- .../parsing/RoomSyncAccountDataHandler.kt | 4 +- .../DefaultSessionAccountDataService.kt | 6 +- .../core/epoxy/profiles/ProfileMatrixItem.kt | 2 + .../profiles/ProfileMatrixItemWithPresence.kt | 44 ++++++++++++++ .../core/ui/views/PresenceStateImageView.kt | 54 +++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/list/RoomSummaryItem.kt | 6 ++ .../home/room/list/RoomSummaryItemFactory.kt | 2 + .../roomprofile/RoomProfileFragment.kt | 1 + .../members/RoomMemberListController.kt | 44 +++++++++++--- .../main/res/drawable/ic_presence_offline.xml | 24 ++++++++ .../main/res/drawable/ic_presence_online.xml | 24 ++++++++ .../main/res/layout/fragment_room_detail.xml | 34 ++++++++--- .../res/layout/item_profile_matrix_item.xml | 30 ++++++++-- vector/src/main/res/layout/item_room.xml | 15 +++++ .../view_stub_room_member_profile_header.xml | 31 ++++++++-- .../layout/view_stub_room_profile_header.xml | 32 +++++++--- vector/src/main/res/values/strings.xml | 3 + 66 files changed, 1031 insertions(+), 67 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler}/CryptoSyncHandler.kt (98%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler}/GroupSyncHandler.kt (98%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler}/SyncResponsePostTreatmentAggregatorHandler.kt (90%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler}/UserAccountDataSyncHandler.kt (99%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler/room}/ReadReceiptHandler.kt (96%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler/room}/RoomFullyReadHandler.kt (95%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler/room}/RoomSyncHandler.kt (98%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler/room}/RoomTagHandler.kt (95%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/{ => handler/room}/RoomTypingUsersHandler.kt (96%) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt create mode 100644 vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt create mode 100644 vector/src/main/res/drawable/ic_presence_offline.xml create mode 100644 vector/src/main/res/drawable/ic_presence_online.xml diff --git a/gradle.properties b/gradle.properties index 98d561815b..23538c5285 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,3 +23,6 @@ vector.debugPrivateData=false # httpLogLevel values: NONE, BASIC, HEADERS, BODY vector.httpLogLevel=BASIC +# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above +#vector.debugPrivateData=true +#vector.httpLogLevel=BODY \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml index b58829bb81..03d1ff69db 100644 --- a/library/ui-styles/src/main/res/values/colors.xml +++ b/library/ui-styles/src/main/res/values/colors.xml @@ -133,4 +133,8 @@ @color/palette_black_900 @color/palette_gray_400 + + + @color/palette_gray_100 + @color/palette_gray_450 diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml index f83953a527..d07e3c5297 100644 --- a/library/ui-styles/src/main/res/values/theme_dark.xml +++ b/library/ui-styles/src/main/res/values/theme_dark.xml @@ -42,6 +42,9 @@ @android:color/black #FFFFFFFF + + @color/vctr_presence_indicator_offline_dark + ?vctr_system ?vctr_content_quinary diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml index cd5e17d607..14ec372f28 100644 --- a/library/ui-styles/src/main/res/values/theme_light.xml +++ b/library/ui-styles/src/main/res/values/theme_light.xml @@ -42,6 +42,9 @@ #FFEEEEEE #FF000000 + + @color/vctr_presence_indicator_offline_light + ?vctr_system ?vctr_content_quinary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index bde68da9d7..67c7cfa383 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -75,6 +76,7 @@ interface Session : TermsService, EventService, ProfileService, + PresenceService, PushRuleService, PushersService, SyncStatusService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 96b44ce8c9..169f90dbca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.session.presence.model.PresenceContent import timber.log.Timber typealias Content = JsonDict @@ -305,3 +306,7 @@ fun Event.isReply(): Boolean { fun Event.isEdition(): Boolean { return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null } + +fun Event.getPresenceContent(): PresenceContent? { + return content.toModel() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt new file mode 100644 index 0000000000..2316eec3b7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.api.session.presence + +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum + +/** + * This interface defines methods for handling user presence information. + */ + +interface PresenceService { + + /** + * Update the presence status for the current user + * @param presence the new presence state + * @param message the status message to attach to this state + */ + suspend fun setMyPresence(presence: PresenceEnum, message: String? = null) + + /** + * Fetch the given user's presence state. + * @param userId the userId whose presence state to get. + */ + suspend fun fetchPresence(userId: String): GetPresenceResponse +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt index fba3a1dd71..87036dfdd5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt @@ -16,12 +16,15 @@ package org.matrix.android.sdk.api.session.room.model +import org.matrix.android.sdk.internal.session.presence.model.UserPresence + /** * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content */ data class RoomMemberSummary constructor( val membership: Membership, val userId: String, + val userPresence: UserPresence? = null, val displayName: String? = null, val avatarUrl: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index cae4775e71..d43eefbebe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.session.presence.model.UserPresence /** * This class holds some data of a room. @@ -38,6 +39,7 @@ data class RoomSummary( val joinRules: RoomJoinRules? = null, val isDirect: Boolean = false, val directUserId: String? = null, + val directUserPresence: UserPresence? = null, val joinedMembersCount: Int? = 0, val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index aa96ca5e1a..f082f19368 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 @@ -35,19 +35,21 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityField import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.query.process import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 17L + const val SESSION_STORE_SCHEMA_VERSION = 18L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -69,6 +71,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) + if (oldVersion <= 17) migrateTo18(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -338,4 +341,26 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("EventInsertEntity") ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java) } + + private fun migrateTo18(realm: DynamicRealm) { + Timber.d("Step 17 -> 18") + realm.schema.create("UserPresenceEntity") + ?.addField(UserPresenceEntityFields.USER_ID, String::class.java) + ?.addPrimaryKey(UserPresenceEntityFields.USER_ID) + ?.setRequired(UserPresenceEntityFields.USER_ID, true) + ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java) + ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java) + ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true) + ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java) + ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java) + ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true) + ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java) + + val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return + realm.schema.get("RoomSummaryEntity") + ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity) + + realm.schema.get("RoomMemberSummaryEntity") + ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt index 2365a39567..efd9b68011 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt @@ -18,12 +18,14 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence internal object RoomMemberSummaryMapper { fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary { return RoomMemberSummary( userId = roomMemberSummaryEntity.userId, + userPresence = roomMemberSummaryEntity.userPresenceEntity?.toUserPresence(), avatarUrl = roomMemberSummaryEntity.avatarUrl, displayName = roomMemberSummaryEntity.displayName, membership = roomMemberSummaryEntity.membership diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 0cf431c340..5900ef6b76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import javax.inject.Inject @@ -48,6 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa joinRules = roomSummaryEntity.joinRules, isDirect = roomSummaryEntity.isDirect, directUserId = roomSummaryEntity.directUserId, + directUserPresence = roomSummaryEntity.directUserPresence?.toUserPresence(), latestPreviewableEvent = latestEvent, joinedMembersCount = roomSummaryEntity.joinedMembersCount, invitedMembersCount = roomSummaryEntity.invitedMembersCount, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index 75771ff12c..a3a38aafca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -21,6 +21,7 @@ import io.realm.annotations.Index import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "", @Index var userId: String = "", @@ -40,6 +41,12 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = membershipStr = value.name } + var userPresenceEntity: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + + fun getBestName() = displayName?.takeIf { it.isNotBlank() } ?: userId fun toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 64dc08e827..88b8886936 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", @@ -204,6 +205,11 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var directUserPresence: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + var hasFailedSending: Boolean = false set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 19472e21d9..c090777972 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.annotations.RealmModule +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity /** * Realm module for Session @@ -64,6 +65,7 @@ import io.realm.annotations.RealmModule WellknownIntegrationManagerConfigEntity::class, RoomAccountDataEntity::class, SpaceChildSummaryEntity::class, - SpaceParentSummaryEntity::class + SpaceParentSummaryEntity::class, + UserPresenceEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt new file mode 100644 index 0000000000..64a2cdb6dc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model.presence + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.session.presence.model.UserPresence + +internal open class UserPresenceEntity(@PrimaryKey var userId: String = "", + var lastActiveAgo: Long? = null, + var statusMessage: String? = null, + var isCurrentlyActive: Boolean? = null, + var avatarUrl: String? = null +) : RealmObject() { + + var presence: PresenceEnum + get() { + return PresenceEnum.valueOf(presenceStr) + } + set(value) { + presenceStr = value.name + } + + private var presenceStr: String = PresenceEnum.UNAVAILABLE.name + + companion object +} + +internal fun UserPresenceEntity.toUserPresence() = + UserPresence( + lastActiveAgo, + statusMessage, + isCurrentlyActive, + presence) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt index a19a9cf725..1ea06b9dfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt @@ -21,6 +21,7 @@ import io.realm.RealmQuery import io.realm.kotlin.where import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery { val query = realm @@ -32,3 +33,13 @@ internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: Strin } return query } + +internal fun RoomMemberSummaryEntity.Companion.updateUserPresence(realm: Realm, userId: String, userPresenceEntity: UserPresenceEntity) { + realm.where() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId) + .isNull(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`) + .findAll() + .map { + it.userPresenceEntity = userPresenceEntity + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 5294f849af..d1b05a4932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -23,6 +23,7 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where 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.model.presence.UserPresenceEntity internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { val query = realm.where() @@ -67,3 +68,11 @@ internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String): .findAll() .isNotEmpty() } + +internal fun RoomSummaryEntity.Companion.updateDirectUserPresence(realm: Realm, directUserId: String, userPresenceEntity: UserPresenceEntity) { + RoomSummaryEntity.where(realm) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .equalTo(RoomSummaryEntityFields.DIRECT_USER_ID, directUserId) + .findFirst() + ?.directUserPresence = userPresenceEntity +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt new file mode 100644 index 0000000000..22790b6f4f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields + +internal fun UserPresenceEntity.Companion.where(realm: Realm, userId: String): RealmQuery { + return realm + .where() + .equalTo(UserPresenceEntityFields.USER_ID, userId) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index d41bf8a702..5d3579cc8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -45,6 +45,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -127,6 +128,7 @@ internal class DefaultSession @Inject constructor( private val callSignalingService: Lazy, private val spaceService: Lazy, private val openIdService: Lazy, + private val presenceService: Lazy, @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy ) : Session, @@ -145,6 +147,7 @@ internal class DefaultSession @Inject constructor( SecureStorageService by secureStorageService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), ProfileService by profileService.get(), + PresenceService by presenceService.get(), AccountService by accountService.get() { override val sharedSecretStorageService: SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 71031a4614..568703b9cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule import org.matrix.android.sdk.internal.session.media.MediaModule import org.matrix.android.sdk.internal.session.openid.OpenIdModule +import org.matrix.android.sdk.internal.session.presence.di.PresenceModule import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker import org.matrix.android.sdk.internal.session.pushers.PushersModule @@ -94,7 +95,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule CallModule::class, SearchModule::class, ThirdPartyModule::class, - SpaceModule::class + SpaceModule::class, + PresenceModule::class ] ) @SessionScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt new file mode 100644 index 0000000000..53d0d5e963 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence + +import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path + +internal interface PresenceAPI { + + /** + * Set the presence status of the current user + * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-presence-userid-status + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun setPresence(@Path("userId") userId: String, + @Body body: SetPresenceBody) + + /** + * Get the given user's presence state. + * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-presence-userid-status + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun getPresence(@Path("userId") userId: String): GetPresenceResponse +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt new file mode 100644 index 0000000000..6b2ee76046 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.di + +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.service.DefaultPresenceService +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultGetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultSetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import retrofit2.Retrofit + +@Module +internal abstract class PresenceModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesPresenceAPI(retrofit: Retrofit): PresenceAPI { + return retrofit.create(PresenceAPI::class.java) + } + } + + @Binds + abstract fun bindPresenceService(service: DefaultPresenceService): PresenceService + + @Binds + abstract fun bindSetPresenceTask(task: DefaultSetPresenceTask): SetPresenceTask + + @Binds + abstract fun bindGetPresenceTask(task: DefaultGetPresenceTask): GetPresenceTask +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt new file mode 100644 index 0000000000..707f0a055f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetPresenceResponse( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "last_active_ago") + val lastActiveAgo: Long? = null, + @Json(name = "status_msg") + val message: String? = null, + @Json(name = "currently_active") + val isCurrentlyActive: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt new file mode 100644 index 0000000000..86a8b31a91 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the EventType.PRESENCE event content + */ +@JsonClass(generateAdapter = true) +data class PresenceContent( + @Json(name = "presence") val presence: PresenceEnum, + @Json(name = "last_active_ago") val lastActiveAgo: Long? = null, + @Json(name = "status_msg") val statusMessage: String? = null, + @Json(name = "currently_active") val isCurrentlyActive: Boolean = false, + @Json(name = "avatar_url") val avatarUrl: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt new file mode 100644 index 0000000000..c6dd307e30 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Annotate enums with @JsonClass(generateAdapter = false) to prevent + * them from being removed/obfuscated from your code by R8/ProGuard. + */ + +@JsonClass(generateAdapter = false) +enum class PresenceEnum(val value: String) { + @Json(name = "online") + ONLINE("online"), + + @Json(name = "offline") + OFFLINE("offline"), + + @Json(name = "unavailable") + UNAVAILABLE("unavailable"); + + companion object { + fun from(s: String): PresenceEnum? = values().find { it.value == s } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt new file mode 100644 index 0000000000..0ddb140908 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SetPresenceBody( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "status_msg") + val message: String? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt new file mode 100644 index 0000000000..f7cd3b3834 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.internal.session.presence.model + +data class UserPresence( + val lastActiveAgo: Long? = null, + val statusMessage: String? = null, + val isCurrentlyActive: Boolean? = null, + val presence: PresenceEnum = PresenceEnum.OFFLINE +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt new file mode 100644 index 0000000000..cb00192ef3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service + +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import javax.inject.Inject + +internal class DefaultPresenceService @Inject constructor(@UserId private val userId: String, + private val setPresenceTask: SetPresenceTask, + private val getPresenceTask: GetPresenceTask) : PresenceService { + + override suspend fun setMyPresence(presence: PresenceEnum, message: String?) { + setPresenceTask.execute(SetPresenceTask.Params(userId, presence, message)) + } + + override suspend fun fetchPresence(userId: String) = getPresenceTask.execute(GetPresenceTask.Params(userId)) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt new file mode 100644 index 0000000000..bb628dbab4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class GetPresenceTask : Task { + data class Params( + val userId: String + ) +} + +internal class DefaultGetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : GetPresenceTask() { + override suspend fun execute(params: Params): GetPresenceResponse { + return executeRequest(globalErrorReceiver) { + presenceAPI.getPresence(params.userId) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt new file mode 100644 index 0000000000..011ec1e41a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class SetPresenceTask : Task { + data class Params( + val userId: String, + val presence: PresenceEnum, + val message: String? + ) +} + +internal class DefaultSetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : SetPresenceTask() { + + override suspend fun execute(params: Params): Any { + return executeRequest(globalErrorReceiver) { + val setPresenceBody = SetPresenceBody(params.presence, params.message) + presenceAPI.setPresence(params.userId, setPresenceBody) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt index f78b5d7992..d6a04de5e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -18,10 +18,11 @@ package org.matrix.android.sdk.internal.session.room.membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal object RoomMemberEntityFactory { - fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity { + fun create(roomId: String, userId: String, roomMember: RoomMemberContent, presence: UserPresenceEntity?): RoomMemberSummaryEntity { val primaryKey = "${roomId}_$userId" return RoomMemberSummaryEntity( primaryKey = primaryKey, @@ -31,6 +32,7 @@ internal object RoomMemberEntityFactory { avatarUrl = roomMember.avatarUrl ).apply { membership = roomMember.membership + userPresenceEntity = presence } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 7528f80cc2..25c124bd6b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -20,6 +20,9 @@ import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator @@ -47,7 +50,13 @@ internal class RoomMemberEventHandler @Inject constructor( if (roomMember == null) { return false } - val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) + val roomMemberEntity = RoomMemberEntityFactory.create( + roomId, + userId, + roomMember, + // When an update is happening, insertOrUpdate replace existing values with null if they are not provided, + // but we want to preserve presence record value and not replace it with null + getExistingPresenceState(realm, roomId, userId)) realm.insertOrUpdate(roomMemberEntity) if (roomMember.membership.isActive()) { val userEntity = UserEntityFactory.create(userId, roomMember) @@ -60,7 +69,15 @@ internal class RoomMemberEventHandler @Inject constructor( if (mxId != null && mxId != myUserId) { aggregator?.directChatsToCheck?.put(roomId, mxId) } - return true } + + /** + * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity + * by NULL value. + */ + + private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? { + return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index eb48958afb..d519a4a96a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -27,13 +27,13 @@ import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import timber.log.Timber import javax.inject.Inject import kotlin.collections.set diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index c0e428ec85..0c917448cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -42,7 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 8de36d0427..47e8f7e3a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -37,7 +37,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultTimelineService @AssistedInject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 8c7401ab47..b253e3db82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -33,6 +33,12 @@ import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber @@ -55,7 +61,9 @@ internal class SyncResponseHandler @Inject constructor( private val cryptoService: DefaultCryptoService, private val tokenStore: SyncTokenStore, private val processEventForPushTask: ProcessEventForPushTask, - private val pushRuleService: PushRuleService) { + private val pushRuleService: PushRuleService, + private val presenceSyncHandler: PresenceSyncHandler + ) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -118,6 +126,13 @@ internal class SyncResponseHandler @Inject constructor( }.also { Timber.v("Finish handling accountData in $it ms") } + + measureTimeMillis { + Timber.v("Handle Presence") + presenceSyncHandler.handle(realm,syncResponse.presence) + }.also { + Timber.v("Finish handling Presence in $it ms") + } tokenStore.saveToken(realm, syncResponse.nextBatch) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index cc4ccc2e46..e2f9ba2f29 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index cec5689a82..c5ec34176c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt index 2b054e578f..552462e25e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import io.realm.Realm import org.matrix.android.sdk.api.session.initsync.InitSyncStep diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt new file mode 100644 index 0000000000..b395b522c8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync.handler + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.getPresenceContent +import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse +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.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.updateDirectUserPresence +import org.matrix.android.sdk.internal.database.query.updateUserPresence +import javax.inject.Inject + +internal class PresenceSyncHandler @Inject constructor() { + + fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) { + presenceSyncResponse?.events?.filter { event -> + event.type == EventType.PRESENCE + }?.forEach { event -> + val content = event.getPresenceContent() ?: return@forEach + val userId = event.senderId ?: return@forEach + val userPresenceEntity = UserPresenceEntity( + userId = userId, + lastActiveAgo = content.lastActiveAgo, + statusMessage = content.statusMessage, + isCurrentlyActive = content.isCurrentlyActive, + avatarUrl = content.avatarUrl + ).also { + it.presence = content.presence + } + + storePresenceToDB(realm, userPresenceEntity) + } + } + + /** + * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly + */ + private fun storePresenceToDB(realm: Realm, userPresenceEntity: UserPresenceEntity) = + realm.copyToRealmOrUpdate(userPresenceEntity)?.apply { + RoomSummaryEntity.updateDirectUserPresence(realm, userPresenceEntity.userId, this) + RoomMemberSummaryEntity.updateUserPresence(realm, userPresenceEntity.userId, this) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index db1100d76c..39ae94c88a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright 2020 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt similarity index 99% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index a9926c70aa..3e38cd7839 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import com.zhuinden.monarchy.Monarchy import io.realm.Realm diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt index fc1a2c3870..025ee329f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.EventType @@ -23,6 +23,8 @@ import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.query.createUnmanaged import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt index 3d0db212c2..b5c8a099d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 52e5b6b58d..8c4af81c99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import io.realm.kotlin.createObject @@ -62,6 +62,9 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent +import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator +import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler import org.matrix.android.sdk.internal.util.computeBestChunkSize import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt index 8997435be4..55b15624bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt index 1433d89143..63db13a5b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.sender.SenderInfo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index 6ca008c5b1..5e7bde87e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -28,8 +28,8 @@ import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityField import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.session.room.read.FullyReadContent -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler -import org.matrix.android.sdk.internal.session.sync.RoomTagHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler import javax.inject.Inject internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index e5c338d511..2e9fe8319c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -19,13 +19,13 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource -import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt index 6eb06722b9..e6451b34ef 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt @@ -23,6 +23,7 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.ui.views.PresenceStateImageView import im.vector.app.core.ui.views.ShieldImageView @EpoxyModelClass(layout = R.layout.item_profile_matrix_item) @@ -31,6 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem(R.id.matrixItemTitle) val subtitleView by bind(R.id.matrixItemSubtitle) + val presenceImageView by bind(R.id.matrixItemPresenceImageView) val avatarImageView by bind(R.id.matrixItemAvatar) val avatarDecorationImageView by bind(R.id.matrixItemAvatarDecoration) val editableView by bind(R.id.matrixItemEditable) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt new file mode 100644 index 0000000000..022d3dd152 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.app.core.epoxy.profiles + +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import org.matrix.android.sdk.internal.session.presence.model.UserPresence + +@EpoxyModelClass(layout = R.layout.item_profile_matrix_item) +abstract class ProfileMatrixItemWithPresence : BaseProfileMatrixItem() { + + @EpoxyAttribute var powerLevelLabel: CharSequence? = null + @EpoxyAttribute var userPresence: UserPresence? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.powerLabel.setTextOrHide(powerLevelLabel) + holder.presenceImageView.render(userPresence = userPresence) + holder.editableView.isVisible = false + } + + class Holder : ProfileMatrixItem.Holder() { + val powerLabel by bind(R.id.matrixItemPowerLevelLabel) + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt new file mode 100644 index 0000000000..3e1b40b3c7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.ui.views + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.view.isVisible +import im.vector.app.R +import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.session.presence.model.UserPresence + +/** + * Custom ImageView to dynamically render Presence state in multiple screens + */ +class PresenceStateImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatImageView(context, attrs, defStyleAttr) { + + fun render(showPresence: Boolean = true, userPresence: UserPresence?) { + isVisible = showPresence && userPresence != null + + when (userPresence?.presence) { + PresenceEnum.ONLINE -> { + setImageResource(R.drawable.ic_presence_online) + contentDescription = context.getString(R.string.a11y_presence_online) + } + PresenceEnum.UNAVAILABLE -> { + setImageResource(R.drawable.ic_presence_offline) + contentDescription = context.getString(R.string.a11y_presence_unavailable) + } + PresenceEnum.OFFLINE -> { + setImageResource(R.drawable.ic_presence_offline) + contentDescription = context.getString(R.string.a11y_presence_offline) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index b3fe6fcbf4..2307bff984 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1430,9 +1430,9 @@ class RoomDetailFragment @Inject constructor( views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN views.roomToolbarTitleView.text = roomSummary.displayName avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView) - renderSubTitle(typingMessage, roomSummary.topic) views.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) + views.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) } } 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 e4abbeb1ff..dda6ffefde 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 @@ -32,12 +32,14 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.ui.views.PresenceStateImageView import im.vector.app.core.ui.views.ShieldImageView import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.session.presence.model.UserPresence @EpoxyModelClass(layout = R.layout.item_room) abstract class RoomSummaryItem : VectorEpoxyModel() { @@ -53,6 +55,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence @EpoxyAttribute lateinit var lastEventTime: CharSequence @EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null + @EpoxyAttribute var userPresence: UserPresence? = null + @EpoxyAttribute var showPresence: Boolean = false @EpoxyAttribute var izPublic: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var hasUnreadMessage: Boolean = false @@ -83,6 +87,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { renderSelection(holder, showSelected) holder.typingView.setTextOrHide(typingMessage) holder.lastEventView.isInvisible = holder.typingView.isVisible + holder.roomAvatarPresenceImageView.render(showPresence, userPresence) } override fun unbind(holder: Holder) { @@ -117,6 +122,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { val roomAvatarDecorationImageView by bind(R.id.roomAvatarDecorationImageView) val roomAvatarPublicDecorationImageView by bind(R.id.roomAvatarPublicDecorationImageView) val roomAvatarFailSendingImageView by bind(R.id.roomAvatarFailSendingImageView) + val roomAvatarPresenceImageView by bind(R.id.roomAvatarPresenceImageView) val rootView by bind(R.id.itemRoomLayout) } } 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 c06ab33a54..fdb7d3a323 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 @@ -124,6 +124,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor // We do not display shield in the room list anymore // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .izPublic(roomSummary.isPublic) + .showPresence(roomSummary.isDirect) + .userPresence(roomSummary.directUserPresence) .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) .typingMessage(typingMessage) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 4b37d038b5..e24b558ff0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -219,6 +219,7 @@ class RoomProfileFragment @Inject constructor( avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView) headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel) views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) + headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence) } } roomProfileController.setData(state) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 8399c9e238..9a2ee04fdc 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -17,23 +17,29 @@ package im.vector.app.features.roomprofile.members import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.epoxy.profiles.profileMatrixItem +import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPresence import im.vector.app.core.extensions.join +import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.AvatarRenderer +import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.internal.session.presence.model.UserPresence import javax.inject.Inject class RoomMemberListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, private val roomMemberSummaryFilter: RoomMemberSummaryFilter ) : TypedEpoxyController() { @@ -84,17 +90,10 @@ class RoomMemberListController @Inject constructor( buildProfileSection( stringProvider.getString(powerLevelCategory.titleRes) ) + filteredRoomMemberList.join( each = { _, roomMember -> - profileMatrixItem { - id(roomMember.userId) - matrixItem(roomMember.toMatrixItem()) - avatarRenderer(host.avatarRenderer) - userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) - clickListener { - host.callback?.onRoomMemberClicked(roomMember) - } - } + buildPresence(roomMember, powerLevelCategory, host, data, roomMember.userPresence) }, between = { _, roomMemberBefore -> dividerItem { @@ -123,6 +122,33 @@ class RoomMemberListController @Inject constructor( } } + private fun buildPresence(roomMember: RoomMemberSummary, + powerLevelCategory: RoomMemberListCategories, + host: RoomMemberListController, + data: RoomMemberListViewState, + userPresence: UserPresence? + ) { + val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) + + profileMatrixItemWithPresence { + id(roomMember.userId) + matrixItem(roomMember.toMatrixItem()) + avatarRenderer(host.avatarRenderer) + userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId)) + clickListener { + host.callback?.onRoomMemberClicked(roomMember) + } + userPresence(userPresence) + powerLevelLabel( + span { + span(powerLabel) { + textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + } + } + ) + } + } + private fun buildThreePidInvites(data: RoomMemberListViewState) { val host = this data.threePidInvites() diff --git a/vector/src/main/res/drawable/ic_presence_offline.xml b/vector/src/main/res/drawable/ic_presence_offline.xml new file mode 100644 index 0000000000..3f0dc251ce --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_offline.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_presence_online.xml b/vector/src/main/res/drawable/ic_presence_online.xml new file mode 100644 index 0000000000..2184f359b2 --- /dev/null +++ b/vector/src/main/res/drawable/ic_presence_online.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 6c58f40bad..0115c9a66a 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -45,19 +45,35 @@ + + + + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + tools:text="@sample/rooms.json/data/topic" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml index 636113752b..cea2f62968 100644 --- a/vector/src/main/res/layout/item_profile_matrix_item.xml +++ b/vector/src/main/res/layout/item_profile_matrix_item.xml @@ -1,4 +1,5 @@ + + + + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml index e7d930b070..0aff5e7a5a 100644 --- a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml @@ -30,14 +30,33 @@ app:layout_constraintVertical_chainStyle="spread_inside" tools:src="@sample/user_round_avatars" /> - + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/view_stub_room_profile_header.xml b/vector/src/main/res/layout/view_stub_room_profile_header.xml index 7308e8a207..cca97cff62 100644 --- a/vector/src/main/res/layout/view_stub_room_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_profile_header.xml @@ -20,25 +20,43 @@ app:layout_constraintTop_toTopOf="parent" tools:src="@sample/room_round_avatars" /> - + tools:ignore="MissingConstraints" + tools:src="@drawable/ic_presence_offline" + tools:visibility="visible" /> + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index bb6f73812f..cc16b61aaa 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3404,6 +3404,9 @@ View read receipts Public room Public space + Online + Offline + Unavailable Dev Tools Explore Room State From e4c3457f37b20e57dd858faab555e91bc13b890c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 6 Oct 2021 19:08:27 +0300 Subject: [PATCH 020/172] Add public room indicator to RoomDetailFragment & RoomProfileFragment --- .../home/room/detail/RoomDetailFragment.kt | 1 + .../features/roomprofile/RoomProfileFragment.kt | 1 + .../src/main/res/layout/fragment_room_detail.xml | 16 +++++++++++++++- .../res/layout/view_stub_room_profile_header.xml | 15 +++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 2307bff984..c888c509c1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1433,6 +1433,7 @@ class RoomDetailFragment @Inject constructor( renderSubTitle(typingMessage, roomSummary.topic) views.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) views.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) + views.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index e24b558ff0..e07746af85 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -220,6 +220,7 @@ class RoomProfileFragment @Inject constructor( headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel) views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence) + headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect } } roomProfileController.setData(state) diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 0115c9a66a..ccebeaba95 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -53,7 +53,6 @@ app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView" app:layout_constraintTop_toTopOf="@+id/roomToolbarTitleView" /> - + + + + Date: Wed, 6 Oct 2021 20:19:21 +0300 Subject: [PATCH 021/172] ktlintFormat fixes --- .../sdk/internal/session/room/read/SetReadMarkersTask.kt | 4 ++-- .../sdk/internal/session/sync/SyncResponseHandler.kt | 9 +++++---- .../user/accountdata/DefaultSessionAccountDataService.kt | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index d519a4a96a..64f1cc34f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -27,13 +27,13 @@ import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import timber.log.Timber import javax.inject.Inject import kotlin.collections.set diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index b253e3db82..335f619623 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -63,7 +63,7 @@ internal class SyncResponseHandler @Inject constructor( private val processEventForPushTask: ProcessEventForPushTask, private val pushRuleService: PushRuleService, private val presenceSyncHandler: PresenceSyncHandler - ) { +) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -128,8 +128,8 @@ internal class SyncResponseHandler @Inject constructor( } measureTimeMillis { - Timber.v("Handle Presence") - presenceSyncHandler.handle(realm,syncResponse.presence) + Timber.v("Handle Presence") + presenceSyncHandler.handle(realm, syncResponse.presence) }.also { Timber.v("Finish handling Presence in $it ms") } @@ -160,7 +160,8 @@ internal class SyncResponseHandler @Inject constructor( private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) { roomsSyncResponse.invite.keys.forEach { roomId -> sessionListeners.dispatch { session, listener -> - listener.onNewInvitedRoom(session, roomId) } + listener.onNewInvitedRoom(session, roomId) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index 2e9fe8319c..ddcac475ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -19,13 +19,13 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback From 362ebcbe42cee1786184d739546e6161236875b2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 7 Oct 2021 11:11:44 +0200 Subject: [PATCH 022/172] Revert "Mavericks 2: remove matrix-sdk-android-flow as it will be easier when entirely migrating to flow" This reverts commit d9b02a20d84840a8549c48ee2dff8188c3ce5dcf. --- matrix-sdk-android-flow/.gitignore | 1 + matrix-sdk-android-flow/build.gradle | 49 +++++ matrix-sdk-android-flow/consumer-rules.pro | 0 matrix-sdk-android-flow/proguard-rules.pro | 21 ++ .../sdk/flow/ExampleInstrumentedTest.kt | 40 ++++ .../src/main/AndroidManifest.xml | 5 + .../org/matrix/android/sdk/flow/FlowRoom.kt | 83 +++++++ .../matrix/android/sdk/flow/FlowSession.kt | 138 ++++++++++++ .../matrix/android/sdk/flow}/OptionalFlow.kt | 2 +- .../android/sdk/flow/ExampleUnitTest.kt | 33 +++ settings.gradle | 1 + vector/build.gradle | 1 + .../app/core/platform/VectorViewModel.kt | 32 +++ .../quads/SharedSecureStorageViewModel.kt | 8 +- .../features/devtools/RoomDevToolViewModel.kt | 7 +- .../discovery/DiscoverySettingsViewModel.kt | 7 +- .../features/home/HomeActivityViewModel.kt | 9 +- .../app/features/home/HomeDetailViewModel.kt | 17 +- .../UnknownDeviceDetectorSharedViewModel.kt | 10 +- .../room/breadcrumbs/BreadcrumbsViewModel.kt | 8 +- .../home/room/detail/RoomDetailViewModel.kt | 26 ++- .../action/MessageActionsViewModel.kt | 12 +- .../home/room/list/RoomListViewModel.kt | 9 +- .../app/features/invite/InvitesAcceptor.kt | 7 +- .../login2/created/AccountCreatedViewModel.kt | 10 +- .../powerlevel/PowerLevelsFlowFactory.kt | 11 +- .../room/RequireActiveMembershipViewModel.kt | 8 +- .../roomdirectory/RoomDirectoryViewModel.kt | 10 +- .../roompreview/RoomPreviewViewModel.kt | 11 +- .../RoomMemberProfileViewModel.kt | 14 +- .../devices/DeviceListBottomSheetViewModel.kt | 10 +- .../roomprofile/RoomProfileViewModel.kt | 26 +-- .../roomprofile/alias/RoomAliasViewModel.kt | 15 +- .../banned/RoomBannedMemberListViewModel.kt | 12 +- .../members/RoomMemberListViewModel.kt | 20 +- .../RoomNotificationSettingsViewModel.kt | 14 +- .../permissions/RoomPermissionsViewModel.kt | 7 +- .../settings/RoomSettingsViewModel.kt | 25 ++- .../uploads/RoomUploadsViewModel.kt | 7 +- .../settings/SecretsSynchronisationInfo.kt | 11 +- .../settings/VectorSettingsGeneralFragment.kt | 12 +- .../CrossSigningSettingsViewModel.kt | 6 +- ...iceVerificationInfoBottomSheetViewModel.kt | 14 +- .../settings/devices/DevicesViewModel.kt | 18 +- .../settings/devtools/AccountDataViewModel.kt | 6 +- .../settings/ignored/IgnoredUsersViewModel.kt | 6 +- .../threepids/ThreePidsSettingsViewModel.kt | 10 +- .../features/share/IncomingShareViewModel.kt | 8 +- .../app/features/spaces/SpaceMenuViewModel.kt | 25 ++- .../features/spaces/SpacesListViewModel.kt | 28 +-- .../spaces/explore/SpaceDirectoryViewModel.kt | 11 +- .../leave/SpaceLeaveAdvancedViewModel.kt | 7 +- .../userdirectory/UserListViewModel.kt | 204 ++++++++++-------- .../app/features/widgets/WidgetViewModel.kt | 14 +- .../RoomWidgetPermissionViewModel.kt | 7 +- .../signout/ServerBackupStatusViewModel.kt | 18 +- .../workers/signout/SignoutCheckViewModel.kt | 6 +- 57 files changed, 767 insertions(+), 370 deletions(-) create mode 100644 matrix-sdk-android-flow/.gitignore create mode 100644 matrix-sdk-android-flow/build.gradle create mode 100644 matrix-sdk-android-flow/consumer-rules.pro create mode 100644 matrix-sdk-android-flow/proguard-rules.pro create mode 100644 matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt create mode 100644 matrix-sdk-android-flow/src/main/AndroidManifest.xml create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt rename {vector/src/main/java/im/vector/app/core/utils => matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow}/OptionalFlow.kt (96%) create mode 100644 matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt diff --git a/matrix-sdk-android-flow/.gitignore b/matrix-sdk-android-flow/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/matrix-sdk-android-flow/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle new file mode 100644 index 0000000000..4aecec169b --- /dev/null +++ b/matrix-sdk-android-flow/build.gradle @@ -0,0 +1,49 @@ + +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 31 + + defaultConfig { + minSdk 21 + targetSdk 31 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation project(":matrix-sdk-android") + implementation libs.androidx.appCompat + + implementation libs.jetbrains.kotlinStdlibJdk7 + implementation libs.jetbrains.coroutinesCore + implementation libs.jetbrains.coroutinesAndroid + implementation libs.androidx.lifecycleLivedata + + // Paging + implementation libs.androidx.pagingRuntimeKtx + + // Logging + implementation libs.jakewharton.timber + +} \ No newline at end of file diff --git a/matrix-sdk-android-flow/consumer-rules.pro b/matrix-sdk-android-flow/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/matrix-sdk-android-flow/proguard-rules.pro b/matrix-sdk-android-flow/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/matrix-sdk-android-flow/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..41799955a4 --- /dev/null +++ b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("org.matrix.android.sdk.flow.test", appContext.packageName) + } +} diff --git a/matrix-sdk-android-flow/src/main/AndroidManifest.xml b/matrix-sdk-android-flow/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..2392c0bfcb --- /dev/null +++ b/matrix-sdk-android-flow/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..a3e476ce08 --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.lifecycle.asFlow +import kotlinx.coroutines.flow.Flow +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +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 +import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState +import org.matrix.android.sdk.api.session.room.send.UserDraft +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.util.Optional + +class FlowRoom(private val room: Room) { + + fun liveRoomSummary(): Flow> { + return room.getRoomSummaryLive().asFlow() + } + + fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { + return room.getRoomMembersLive(queryParams).asFlow() + } + + fun liveAnnotationSummary(eventId: String): Flow> { + return room.getEventAnnotationsSummaryLive(eventId).asFlow() + } + + fun liveTimelineEvent(eventId: String): Flow> { + return room.getTimeLineEventLive(eventId).asFlow() + } + + fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { + return room.getStateEventLive(eventType, stateKey).asFlow() + } + + fun liveStateEvents(eventTypes: Set): Flow> { + return room.getStateEventsLive(eventTypes).asFlow() + } + + fun liveReadMarker(): Flow> { + return room.getReadMarkerLive().asFlow() + } + + fun liveReadReceipt(): Flow> { + return room.getMyReadReceiptLive().asFlow() + } + + fun liveEventReadReceipts(eventId: String): Flow> { + return room.getEventReadReceiptsLive(eventId).asFlow() + } + + fun liveDraft(): Flow> { + return room.getDraftLive().asFlow() + } + + fun liveNotificationState(): Flow { + return room.getLiveRoomNotificationState().asFlow() + } +} + +fun Room.flow(): FlowRoom { + return FlowRoom(this) +} 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 new file mode 100644 index 0000000000..affcd4a65d --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import androidx.lifecycle.asFlow +import androidx.paging.PagedList +import kotlinx.coroutines.flow.Flow +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo +import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams +import org.matrix.android.sdk.api.session.group.model.GroupSummary +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +import org.matrix.android.sdk.api.session.sync.SyncState +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.session.widgets.model.Widget +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo + +class RxFlow(private val session: Session) { + + fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { + return session.getRoomSummariesLive(queryParams).asFlow() + } + + fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { + return session.getGroupSummariesLive(queryParams).asFlow() + } + + fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow> { + return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() + } + + fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { + return session.getBreadcrumbsLive(queryParams).asFlow() + } + + fun liveMyDevicesInfo(): Flow> { + return session.cryptoService().getLiveMyDevicesInfo().asFlow() + } + + fun liveSyncState(): Flow { + return session.getSyncStateLive().asFlow() + } + + fun livePushers(): Flow> { + return session.getPushersLive().asFlow() + } + + fun liveUser(userId: String): Flow> { + return session.getUserLive(userId).asFlow() + } + + fun liveRoomMember(userId: String, roomId: String): Flow> { + return session.getRoomMemberLive(userId, roomId).asFlow() + } + + fun liveUsers(): Flow> { + return session.getUsersLive().asFlow() + } + + fun liveIgnoredUsers(): Flow> { + return session.getIgnoredUsersLive().asFlow() + } + + fun livePagedUsers(filter: String? = null, excludedUserIds: Set? = null): Flow> { + return session.getPagedUsersLive(filter, excludedUserIds).asFlow() + } + + fun liveThreePIds(refreshData: Boolean): Flow> { + return session.getThreePidsLive(refreshData).asFlow() + } + + fun livePendingThreePIds(): Flow> { + return session.getPendingThreePidsLive().asFlow() + } + + fun liveUserCryptoDevices(userId: String): Flow> { + return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() + } + + fun liveCrossSigningInfo(userId: String): Flow> { + return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() + } + + fun liveCrossSigningPrivateKeys(): Flow> { + return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() + } + + fun liveUserAccountData(types: Set): Flow> { + return session.accountDataService().getLiveUserAccountDataEvents(types).asFlow() + } + + fun liveRoomAccountData(types: Set): Flow> { + return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() + } + + fun liveRoomWidgets( + roomId: String, + widgetId: QueryStringValue, + widgetTypes: Set? = null, + excludedTypes: Set? = null + ): Flow> { + return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asFlow() + } + + fun liveRoomChangeMembershipState(): Flow> { + return session.getChangeMembershipsLive().asFlow() + } +} + +fun Session.flow(): RxFlow { + return RxFlow(this) +} diff --git a/vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt similarity index 96% rename from vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt rename to matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt index 8e37ccfe8d..a9f062f379 100644 --- a/vector/src/main/java/im/vector/app/core/utils/OptionalFlow.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/OptionalFlow.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.app.core.utils +package org.matrix.android.sdk.flow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter diff --git a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt new file mode 100644 index 0000000000..bd627b2041 --- /dev/null +++ b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle b/settings.gradle index b88ea99b05..e3b84b4733 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,4 @@ include ':diff-match-patch' include ':attachment-viewer' include ':multipicker' include ':library:ui-styles' +include ':matrix-sdk-android-flow' diff --git a/vector/build.gradle b/vector/build.gradle index a9c6a407d8..76bc71b2d4 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -323,6 +323,7 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") + implementation project(":matrix-sdk-android-flow") implementation project(":diff-match-patch") implementation project(":multipicker") implementation project(":attachment-viewer") diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 01700049c1..1a77a00fab 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -16,10 +16,16 @@ package im.vector.app.core.platform +import com.airbnb.mvrx.Async import com.airbnb.mvrx.BaseMvRxViewModel +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.Success import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.PublishDataSource +import io.reactivex.Observable +import io.reactivex.Single abstract class VectorViewModel(initialState: S) : BaseMvRxViewModel(initialState) { @@ -32,5 +38,31 @@ abstract class VectorViewModel() val viewEvents: DataSource = _viewEvents + /** + * This method does the same thing as the execute function, but it doesn't subscribe to the stream + * so you can use this in a switchMap or a flatMap + */ + // False positive + @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") + fun Single.toAsync(stateReducer: S.(Async) -> S): Single> { + setState { stateReducer(Loading()) } + return map { Success(it) as Async } + .onErrorReturn { Fail(it) } + .doOnSuccess { setState { stateReducer(it) } } + } + + /** + * This method does the same thing as the execute function, but it doesn't subscribe to the stream + * so you can use this in a switchMap or a flatMap + */ + // False positive + @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") + fun Observable.toAsync(stateReducer: S.(Async) -> S): Observable> { + setState { stateReducer(Loading()) } + return map { Success(it) as Async } + .onErrorReturn { Fail(it) } + .doOnNext { setState { stateReducer(it) } } + } + abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 2a0ce1da2b..151b73ff32 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.crypto.quads -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -44,7 +43,9 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding +import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream @@ -115,9 +116,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } } - session.cryptoService() - .getLiveCryptoDeviceInfo(session.myUserId) - .asFlow() + session.flow() + .liveUserCryptoDevices(session.myUserId) .distinctUntilChanged() .execute { copy( diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index 0aed35fd55..4bab65fd5d 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.devtools -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail @@ -41,7 +40,9 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.rx.rx class RoomDevToolViewModel @AssistedInject constructor( @Assisted val initialState: RoomDevToolViewState, @@ -69,8 +70,8 @@ class RoomDevToolViewModel @AssistedInject constructor( init { session.getRoom(initialState.roomId) - ?.getStateEventsLive(emptySet()) - ?.asFlow() + ?.flow() + ?.liveStateEvents(emptySet()) ?.execute { async -> copy(stateEvents = async) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index ddbe2e151a..b248bcd065 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.discovery -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -37,6 +36,7 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.flow.flow class DiscoverySettingsViewModel @AssistedInject constructor( @Assisted initialState: DiscoverySettingsState, @@ -85,9 +85,8 @@ class DiscoverySettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session - .getThreePidsLive(true) - .asFlow() + session.flow() + .liveThreePIds(true) .onEach { retrieveBinding(it) } 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 52ea898367..fa3df1ca97 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 @@ -47,9 +47,12 @@ import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.util.awaitCallback +import org.matrix.android.sdk.rx.asObservable +import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -101,10 +104,8 @@ class HomeActivityViewModel @AssistedInject constructor( .crossSigningService().allPrivateKeysKnown() safeActiveSession - .cryptoService() - .crossSigningService() - .getLiveCrossSigningKeys(safeActiveSession.myUserId) - .asFlow() + .flow() + .liveCrossSigningInfo(safeActiveSession.myUserId) .onEach { val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 85a0716a77..316c1791fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import timber.log.Timber import java.util.concurrent.TimeUnit @@ -95,13 +96,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho updateShowDialPadTab() observeDataStore() callManager.addProtocolsCheckerListener(this) - session.getUserLive(session.myUserId) - .asFlow() - .execute { - copy( - myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() - ) - } + session.flow().liveUser(session.myUserId).execute { + copy( + myMatrixItem = it.invoke()?.getOrNull()?.toMatrixItem() + ) + } } private fun observeDataStore() { @@ -184,8 +183,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeSyncState() { - session.getSyncStateLive() - .asFlow() + session.flow() + .liveSyncState() .setOnEach { syncState -> copy(syncState = syncState) } diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index ef22875eaa..143f843954 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -42,6 +41,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import timber.log.Timber @@ -99,9 +99,9 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) combine( - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow(), - session.cryptoService().getLiveMyDevicesInfo().asFlow(), - session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningPrivateKeys() ) { cryptoList, infoList, pInfo -> // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") @@ -132,7 +132,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted ) } - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() + session.flow().liveUserCryptoDevices(session.myUserId) .distinctUntilChanged() .sample(5_000) .onEach { diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index 0e838db525..f32f132fe9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.breadcrumbs -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -26,10 +25,13 @@ import dagger.assisted.AssistedFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import io.reactivex.schedulers.Schedulers import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.rx.rx class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, private val session: Session) @@ -60,11 +62,11 @@ class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: B // PRIVATE METHODS ***************************************************************************** private fun observeBreadcrumbs() { - session.getBreadcrumbsLive(roomSummaryQueryParams { + session.flow() + .liveBreadcrumbs(roomSummaryQueryParams { displayName = QueryStringValue.NoCondition memberships = listOf(Membership.JOIN) }) - .asFlow() .execute { asyncBreadcrumbs -> copy(asyncBreadcrumbs = asyncBreadcrumbs) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index b1d18d7df4..ddb1c51b5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -37,7 +37,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.unwrap import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder @@ -109,6 +108,8 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber import java.util.concurrent.TimeUnit @@ -253,12 +254,11 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeActiveRoomWidgets() { - session.widgetService() - .getRoomWidgetsLive( + session.flow() + .liveRoomWidgets( roomId = initialState.roomId, widgetId = QueryStringValue.NoCondition ) - .asFlow() .map { widgets -> widgets.filter { it.isActive } } @@ -287,9 +287,8 @@ class RoomDetailViewModel @AssistedInject constructor( val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(session.myUserId, QueryStringValue.Case.SENSITIVE) } - room - .getRoomMembersLive(queryParams) - .asFlow() + room.flow() + .liveRoomMembers(queryParams) .map { it.firstOrNull().toOptional() } @@ -1506,8 +1505,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeSyncState() { - session.getSyncStateLive() - .asFlow() + session.flow() + .liveSyncState() .setOnEach { syncState -> copy(syncState = syncState) } @@ -1521,8 +1520,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( @@ -1534,7 +1532,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun getUnreadState() { combine( timelineEvents, - room.getRoomSummaryLive().asFlow().unwrap() + room.flow().liveRoomSummary().unwrap() ) { timelineEvents, roomSummary -> computeUnreadState(timelineEvents, roomSummary) } @@ -1581,8 +1579,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.getChangeMembershipsLive() - .asFlow() + session.flow() + .liveRoomChangeMembershipState() .map { it[initialState.roomId] ?: ChangeMembershipState.Unknown } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index b04fa98ab1..f63366482b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.home.room.detail.timeline.action -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -29,7 +28,6 @@ import im.vector.app.core.extensions.canReact import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.unwrap import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillsPostProcessor @@ -60,6 +58,8 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap /** * Information related to an event and used to display preview in contextual bottom sheet. @@ -137,8 +137,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeEvent() { if (room == null) return - room.getTimeLineEventLive(initialState.eventId) - .asFlow() + room.flow() + .liveTimelineEvent(initialState.eventId) .unwrap() .execute { copy(timelineEvent = it) @@ -149,8 +149,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (room == null) return eventIdFlow .flatMapLatest { eventId -> - room.getEventAnnotationsSummaryLive(eventId) - .asFlow() + room.flow() + .liveAnnotationSummary(eventId) .map { annotations -> EmojiDataSource.quickEmojis.map { emoji -> ToggleState(emoji, annotations.getOrNull()?.reactionsSummary?.firstOrNull { it.key == emoji }?.addedByMe ?: false) 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 59e73462bc..b54d2b1b4f 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 @@ -17,7 +17,6 @@ package im.vector.app.features.home.room.list import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -43,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject @@ -95,8 +95,7 @@ class RoomListViewModel @Inject constructor( ) } - session.getUserLive(session.myUserId) - .asFlow() + session.flow().liveUser(session.myUserId) .map { it.getOrNull()?.getBestName() } .distinctUntilChanged() .execute { @@ -107,8 +106,8 @@ class RoomListViewModel @Inject constructor( } private fun observeMembershipChanges() { - session.getChangeMembershipsLive() - .asFlow() + session.flow() + .liveRoomChangeMembershipState() .setOnEach { copy(roomMembershipChanges = it) } diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index b22390c4d5..09eff756d5 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -16,7 +16,6 @@ package im.vector.app.features.invite -import androidx.lifecycle.asFlow import im.vector.app.ActiveSessionDataSource import im.vector.app.features.session.coroutineScope import io.reactivex.disposables.Disposable @@ -36,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -82,9 +82,10 @@ class InvitesAcceptor @Inject constructor( val roomQueryParams = roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + val flowSession = session.flow() combine( - session.getRoomSummariesLive(roomQueryParams).asFlow(), - session.getChangeMembershipsLive().asFlow().debounce(1000) + flowSession.liveRoomSummaries(roomQueryParams), + flowSession.liveRoomChangeMembershipState().debounce(1000) ) { invitedRooms, _ -> invitedRooms.map { it.roomId } } .filter { it.isNotEmpty() } .onEach { invitedRoomIds -> diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index 01ee3a7a23..c95434a548 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.login2.created -import androidx.lifecycle.asFlow +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -24,13 +24,13 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.unwrap -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap import timber.log.Timber class AccountCreatedViewModel @AssistedInject constructor( @@ -62,8 +62,8 @@ class AccountCreatedViewModel @AssistedInject constructor( } private fun observeUser() { - session.getUserLive(session.myUserId) - .asFlow() + session.rx() + .liveUser(session.myUserId) .unwrap() .map { if (MatrixPatterns.isUserId(it.userId)) { diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index e1992ec572..767d6f1ba7 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -16,9 +16,6 @@ package im.vector.app.features.powerlevel -import androidx.lifecycle.asFlow -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn @@ -27,13 +24,15 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap class PowerLevelsFlowFactory(private val room: Room) { fun createFlow(): Flow { - return room - .getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .asFlow() + return room.flow() + .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .flowOn(Dispatchers.Default) .mapOptional { it.content.toModel() } .unwrap() diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index a24d2df72c..62519336f5 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.room -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -28,7 +27,6 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.unwrap import io.reactivex.Observable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -49,6 +47,8 @@ import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap /** * This ViewModel observe a room summary and notify when the room is left @@ -90,8 +90,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( val emptyResult = Optional.empty() emit(emptyResult) } - room.getRoomSummaryLive() - .asFlow() + room.flow() + .liveRoomSummary() .unwrap() .flowOn(Dispatchers.Default) .map { mapToLeftViewEvent(room, it) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index ff5e3ac3f5..a2089e6cd5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomdirectory -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -39,6 +38,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import timber.log.Timber class RoomDirectoryViewModel @AssistedInject constructor( @@ -80,8 +80,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) } session - .getRoomSummariesLive(queryParams) - .asFlow() + .flow() + .liveRoomSummaries(queryParams) .map { roomSummaries -> roomSummaries .map { it.roomId } @@ -93,8 +93,8 @@ class RoomDirectoryViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.getChangeMembershipsLive() - .asFlow() + session.flow() + .liveRoomChangeMembershipState() .setOnEach { copy(changeMembershipStates = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 5fa7f34aca..2635307e95 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomdirectory.roompreview -import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -43,6 +42,8 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.rx.rx import timber.log.Timber class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState, @@ -167,8 +168,8 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini excludeType = null } session - .getRoomSummariesLive(queryParams) - .asFlow() + .flow() + .liveRoomSummaries(queryParams) .onEach { list -> val isRoomJoined = list.any { it.membership == Membership.JOIN @@ -186,8 +187,8 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini } private fun observeMembershipChanges() { - session.getChangeMembershipsLive() - .asFlow() + session.flow() + .liveRoomChangeMembershipState() .onEach { val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown val joinState = when (changeMembership) { diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index b424d62b1c..14624b800e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roommemberprofile -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -32,7 +31,6 @@ import im.vector.app.R import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine @@ -55,6 +53,8 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, private val stringProvider: StringProvider, @@ -107,7 +107,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - session.cryptoService().getLiveCryptoDeviceInfo(initialState.userId).asFlow() + session.flow().liveUserCryptoDevices(initialState.userId) .map { Pair( it.fold(true, { prev, dev -> prev && dev.isVerified }), @@ -121,14 +121,14 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v ) } - session.cryptoService().crossSigningService().getLiveCrossSigningKeys(initialState.userId).asFlow() + session.flow().liveCrossSigningInfo(initialState.userId) .execute { copy(userMXCrossSigningInfo = it.invoke()?.getOrNull()) } } private fun observeIgnoredState() { - session.getIgnoredUsersLive().asFlow() + session.flow().liveIgnoredUsers() .map { ignored -> ignored.find { it.userId == initialState.userId @@ -245,7 +245,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v val queryParams = roomMemberQueryParams { this.userId = QueryStringValue.Equals(initialState.userId, QueryStringValue.Case.SENSITIVE) } - room.getRoomMembersLive(queryParams).asFlow() + room.flow().liveRoomMembers(queryParams) .map { it.firstOrNull().toOptional() } .unwrap() .execute { @@ -285,7 +285,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } private fun observeRoomSummaryAndPowerLevels(room: Room) { - val roomSummaryLive = room.getRoomSummaryLive().asFlow().unwrap() + val roomSummaryLive = room.flow().liveRoomSummary().unwrap() val powerLevelsContentLive = PowerLevelsFlowFactory(room).createFlow() powerLevelsContentLive diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index 1de96a9ef4..b638d84181 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -16,7 +16,6 @@ */ package im.vector.app.features.roommemberprofile.devices -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -34,7 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.rx.rx data class DeviceListViewState( val userItem: MatrixItem? = null, @@ -55,17 +56,14 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } init { - session.cryptoService().getLiveCryptoDeviceInfo(args.userId) - .asFlow() + session.flow().liveUserCryptoDevices(args.userId) .execute { copy(cryptoDevices = it).also { refreshSelectedId() } } - session.cryptoService().crossSigningService() - .getLiveCrossSigningKeys(args.userId) - .asFlow() + session.flow().liveCrossSigningInfo(args.userId) .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index d08d78e64b..c4fc2bc7bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -28,8 +27,6 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers @@ -43,6 +40,10 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.flow.FlowRoom +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap class RoomProfileViewModel @AssistedInject constructor( @Assisted private val initialState: RoomProfileViewState, @@ -68,14 +69,15 @@ class RoomProfileViewModel @AssistedInject constructor( private val room = session.getRoom(initialState.roomId)!! init { - observeRoomSummary() - observeRoomCreateContent() - observeBannedRoomMembers() + val flowRoom = room.flow() + observeRoomSummary(flowRoom) + observeRoomCreateContent(flowRoom) + observeBannedRoomMembers(flowRoom) observePermissions() } - private fun observeRoomCreateContent() { - room.getStateEventLive(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition).asFlow() + private fun observeRoomCreateContent(flowRoom: FlowRoom) { + flowRoom.liveStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .execute { async -> @@ -90,16 +92,16 @@ class RoomProfileViewModel @AssistedInject constructor( } } - private fun observeRoomSummary() { - room.getRoomSummaryLive().asFlow() + private fun observeRoomSummary(flowRoom: FlowRoom) { + flowRoom.liveRoomSummary() .unwrap() .execute { copy(roomSummary = it) } } - private fun observeBannedRoomMembers() { - room.getRoomMembersLive(roomMemberQueryParams { memberships = listOf(Membership.BAN) }).asFlow() + private fun observeBannedRoomMembers(flowRoom: FlowRoom) { + flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) .execute { copy(bannedMembership = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 086f279655..28a1804fab 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.alias -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading @@ -29,8 +28,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -42,6 +39,11 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState, private val session: Session) @@ -129,8 +131,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( @@ -172,8 +173,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomCanonicalAlias() { - room.getStateEventLive(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) - .asFlow() + room.flow() + .liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .setOnEach { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 4e244b747b..813d50c6bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.banned -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -27,7 +26,6 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -40,6 +38,10 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, private val stringProvider: StringProvider, @@ -55,15 +57,13 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia init { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) } - room.getRoomMembersLive(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) - .asFlow() + room.flow().liveRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.BAN) }) .execute { copy( bannedMemberSummaries = it diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index c5ed085bff..2873b20400 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -27,8 +27,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers @@ -53,6 +51,9 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -93,9 +94,9 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } combine( - room.getRoomMembersLive(roomMemberQueryParams).asFlow(), - room.getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .asFlow() + room.flow().liveRoomMembers(roomMemberQueryParams), + room.flow() + .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() ) @@ -108,8 +109,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } if (room.isEncrypted()) { - room.getRoomMembersLive(roomMemberQueryParams) - .asFlow() + room.flow().liveRoomMembers(roomMemberQueryParams) .flowOn(Dispatchers.Main) .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) @@ -153,8 +153,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) @@ -162,8 +161,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } private fun observeThirdPartyInvites() { - room.getStateEventsLive(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) - .asFlow() + room.flow().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) .execute { async -> copy(threePidInvites = async) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index 498a70a1cb..d944b77f7d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.notifications -import androidx.lifecycle.asFlow +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success @@ -25,10 +25,13 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.unwrap import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap class RoomNotificationSettingsViewModel @AssistedInject constructor( @Assisted initialState: RoomNotificationSettingsViewState, @@ -63,8 +66,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) @@ -72,8 +74,8 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeNotificationState() { - room.getLiveRoomNotificationState() - .asFlow() + room.rx() + .liveNotificationState() .execute { copy(notificationState = it) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt index e3dd76f44c..71e8a313b5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.permissions -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success @@ -26,7 +25,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -36,6 +34,8 @@ 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.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialState: RoomPermissionsViewState, private val session: Session) @@ -63,8 +63,7 @@ class RoomPermissionsViewModel @AssistedInject constructor(@Assisted initialStat } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index f9d64dfb56..7b28ced130 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -27,8 +26,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.flow.launchIn @@ -46,6 +43,9 @@ import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val vectorPreferences: VectorPreferences, @@ -125,8 +125,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> val roomSummary = async.invoke() @@ -162,8 +161,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeRoomHistoryVisibility() { - room.getStateEventLive(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) - .asFlow() + room.flow() + .liveStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.historyVisibility } @@ -173,8 +172,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeJoinRule() { - room.getStateEventLive(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) - .asFlow() + room.flow() + .liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.joinRules } @@ -184,8 +183,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeGuestAccess() { - room.getStateEventLive(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) - .asFlow() + room.flow() + .liveStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .mapNotNull { it.guestAccess } @@ -198,8 +197,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ private fun observeRoomAvatar() { - room.getStateEventLive(EventType.STATE_ROOM_AVATAR, QueryStringValue.NoCondition) - .asFlow() + room.flow() + .liveStateEvent(EventType.STATE_ROOM_AVATAR, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .setOnEach { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index c9d9b6b23c..4526024143 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.uploads -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -29,10 +28,11 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.unwrap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -65,8 +65,7 @@ class RoomUploadsViewModel @AssistedInject constructor( } private fun observeRoomSummary() { - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .execute { async -> copy(roomSummary = async) diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index bb0330e6f6..5afcb77587 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings -import androidx.lifecycle.asFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -26,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.SecretsSynchronisationInfo data class SecretsSynchronisationInfo( @@ -39,12 +39,11 @@ data class SecretsSynchronisationInfo( ) fun Session.liveSecretSynchronisationInfo(): Flow { + val sessionFlow = flow() return combine( - accountDataService() - .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)) - .asFlow(), - cryptoService().crossSigningService().getLiveCrossSigningKeys(myUserId).asFlow(), - cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() + sessionFlow.liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)), + sessionFlow.liveCrossSigningInfo(myUserId), + sessionFlow.liveCrossSigningPrivateKeys() ) { _, crossSigningInfo, pInfo -> // first check if 4S is already setup val is4SSetup = sharedSecretStorageService.isRecoverySetup() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index f539f46b3a..8eb9e2cdf3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -26,7 +26,6 @@ import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import androidx.lifecycle.asFlow import androidx.lifecycle.lifecycleScope import androidx.preference.EditTextPreference import androidx.preference.Preference @@ -48,7 +47,6 @@ import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.TextUtils import im.vector.app.core.utils.getSizeOfFiles import im.vector.app.core.utils.toast -import im.vector.app.core.utils.unwrap import im.vector.app.databinding.DialogChangePasswordBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs @@ -64,6 +62,8 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import java.io.File import java.util.UUID import javax.inject.Inject @@ -122,8 +122,8 @@ class VectorSettingsGeneralFragment @Inject constructor( } private fun observeUserAvatar() { - session.getUserLive(session.myUserId) - .asFlow() + session.flow() + .liveUser(session.myUserId) .unwrap() .distinctUntilChangedBy { user -> user.avatarUrl } .onEach { @@ -133,8 +133,8 @@ class VectorSettingsGeneralFragment @Inject constructor( } private fun observeUserDisplayName() { - session.getUserLive(session.myUserId) - .asFlow() + session.flow() + .liveUser(session.myUserId) .unwrap() .map { it.displayName ?: "" } .distinctUntilChanged() diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 47010f180a..a8fafb096a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.settings.crosssigning -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -38,6 +37,7 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth @@ -56,8 +56,8 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( init { combine( - session.cryptoService().getLiveMyDevicesInfo().asFlow(), - session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId).asFlow() + session.flow().liveMyDevicesInfo(), + session.flow().liveCrossSigningInfo(session.myUserId) ) { myDevicesInfo, mxCrossSigningInfo -> myDevicesInfo to mxCrossSigningInfo diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 8c0c077472..38342efc46 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.settings.devices -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory @@ -28,7 +27,9 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import org.matrix.android.sdk.rx.rx class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState, @Assisted val deviceId: String, @@ -49,8 +50,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As isRecoverySetup = session.sharedSecretStorageService.isRecoverySetup() ) } - session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId) - .asFlow() + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -58,8 +58,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId) - .asFlow() + session.flow().liveUserCryptoDevices(session.myUserId) .map { list -> list.firstOrNull { it.deviceId == deviceId } } @@ -70,7 +69,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As ) } - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .execute { copy( @@ -82,8 +81,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As copy(deviceInfo = Loading()) } - session.cryptoService().getLiveMyDevicesInfo() - .asFlow() + session.flow().liveMyDevicesInfo() .map { devices -> devices.firstOrNull { it.deviceId == deviceId } ?: DeviceInfo(deviceId = deviceId) } 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 d58ad4a99a..b2b4c0c396 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 @@ -16,7 +16,6 @@ package im.vector.app.features.settings.devices -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -57,6 +56,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod 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 org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo @@ -123,14 +123,8 @@ class DevicesViewModel @AssistedInject constructor( } combine( - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId).asFlow() - .onEach { - Timber.v("getLiveCryptoDeviceInfo") - }, - session.cryptoService().getLiveMyDevicesInfo().asFlow() - .onEach { - Timber.v("getLiveMyDevicesInfo") - } + session.flow().liveUserCryptoDevices(session.myUserId), + session.flow().liveMyDevicesInfo() ) { cryptoList, infoList -> infoList @@ -147,8 +141,7 @@ class DevicesViewModel @AssistedInject constructor( ) } - session.cryptoService().crossSigningService().getLiveCrossSigningKeys(session.myUserId) - .asFlow() + session.flow().liveCrossSigningInfo(session.myUserId) .execute { copy( hasAccountCrossSigning = it.invoke()?.getOrNull() != null, @@ -164,8 +157,7 @@ class DevicesViewModel @AssistedInject constructor( // ) // } - session.cryptoService().getLiveCryptoDeviceInfo(session.myUserId) - .asFlow() + session.flow().liveUserCryptoDevices(session.myUserId) .map { it.size } .distinctUntilChanged() .sample(5_000) diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index 69ee9165a7..e5739ec446 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.devtools -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState @@ -32,6 +31,7 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.flow.flow data class AccountDataViewState( val accountData: Async> = Uninitialized @@ -42,9 +42,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A : VectorViewModel(initialState) { init { - session.accountDataService() - .getLiveUserAccountDataEvents(emptySet()) - .asFlow() + session.flow().liveUserAccountData(emptySet()) .execute { copy(accountData = it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index 702a31f22c..7b7b5d0570 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.ignored -import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -34,6 +33,7 @@ import im.vector.app.core.platform.VectorViewModelAction import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.flow.flow data class IgnoredUsersViewState( val ignoredUsers: List = emptyList(), @@ -67,8 +67,8 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: } private fun observeIgnoredUsers() { - session.getIgnoredUsersLive() - .asFlow() + session.flow() + .liveIgnoredUsers() .execute { async -> copy( ignoredUsers = async.invoke().orEmpty() diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 0d00c72062..cd0d74a288 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.threepids -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -39,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64 import org.matrix.android.sdk.internal.crypto.model.rest.DefaultBaseAuth import timber.log.Timber @@ -101,8 +101,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observeThreePids() { - session.getThreePidsLive(true) - .asFlow() + session.flow() + .liveThreePIds(true) .execute { copy( threePids = it @@ -111,8 +111,8 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( } private fun observePendingThreePids() { - session.getPendingThreePidsLive() - .asFlow() + session.flow() + .livePendingThreePIds() .execute { copy( pendingThreePids = it, diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt index 39dc3f2344..44e5ca39f9 100644 --- a/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.share -import androidx.lifecycle.asFlow import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -38,6 +37,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow class IncomingShareViewModel @AssistedInject constructor( @Assisted initialState: IncomingShareViewState, @@ -69,8 +69,8 @@ class IncomingShareViewModel @AssistedInject constructor( val queryParams = roomSummaryQueryParams { memberships = listOf(Membership.JOIN) } - session.getRoomSummariesLive(queryParams) - .asFlow() + session + .flow().liveRoomSummaries(queryParams) .execute { copy(roomSummaries = it) } @@ -86,7 +86,7 @@ class IncomingShareViewModel @AssistedInject constructor( displayName = displayNameQuery memberships = listOf(Membership.JOIN) } - session.getRoomSummariesLive(filterQueryParams).asFlow() + session.flow().liveRoomSummaries(filterQueryParams) } .sample(300) .map { it.sortedWith(breadcrumbsRoomComparator) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index a75f773a4c..bb30670da9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -43,6 +42,8 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.rx.rx import timber.log.Timber class SpaceMenuViewModel @AssistedInject constructor( @@ -77,19 +78,17 @@ class SpaceMenuViewModel @AssistedInject constructor( session.getRoom(initialState.spaceId)?.let { room -> - room.getRoomSummaryLive() - .asFlow() - .onEach { - it.getOrNull()?.let { - if (it.membership == Membership.LEAVE) { - setState { copy(leavingState = Success(Unit)) } - if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { - // switch to home? - appStateHandler.setCurrentSpace(null, session) - } - } + room.flow().liveRoomSummary().onEach { + it.getOrNull()?.let { + if (it.membership == Membership.LEAVE) { + setState { copy(leavingState = Success(Unit)) } + if (appStateHandler.safeActiveSpaceId() == initialState.spaceId) { + // switch to home? + appStateHandler.setCurrentSpace(null, session) } - }.launchIn(viewModelScope) + } + } + }.launchIn(viewModelScope) PowerLevelsFlowFactory(room) .createFlow() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index e902240a2e..46293da209 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.space.SpaceOrderUtils import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.rx.asObservable import java.util.concurrent.TimeUnit @@ -81,13 +82,14 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp init { - session.getUserLive(session.myUserId) - .asFlow() - .setOnEach { - copy( - myMxItem = it.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() - ) - } + session.getUserLive(session.myUserId).asObservable() + .subscribe { + setState { + copy( + myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() + ) + } + }.disposeOnClear() observeSpaceSummaries() // observeSelectionState() @@ -282,13 +284,16 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp null) } + val flowSession = session.flow() + combine( - session.getUserLive(session.myUserId) - .asFlow() + flowSession + .liveUser(session.myUserId) .map { it.getOrNull() }, - session.spaceService().getSpaceSummariesLive(spaceSummaryQueryParams).asFlow(), + flowSession + .liveSpaceSummaries(spaceSummaryQueryParams), session .accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) @@ -314,8 +319,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // clear local echos on update session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) - .asFlow() - .execute { + .asObservable().execute { copy( spaceOrderLocalEchos = emptyMap() ) diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 127ef5d6bf..5e2537f587 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.explore -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -44,6 +43,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow import timber.log.Timber class SpaceDirectoryViewModel @AssistedInject constructor( @@ -147,8 +147,9 @@ class SpaceDirectoryViewModel @AssistedInject constructor( memberships = listOf(Membership.JOIN) excludeType = null } - session.getRoomSummariesLive(queryParams) - .asFlow() + session + .flow() + .liveRoomSummaries(queryParams) .map { it.map { it.roomId }.toSet() } @@ -158,8 +159,8 @@ class SpaceDirectoryViewModel @AssistedInject constructor( } private fun observeMembershipChanges() { - session.getChangeMembershipsLive() - .asFlow() + session.flow() + .liveRoomChangeMembershipState() .setOnEach { copy(changeMembershipStates = it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt index 5f4731e9f4..3d24cf6225 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.spaces.leave -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -31,7 +30,6 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.unwrap import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -41,6 +39,8 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class SpaceLeaveAdvancedViewModel @AssistedInject constructor( @@ -97,8 +97,7 @@ class SpaceLeaveAdvancedViewModel @AssistedInject constructor( val spaceSummary = session.getRoomSummary(initialState.spaceId) setState { copy(spaceSummary = spaceSummary) } session.getRoom(initialState.spaceId)?.let { room -> - room.getRoomSummaryLive() - .asFlow() + room.flow().liveRoomSummary() .unwrap() .onEach { if (it.membership == Membership.LEAVE) { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index cae8540f8b..69b98200c1 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -16,12 +16,12 @@ package im.vector.app.features.userdirectory -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext +import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -29,23 +29,21 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.sample +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.rx.rx +import java.util.concurrent.TimeUnit + +private typealias KnownUsersSearch = String +private typealias DirectoryUsersSearch = String data class ThreePidUser( val email: String, @@ -56,9 +54,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val session: Session) : VectorViewModel(initialState) { - private val knownUsersSearch = MutableStateFlow("") - private val directoryUsersSearch = MutableStateFlow("") - private val identityServerUsersSearch = MutableStateFlow("") + private val knownUsersSearch = BehaviorRelay.create() + private val directoryUsersSearch = BehaviorRelay.create() + private val identityServerUsersSearch = BehaviorRelay.create() @AssistedFactory interface Factory { @@ -79,10 +77,11 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val identityServerListener = object : IdentityServiceListener { override fun onIdentityServerChange() { withState { - identityServerUsersSearch.tryEmit(it.searchTerm) - val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) + identityServerUsersSearch.accept(it.searchTerm) setState { - copy(configuredIdentityServer = identityServerURL) + copy( + configuredIdentityServer = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) + ) } } } @@ -121,7 +120,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { session.identityService().setUserConsent(action.consent) withState { - identityServerUsersSearch.tryEmit(it.searchTerm) + identityServerUsersSearch.accept(it.searchTerm) } } @@ -140,9 +139,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User ) } } - identityServerUsersSearch.tryEmit(searchTerm) - knownUsersSearch.tryEmit(searchTerm) - directoryUsersSearch.tryEmit(searchTerm) + identityServerUsersSearch.accept(searchTerm) + knownUsersSearch.accept(searchTerm) + directoryUsersSearch.accept(searchTerm) } private fun handleShareMyMatrixToLink() { @@ -152,9 +151,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } private fun handleClearSearchUsers() { - knownUsersSearch.tryEmit("") - directoryUsersSearch.tryEmit("") - identityServerUsersSearch.tryEmit("") + knownUsersSearch.accept("") + directoryUsersSearch.accept("") + identityServerUsersSearch.accept("") setState { copy(searchTerm = "") } @@ -163,82 +162,103 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun observeUsers() = withState { state -> identityServerUsersSearch .filter { it.isEmail() } - .sample(300) - .onEach { search -> - executeSearchEmail(search) - }.launchIn(viewModelScope) + .throttleLast(300, TimeUnit.MILLISECONDS) + .switchMapSingle { search -> + val flowSession = session.rx() + val stream = + flowSession.lookupThreePid(ThreePid.Email(search)).flatMap { + it.getOrNull()?.let { foundThreePid -> + flowSession.getProfileInfo(foundThreePid.matrixId) + .map { json -> + ThreePidUser( + email = search, + user = User( + userId = foundThreePid.matrixId, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String + ) + ) + } + .onErrorResumeNext { + Single.just(ThreePidUser(email = search, user = User(foundThreePid.matrixId))) + } + } ?: Single.just(ThreePidUser(email = search, user = null)) + } + stream.toAsync { + copy(matchingEmail = it) + } + } + .subscribe() + .disposeOnClear() knownUsersSearch - .sample(300) - .flowOn(Dispatchers.Main) - .flatMapLatest { search -> - session.getPagedUsersLive(search, state.excludedUserIds).asFlow() - }.execute { - copy(knownUsers = it) + .throttleLast(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + session.rx().livePagedUsers(it, state.excludedUserIds) + } + .execute { async -> + copy(knownUsers = async) } directoryUsersSearch - .debounce(300) - .onEach { search -> - executeSearchDirectory(state, search) - }.launchIn(viewModelScope) - } + .debounce(300, TimeUnit.MILLISECONDS) + .switchMapSingle { search -> + val stream = if (search.isBlank()) { + Single.just(emptyList()) + } else { + val searchObservable = session.rx() + .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) + .map { users -> + users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + } + // If it's a valid user id try to use Profile API + // because directory only returns users that are in public rooms or share a room with you, where as + // profile will work other federations + if (!MatrixPatterns.isUserId(search)) { + searchObservable + } else { + val profileObservable = session.rx().getProfileInfo(search) + .map { json -> + User( + userId = search, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String + ).toOptional() + } + .onErrorResumeNext { + // Profile API can be restricted and doesn't have to return result. + // In this case allow inviting valid user ids. + Single.just( + User( + userId = search, + displayName = null, + avatarUrl = null + ).toOptional() + ) + } - private suspend fun executeSearchEmail(search: String) { - suspend { - val params = listOf(ThreePid.Email(search)) - val foundThreePid = tryOrNull { - session.identityService().lookUp(params).firstOrNull() - } - if (foundThreePid == null) { - null - } else { - try { - val json = session.getProfile(foundThreePid.matrixId) - ThreePidUser( - email = search, - user = User( - userId = foundThreePid.matrixId, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String + Single.zip( + searchObservable, + profileObservable, + { searchResults, optionalProfile -> + val profile = optionalProfile.getOrNull() ?: return@zip searchResults + val searchContainsProfile = searchResults.any { it.userId == profile.userId } + if (searchContainsProfile) { + searchResults + } else { + listOf(profile) + searchResults + } + } ) - ) - } catch (failure: Throwable) { - ThreePidUser(email = search, user = User(foundThreePid.matrixId)) + } + } + stream.toAsync { + copy(directoryUsers = it) + } } - } - }.execute { - copy(matchingEmail = it) - } - } - - private suspend fun executeSearchDirectory(state: UserListViewState, search: String) { - suspend { - if (search.isBlank()) { - emptyList() - } else { - val searchResult = session - .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) - .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } - val userProfile = if (MatrixPatterns.isUserId(search)) { - val json = tryOrNull { session.getProfile(search) } - User( - userId = search, - displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String, - avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String - ) - } else { - null - } - if (userProfile == null || searchResult.any { it.userId == userProfile.userId }) { - searchResult - } else { - listOf(userProfile) + searchResult - } - } - }.execute { - copy(directoryUsers = it) - } + .subscribe() + .disposeOnClear() } private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 05f6157d11..c88750e6e1 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.widgets import android.net.Uri -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -30,8 +29,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.mapOptional -import im.vector.app.core.utils.unwrap import im.vector.app.features.widgets.permissions.WidgetPermissionsHelper import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -45,6 +42,9 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.mapOptional +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber import javax.net.ssl.HttpsURLConnection @@ -119,8 +119,7 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi if (room == null) { return } - room.getStateEventLive(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .asFlow() + room.flow().liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() .map { @@ -136,9 +135,8 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi return } val widgetId = initialState.widgetId ?: return - session.widgetService() - .getRoomWidgetsLive(initialState.roomId, QueryStringValue.Equals(widgetId)) - .asFlow() + session.flow() + .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) .filter { it.isNotEmpty() } .map { it.first() } .execute { diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index ee1ffd62c6..bbfeea6a76 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -15,7 +15,6 @@ */ package im.vector.app.features.widgets.permissions -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory @@ -33,6 +32,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.widgets.model.WidgetType +import org.matrix.android.sdk.flow.flow import timber.log.Timber import java.net.URL @@ -49,9 +49,8 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in private fun observeWidget() { val widgetId = initialState.widgetId ?: return - session.widgetService() - .getRoomWidgetsLive(initialState.roomId, QueryStringValue.Equals(widgetId)) - .asFlow() + session.flow() + .liveRoomWidgets(initialState.roomId, QueryStringValue.Equals(widgetId)) .filter { it.isNotEmpty() } .map { val widget = it.first() diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt index f0caac9b97..c3719ffd8e 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/ServerBackupStatusViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.workers.signout import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -42,6 +41,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener +import org.matrix.android.sdk.flow.flow data class ServerBackupStatusViewState( val bannerState: Async = Uninitialized @@ -92,19 +92,9 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS init { session.cryptoService().keysBackupService().addListener(this) keysBackupState.value = session.cryptoService().keysBackupService().state - val liveUserAccountData = session.accountDataService() - .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) - .asFlow() - val liveCrossSigningInfo = session.cryptoService() - .crossSigningService() - .getLiveCrossSigningKeys(session.myUserId) - .asFlow() - val liveCrossSigningPrivateKeys = session.cryptoService() - .crossSigningService() - .getLiveCrossSigningPrivateKeys() - .asFlow() - - + val liveUserAccountData = session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) + val liveCrossSigningInfo = session.flow().liveCrossSigningInfo(session.myUserId) + val liveCrossSigningPrivateKeys = session.flow().liveCrossSigningPrivateKeys() combine(liveUserAccountData, liveCrossSigningInfo, keyBackupFlow, liveCrossSigningPrivateKeys) { _, crossSigningInfo, keyBackupState, pInfo -> // first check if 4S is already setup if (session.sharedSecretStorageService.isRecoverySetup()) { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt index e6429b6263..057d9e31f8 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignoutCheckViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.workers.signout import android.net.Uri -import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -44,6 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener +import org.matrix.android.sdk.flow.flow import timber.log.Timber data class SignoutCheckViewState( @@ -98,9 +98,7 @@ class SignoutCheckViewModel @AssistedInject constructor( ) } - session.accountDataService() - .getLiveUserAccountDataEvents(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) - .asFlow() + session.flow().liveUserAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME)) .map { session.sharedSecretStorageService.isRecoverySetup() } From acf3b847819860c2e244c827a08217f59dc8daf5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 7 Oct 2021 12:24:08 +0200 Subject: [PATCH 023/172] Mavericks 2: migrate UserListViewModel --- .../userdirectory/UserListViewModel.kt | 204 ++++++++---------- 1 file changed, 92 insertions(+), 112 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 69b98200c1..cae8540f8b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -16,12 +16,12 @@ package im.vector.app.features.userdirectory +import androidx.lifecycle.asFlow import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -29,21 +29,23 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit - -private typealias KnownUsersSearch = String -private typealias DirectoryUsersSearch = String data class ThreePidUser( val email: String, @@ -54,9 +56,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val session: Session) : VectorViewModel(initialState) { - private val knownUsersSearch = BehaviorRelay.create() - private val directoryUsersSearch = BehaviorRelay.create() - private val identityServerUsersSearch = BehaviorRelay.create() + private val knownUsersSearch = MutableStateFlow("") + private val directoryUsersSearch = MutableStateFlow("") + private val identityServerUsersSearch = MutableStateFlow("") @AssistedFactory interface Factory { @@ -77,11 +79,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val identityServerListener = object : IdentityServiceListener { override fun onIdentityServerChange() { withState { - identityServerUsersSearch.accept(it.searchTerm) + identityServerUsersSearch.tryEmit(it.searchTerm) + val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) setState { - copy( - configuredIdentityServer = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) - ) + copy(configuredIdentityServer = identityServerURL) } } } @@ -120,7 +121,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { session.identityService().setUserConsent(action.consent) withState { - identityServerUsersSearch.accept(it.searchTerm) + identityServerUsersSearch.tryEmit(it.searchTerm) } } @@ -139,9 +140,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User ) } } - identityServerUsersSearch.accept(searchTerm) - knownUsersSearch.accept(searchTerm) - directoryUsersSearch.accept(searchTerm) + identityServerUsersSearch.tryEmit(searchTerm) + knownUsersSearch.tryEmit(searchTerm) + directoryUsersSearch.tryEmit(searchTerm) } private fun handleShareMyMatrixToLink() { @@ -151,9 +152,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } private fun handleClearSearchUsers() { - knownUsersSearch.accept("") - directoryUsersSearch.accept("") - identityServerUsersSearch.accept("") + knownUsersSearch.tryEmit("") + directoryUsersSearch.tryEmit("") + identityServerUsersSearch.tryEmit("") setState { copy(searchTerm = "") } @@ -162,103 +163,82 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun observeUsers() = withState { state -> identityServerUsersSearch .filter { it.isEmail() } - .throttleLast(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val flowSession = session.rx() - val stream = - flowSession.lookupThreePid(ThreePid.Email(search)).flatMap { - it.getOrNull()?.let { foundThreePid -> - flowSession.getProfileInfo(foundThreePid.matrixId) - .map { json -> - ThreePidUser( - email = search, - user = User( - userId = foundThreePid.matrixId, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ) - ) - } - .onErrorResumeNext { - Single.just(ThreePidUser(email = search, user = User(foundThreePid.matrixId))) - } - } ?: Single.just(ThreePidUser(email = search, user = null)) - } - stream.toAsync { - copy(matchingEmail = it) - } - } - .subscribe() - .disposeOnClear() + .sample(300) + .onEach { search -> + executeSearchEmail(search) + }.launchIn(viewModelScope) knownUsersSearch - .throttleLast(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - session.rx().livePagedUsers(it, state.excludedUserIds) - } - .execute { async -> - copy(knownUsers = async) + .sample(300) + .flowOn(Dispatchers.Main) + .flatMapLatest { search -> + session.getPagedUsersLive(search, state.excludedUserIds).asFlow() + }.execute { + copy(knownUsers = it) } directoryUsersSearch - .debounce(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val stream = if (search.isBlank()) { - Single.just(emptyList()) - } else { - val searchObservable = session.rx() - .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) - .map { users -> - users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } - } - // If it's a valid user id try to use Profile API - // because directory only returns users that are in public rooms or share a room with you, where as - // profile will work other federations - if (!MatrixPatterns.isUserId(search)) { - searchObservable - } else { - val profileObservable = session.rx().getProfileInfo(search) - .map { json -> - User( - userId = search, - displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String - ).toOptional() - } - .onErrorResumeNext { - // Profile API can be restricted and doesn't have to return result. - // In this case allow inviting valid user ids. - Single.just( - User( - userId = search, - displayName = null, - avatarUrl = null - ).toOptional() - ) - } + .debounce(300) + .onEach { search -> + executeSearchDirectory(state, search) + }.launchIn(viewModelScope) + } - Single.zip( - searchObservable, - profileObservable, - { searchResults, optionalProfile -> - val profile = optionalProfile.getOrNull() ?: return@zip searchResults - val searchContainsProfile = searchResults.any { it.userId == profile.userId } - if (searchContainsProfile) { - searchResults - } else { - listOf(profile) + searchResults - } - } + private suspend fun executeSearchEmail(search: String) { + suspend { + val params = listOf(ThreePid.Email(search)) + val foundThreePid = tryOrNull { + session.identityService().lookUp(params).firstOrNull() + } + if (foundThreePid == null) { + null + } else { + try { + val json = session.getProfile(foundThreePid.matrixId) + ThreePidUser( + email = search, + user = User( + userId = foundThreePid.matrixId, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String ) - } - } - stream.toAsync { - copy(directoryUsers = it) - } + ) + } catch (failure: Throwable) { + ThreePidUser(email = search, user = User(foundThreePid.matrixId)) } - .subscribe() - .disposeOnClear() + } + }.execute { + copy(matchingEmail = it) + } + } + + private suspend fun executeSearchDirectory(state: UserListViewState, search: String) { + suspend { + if (search.isBlank()) { + emptyList() + } else { + val searchResult = session + .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) + .sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + val userProfile = if (MatrixPatterns.isUserId(search)) { + val json = tryOrNull { session.getProfile(search) } + User( + userId = search, + displayName = json?.get(ProfileService.DISPLAY_NAME_KEY) as? String, + avatarUrl = json?.get(ProfileService.AVATAR_URL_KEY) as? String + ) + } else { + null + } + if (userProfile == null || searchResult.any { it.userId == userProfile.userId }) { + searchResult + } else { + listOf(userProfile) + searchResult + } + } + }.execute { + copy(directoryUsers = it) + } } private fun handleSelectUser(action: UserListAction.AddPendingSelection) = withState { state -> From 578358d8392df78ec68c699e2c4050c2ebe12d73 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 7 Oct 2021 12:24:53 +0200 Subject: [PATCH 024/172] Mavericks 2: introduce startWith (like startWithCallable from matrix-android-sdk-rx) --- .../org/matrix/android/sdk/flow/FlowExt.kt | 32 ++++++++++++++ .../org/matrix/android/sdk/flow/FlowRoom.kt | 22 ++++++++++ .../matrix/android/sdk/flow/FlowSession.kt | 42 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt new file mode 100644 index 0000000000..dd8a5d7750 --- /dev/null +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.flow + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +internal fun Flow.startWith(supplier: suspend () -> T): Flow { + return this + .onStart { + val value = withContext(Dispatchers.IO) { + supplier() + } + emit(value) + } +} 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 a3e476ce08..1e0a1bc2e8 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 @@ -30,31 +30,50 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional class FlowRoom(private val room: Room) { fun liveRoomSummary(): Flow> { return room.getRoomSummaryLive().asFlow() + .startWith { + room.roomSummary().toOptional() + } } fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { return room.getRoomMembersLive(queryParams).asFlow() + .startWith { + room.getRoomMembers(queryParams) + } } fun liveAnnotationSummary(eventId: String): Flow> { return room.getEventAnnotationsSummaryLive(eventId).asFlow() + .startWith { + room.getEventAnnotationsSummary(eventId).toOptional() + } } fun liveTimelineEvent(eventId: String): Flow> { return room.getTimeLineEventLive(eventId).asFlow() + .startWith { + room.getTimeLineEvent(eventId).toOptional() + } } fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { return room.getStateEventLive(eventType, stateKey).asFlow() + .startWith { + room.getStateEvent(eventType, stateKey).toOptional() + } } fun liveStateEvents(eventTypes: Set): Flow> { return room.getStateEventsLive(eventTypes).asFlow() + .startWith { + room.getStateEvents(eventTypes) + } } fun liveReadMarker(): Flow> { @@ -71,6 +90,9 @@ class FlowRoom(private val room: Room) { fun liveDraft(): Flow> { return room.getDraftLive().asFlow() + .startWith { + room.getDraft().toOptional() + } } fun liveNotificationState(): Flow { 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 affcd4a65d..563ae30b45 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 @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo @@ -45,22 +46,37 @@ class RxFlow(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { return session.getRoomSummariesLive(queryParams).asFlow() + .startWith { + session.getRoomSummaries(queryParams) + } } fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { return session.getGroupSummariesLive(queryParams).asFlow() + .startWith { + session.getGroupSummaries(queryParams) + } } fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow> { return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() + .startWith { + session.spaceService().getSpaceSummaries(queryParams) + } } fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { return session.getBreadcrumbsLive(queryParams).asFlow() + .startWith { + session.getBreadcrumbs(queryParams) + } } fun liveMyDevicesInfo(): Flow> { return session.cryptoService().getLiveMyDevicesInfo().asFlow() + .startWith { + session.cryptoService().getMyDevicesInfo() + } } fun liveSyncState(): Flow { @@ -73,10 +89,16 @@ class RxFlow(private val session: Session) { fun liveUser(userId: String): Flow> { return session.getUserLive(userId).asFlow() + .startWith { + session.getUser(userId).toOptional() + } } fun liveRoomMember(userId: String, roomId: String): Flow> { return session.getRoomMemberLive(userId, roomId).asFlow() + .startWith { + session.getRoomMember(userId, roomId).toOptional() + } } fun liveUsers(): Flow> { @@ -93,30 +115,47 @@ class RxFlow(private val session: Session) { fun liveThreePIds(refreshData: Boolean): Flow> { return session.getThreePidsLive(refreshData).asFlow() + .startWith { session.getThreePids() } } fun livePendingThreePIds(): Flow> { return session.getPendingThreePidsLive().asFlow() + .startWith { session.getPendingThreePids() } } fun liveUserCryptoDevices(userId: String): Flow> { return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() + .startWith { + session.cryptoService().getCryptoDeviceInfo(userId) + } } fun liveCrossSigningInfo(userId: String): Flow> { return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() + .startWith { + session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional() + } } fun liveCrossSigningPrivateKeys(): Flow> { return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() + .startWith { + session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional() + } } fun liveUserAccountData(types: Set): Flow> { return session.accountDataService().getLiveUserAccountDataEvents(types).asFlow() + .startWith { + session.accountDataService().getUserAccountDataEvents(types) + } } fun liveRoomAccountData(types: Set): Flow> { return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() + .startWith { + session.accountDataService().getRoomAccountDataEvents(types) + } } fun liveRoomWidgets( @@ -126,6 +165,9 @@ class RxFlow(private val session: Session) { excludedTypes: Set? = null ): Flow> { return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asFlow() + .startWith { + session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) + } } fun liveRoomChangeMembershipState(): Flow> { From 79ec0591d2817ce1c64e14129ebfa0425e101265 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 7 Oct 2021 15:32:57 +0200 Subject: [PATCH 025/172] Mavericks 2: continue removing rx --- .../call/conference/JitsiCallViewModel.kt | 15 ++++-- .../quads/SharedSecureStorageViewModel.kt | 1 - .../features/devtools/RoomDevToolViewModel.kt | 1 - .../features/home/HomeActivityViewModel.kt | 1 - .../room/breadcrumbs/BreadcrumbsViewModel.kt | 1 - .../invite/InviteUsersToRoomViewModel.kt | 1 - .../login2/created/AccountCreatedViewModel.kt | 7 +-- .../roompreview/RoomPreviewViewModel.kt | 1 - .../devices/DeviceListBottomSheetViewModel.kt | 1 - .../roomprofile/alias/RoomAliasViewModel.kt | 2 - .../banned/RoomBannedMemberListViewModel.kt | 2 - .../RoomNotificationSettingsViewModel.kt | 4 +- .../VectorSettingsSecurityPrivacyFragment.kt | 1 - ...iceVerificationInfoBottomSheetViewModel.kt | 1 - .../GossipingEventsPaperTrailViewModel.kt | 4 +- .../devtools/KeyRequestListViewModel.kt | 5 +- .../settings/push/PushGatewaysViewModel.kt | 5 +- .../app/features/spaces/SpaceMenuViewModel.kt | 1 - .../features/spaces/SpacesListViewModel.kt | 46 ++++++++++--------- 19 files changed, 47 insertions(+), 53 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt index b4bb01d374..d1d94cbf35 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.call.conference +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Fail import com.airbnb.mvrx.MavericksViewModelFactory @@ -28,7 +29,11 @@ import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import io.reactivex.disposables.Disposable +import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -47,7 +52,7 @@ class JitsiCallViewModel @AssistedInject constructor( fun create(initialState: JitsiCallViewState): JitsiCallViewModel } - private var currentWidgetObserver: Disposable? = null + private var currentWidgetObserver: Job? = null private val widgetService = session.widgetService() private var confIsJoined = false @@ -59,11 +64,11 @@ class JitsiCallViewModel @AssistedInject constructor( private fun observeWidget(roomId: String, widgetId: String) { confIsJoined = false - currentWidgetObserver?.dispose() + currentWidgetObserver?.cancel() currentWidgetObserver = widgetService.getRoomWidgetsLive(roomId, QueryStringValue.Equals(widgetId), WidgetType.Jitsi.values()) - .asObservable() + .asFlow() .distinctUntilChanged() - .subscribe { + .onEach { val jitsiWidget = it.firstOrNull() if (jitsiWidget != null) { setState { @@ -81,7 +86,7 @@ class JitsiCallViewModel @AssistedInject constructor( } } } - .disposeOnClear() + .launchIn(viewModelScope) } private fun joinConference(jitsiWidget: Widget) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 151b73ff32..025fea1fb6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -45,7 +45,6 @@ import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt index 4bab65fd5d..023d1976c9 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolViewModel.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.rx.rx class RoomDevToolViewModel @AssistedInject constructor( @Assisted val initialState: RoomDevToolViewState, 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 fa3df1ca97..0f70648d88 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 @@ -52,7 +52,6 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.asObservable -import org.matrix.android.sdk.rx.rx import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index f32f132fe9..2fbdf7d0ec 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 1f723104cf..d6a6b42efa 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -36,7 +36,6 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.rx.rx class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted initialState: InviteUsersToRoomViewState, diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt index c95434a548..34957dd47b 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedViewModel.kt @@ -24,13 +24,14 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap import timber.log.Timber class AccountCreatedViewModel @AssistedInject constructor( @@ -62,7 +63,7 @@ class AccountCreatedViewModel @AssistedInject constructor( } private fun observeUser() { - session.rx() + session.flow() .liveUser(session.myUserId) .unwrap() .map { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 2635307e95..938be7c955 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -43,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx import timber.log.Timber class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val initialState: RoomPreviewViewState, diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index b638d84181..0b30b0487f 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo -import org.matrix.android.sdk.rx.rx data class DeviceListViewState( val userItem: MatrixItem? = null, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 28a1804fab..7a4e4b44d0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -42,8 +42,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.mapOptional import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState, private val session: Session) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 813d50c6bb..fd4871b682 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -40,8 +40,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, private val stringProvider: StringProvider, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt index d944b77f7d..f91b482b13 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/notifications/RoomNotificationSettingsViewModel.kt @@ -30,8 +30,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap -import org.matrix.android.sdk.rx.rx -import org.matrix.android.sdk.rx.unwrap class RoomNotificationSettingsViewModel @AssistedInject constructor( @Assisted initialState: RoomNotificationSettingsViewState, @@ -74,7 +72,7 @@ class RoomNotificationSettingsViewModel @AssistedInject constructor( } private fun observeNotificationState() { - room.rx() + room.flow() .liveNotificationState() .execute { copy(notificationState = it) 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 103c4ab06d..82d4e590c1 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 @@ -72,7 +72,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.rx.SecretsSynchronisationInfo -import org.matrix.android.sdk.rx.rx import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 38342efc46..3c8b6095e2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import org.matrix.android.sdk.rx.rx class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState, @Assisted val deviceId: String, diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index e07da1065c..a00d3ebb13 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devtools +import androidx.lifecycle.asFlow import androidx.paging.PagedList import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -50,7 +51,8 @@ class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted i setState { copy(events = Loading()) } - session.cryptoService().getGossipingEventsTrail().asObservable() + session.cryptoService().getGossipingEventsTrail() + .asFlow() .execute { copy(events = it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index 362f2768fc..9020d35081 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.devtools +import androidx.lifecycle.asFlow import androidx.lifecycle.viewModelScope import androidx.paging.PagedList import com.airbnb.mvrx.Async @@ -51,13 +52,13 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState fun refresh() { viewModelScope.launch { - session.cryptoService().getOutgoingRoomKeyRequestsPaged().asObservable() + session.cryptoService().getOutgoingRoomKeyRequestsPaged().asFlow() .execute { copy(outgoingRoomKeyRequests = it) } session.cryptoService().getIncomingRoomKeyRequestsPaged() - .asObservable() + .asFlow() .execute { copy(incomingRequests = it) } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt index bc159e3fe0..7e6683007f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.settings.push -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksState @@ -31,7 +30,7 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.pushers.Pusher -import org.matrix.android.sdk.rx.RxSession +import org.matrix.android.sdk.flow.flow data class PushGatewayViewState( val pushGateways: Async> = Uninitialized @@ -62,7 +61,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: } private fun observePushers() { - RxSession(session) + session.flow() .livePushers() .execute { copy(pushGateways = it) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt index bb30670da9..887c93afd4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceMenuViewModel.kt @@ -43,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.rx import timber.log.Timber class SpaceMenuViewModel @AssistedInject constructor( diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 46293da209..ba789cbd4a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -34,8 +34,14 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.observeOn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -82,14 +88,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp init { - session.getUserLive(session.myUserId).asObservable() - .subscribe { - setState { - copy( - myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() - ) - } - }.disposeOnClear() + session.getUserLive(session.myUserId) + .asFlow() + .setOnEach { + copy( + myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() + ) + } observeSpaceSummaries() // observeSelectionState() @@ -105,14 +110,10 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .disposeOnClear() session.getGroupSummariesLive(groupSummaryQueryParams {}) - .asObservable() - .subscribe { - setState { - copy( - legacyGroups = it - ) - } - }.disposeOnClear() + .asFlow() + .setOnEach { + copy(legacyGroups = it) + } // XXX there should be a way to refactor this and share it session.getPagedRoomSummariesLive( @@ -122,10 +123,10 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp !vectorPreferences.prefSpacesShowAllRoomInHome() } ?: ActiveSpaceFilter.None }, sortOrder = RoomSortOrder.NONE - ).asObservable() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(Schedulers.computation()) - .subscribe { + ).asFlow() + .sample(300) + .flowOn(Dispatchers.Default) + .onEach { val inviteCount = if (autoAcceptInvites.hideInvites) { 0 } else { @@ -150,7 +151,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp homeAggregateCount = counts ) } - }.disposeOnClear() + }.launchIn(viewModelScope) } override fun handle(action: SpaceListAction) { @@ -319,7 +320,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // clear local echos on update session.accountDataService() .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) - .asObservable().execute { + .asFlow() + .execute { copy( spaceOrderLocalEchos = emptyMap() ) From 56b0b28d5eb40b038c0e1c38cbefe4d1da8159ef Mon Sep 17 00:00:00 2001 From: koh6uawi <89084173+koh6uawi@users.noreply.github.com> Date: Fri, 8 Oct 2021 01:51:05 +0200 Subject: [PATCH 026/172] Make "Select text size" dialog scrollable Wrap the LinearLayout inside a ScrollView. --- .../res/layout/dialog_select_text_size.xml | 152 +++++++++--------- 1 file changed, 78 insertions(+), 74 deletions(-) diff --git a/vector/src/main/res/layout/dialog_select_text_size.xml b/vector/src/main/res/layout/dialog_select_text_size.xml index 1a3bd91485..0790adf88a 100644 --- a/vector/src/main/res/layout/dialog_select_text_size.xml +++ b/vector/src/main/res/layout/dialog_select_text_size.xml @@ -1,83 +1,87 @@ - - - - + + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingStart="?dialogPreferredPadding" + android:paddingTop="12dp" + android:paddingEnd="?dialogPreferredPadding" + tools:ignore="SpUsage"> - + + - + - + - + - + - - \ No newline at end of file + + + + + From c66d6aab5ce3751714b1458afd8d0c75d80e5ab3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 8 Oct 2021 12:55:37 +0200 Subject: [PATCH 027/172] Timeline: dispatch update on a background thread --- .../timeline/TimelineEventController.kt | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 5a37a343e6..d320f0b6e0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -244,20 +244,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec interceptorHelper.intercept(models, partialState.unreadState, timeline, callback) } - fun update(viewState: RoomDetailViewState) = synchronized(modelCache) { - val newPartialState = PartialState(viewState) - if (partialState.highlightedEventId != newPartialState.highlightedEventId) { - // Clear cache to force a refresh - for (i in 0 until modelCache.size) { - if (modelCache[i]?.eventId == viewState.highlightedEventId || - modelCache[i]?.eventId == partialState.highlightedEventId) { - modelCache[i] = null + fun update(viewState: RoomDetailViewState) = backgroundHandler.post { + synchronized(modelCache) { + val newPartialState = PartialState(viewState) + if (partialState.highlightedEventId != newPartialState.highlightedEventId) { + // Clear cache to force a refresh + for (i in 0 until modelCache.size) { + if (modelCache[i]?.eventId == viewState.highlightedEventId || + modelCache[i]?.eventId == partialState.highlightedEventId) { + modelCache[i] = null + } } } - } - if (newPartialState != partialState) { - partialState = newPartialState - requestModelBuild() + if (newPartialState != partialState) { + partialState = newPartialState + requestModelBuild() + } } } From 2a29243298d28a69b2866f0222910a4cd7c77433 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 8 Oct 2021 16:45:29 +0200 Subject: [PATCH 028/172] Mavericks 2: clean code, but have warnings --- build.gradle | 2 +- .../matrix/android/sdk/flow/ExampleInstrumentedTest.kt | 6 ++---- .../main/java/org/matrix/android/sdk/flow/FlowRoom.kt | 4 ++-- .../java/org/matrix/android/sdk/flow/ExampleUnitTest.kt | 3 +-- vector/build.gradle | 1 + .../core/platform/VectorBaseBottomSheetDialogFragment.kt | 3 +-- .../im/vector/app/features/call/VectorCallActivity.kt | 6 ++++-- .../app/features/call/conference/JitsiCallViewModel.kt | 2 -- .../app/features/discovery/DiscoverySettingsViewModel.kt | 8 ++++---- .../java/im/vector/app/features/home/HomeActivity.kt | 9 ++++----- .../im/vector/app/features/home/HomeActivityViewModel.kt | 1 - .../home/UnknownDeviceDetectorSharedViewModel.kt | 3 +-- .../home/room/breadcrumbs/BreadcrumbsViewModel.kt | 1 - .../app/features/home/room/detail/RoomDetailFragment.kt | 2 -- .../home/room/detail/composer/TextComposerViewModel.kt | 4 ++-- .../home/room/detail/composer/TextComposerViewState.kt | 3 ++- .../app/features/home/room/list/RoomListViewModel.kt | 2 +- .../app/features/invite/InviteUsersToRoomViewModel.kt | 3 --- .../im/vector/app/features/permalink/PermalinkHandler.kt | 2 -- .../main/java/im/vector/app/features/pin/PinActivity.kt | 2 +- .../features/room/RequireActiveMembershipViewModel.kt | 7 +------ .../roommemberprofile/RoomMemberProfileViewModel.kt | 2 +- .../roomprofile/members/RoomMemberListViewModel.kt | 6 +----- .../roomprofile/settings/RoomSettingsViewModel.kt | 2 +- .../settings/VectorSettingsSecurityPrivacyFragment.kt | 1 - .../crosssigning/CrossSigningSettingsViewModel.kt | 3 +-- .../app/features/settings/devices/DevicesViewModel.kt | 3 +-- .../devtools/GossipingEventsPaperTrailViewModel.kt | 1 - .../settings/devtools/KeyRequestListViewModel.kt | 1 - .../im/vector/app/features/spaces/SpacesListViewModel.kt | 6 +----- .../im/vector/app/features/usercode/UserCodeActivity.kt | 2 +- 31 files changed, 35 insertions(+), 66 deletions(-) diff --git a/build.gradle b/build.gradle index 6b611a79bb..93f3e17f34 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { // Warnings are potential errors, so stop ignoring them // You can override by passing `-PallWarningsAsErrors=false` in the command line - kotlinOptions.allWarningsAsErrors = false //project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean() + kotlinOptions.allWarningsAsErrors = project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean() } // Fix "Java heap space" issue diff --git a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt index 41799955a4..ca3502a211 100644 --- a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt +++ b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt @@ -16,14 +16,12 @@ package org.matrix.android.sdk.flow -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * 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 1e0a1bc2e8..a78597ee46 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 @@ -36,8 +36,8 @@ class FlowRoom(private val room: Room) { fun liveRoomSummary(): Flow> { return room.getRoomSummaryLive().asFlow() - .startWith { - room.roomSummary().toOptional() + .startWith { + room.roomSummary().toOptional() } } diff --git a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt index bd627b2041..59e0856576 100644 --- a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt +++ b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt @@ -16,9 +16,8 @@ package org.matrix.android.sdk.flow -import org.junit.Test - import org.junit.Assert.* +import org.junit.Test /** * Example local unit test, which will execute on the development machine (host). diff --git a/vector/build.gradle b/vector/build.gradle index fc83b81432..d766cb947f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -378,6 +378,7 @@ dependencies { kapt libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks + //TODO: remove when entirely migrated to Flow implementation libs.airbnb.mavericksRx // Work diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index d13e446ed4..68765d615e 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -27,9 +27,8 @@ import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.MavericksView import com.airbnb.mvrx.Mavericks -import com.airbnb.mvrx.MvRxView +import com.airbnb.mvrx.MavericksView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt index 36aafc3a9a..0654942d4b 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt @@ -631,10 +631,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro const val INCOMING_ACCEPT = "INCOMING_ACCEPT" fun newIntent(context: Context, call: WebRtcCall, mode: String?): Intent { + val callArgs = CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall) return Intent(context, VectorCallActivity::class.java).apply { // what could be the best flags? flags = Intent.FLAG_ACTIVITY_NEW_TASK - putExtra(Mavericks.KEY_ARG, CallArgs(call.nativeRoomId, call.callId, call.mxCall.opponentUserId, !call.mxCall.isOutgoing, call.mxCall.isVideoCall)) + putExtra(Mavericks.KEY_ARG, callArgs) putExtra(EXTRA_MODE, mode) } } @@ -646,10 +647,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro isIncomingCall: Boolean, isVideoCall: Boolean, mode: String?): Intent { + val callArgs = CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall) return Intent(context, VectorCallActivity::class.java).apply { // what could be the best flags? flags = Intent.FLAG_ACTIVITY_NEW_TASK - putExtra(Mavericks.KEY_ARG, CallArgs(signalingRoomId, callId, otherUserId, isIncomingCall, isVideoCall)) + putExtra(Mavericks.KEY_ARG, callArgs) putExtra(EXTRA_MODE, mode) } } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt index 7f50e76e73..c46b4e459d 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt @@ -28,7 +28,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import io.reactivex.disposables.Disposable import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged @@ -39,7 +38,6 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.rx.asObservable class JitsiCallViewModel @AssistedInject constructor( @Assisted initialState: JitsiCallViewState, diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 9578e143eb..66f38928a7 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -26,21 +26,21 @@ import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ensureProtocol +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.api.session.terms.TermsService +import org.matrix.android.sdk.flow.flow class DiscoverySettingsViewModel @AssistedInject constructor( @Assisted initialState: DiscoverySettingsState, 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 a41f414011..ff1154acc3 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 @@ -27,10 +27,10 @@ import android.view.MenuItem import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.Mavericks import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -74,7 +74,6 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import im.vector.app.push.fcm.FcmHelper -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.initsync.SyncStatusService @@ -319,8 +318,8 @@ class HomeActivity : buildTask = true ) if (!isHandled) { - val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) - || deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) + val isMatrixToLink = deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE) || + deepLink.startsWith(MATRIX_TO_CUSTOM_SCHEME_URL_BASE) MaterialAlertDialogBuilder(this@HomeActivity) .setTitle(R.string.dialog_title_error) .setMessage(if (isMatrixToLink) R.string.permalink_malformed else R.string.universal_link_malformed) 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 c45c128c55..627ff4be12 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 @@ -51,7 +51,6 @@ import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.util.awaitCallback -import org.matrix.android.sdk.rx.asObservable import timber.log.Timber import kotlin.coroutines.Continuation import kotlin.coroutines.resume diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index b434c48373..36e31770a9 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -102,8 +102,7 @@ class UnknownDeviceDetectorSharedViewModel @AssistedInject constructor(@Assisted session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveMyDevicesInfo(), session.flow().liveCrossSigningPrivateKeys() - ) - { cryptoList, infoList, pInfo -> + ) { cryptoList, infoList, pInfo -> // Timber.v("## Detector trigger ${cryptoList.map { "${it.deviceId} ${it.trustLevel}" }}") // Timber.v("## Detector trigger canCrossSign ${pInfo.get().selfSigned != null}") infoList diff --git a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index 795899a7b0..8ed44053a1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -25,7 +25,6 @@ import dagger.assisted.AssistedInject import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import io.reactivex.schedulers.Schedulers import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 6aebbb09e6..ed87f1b833 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -184,8 +184,6 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 96b684afa6..742d2848a1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.composer import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -707,7 +707,7 @@ class TextComposerViewModel @AssistedInject constructor( fun create(initialState: TextComposerViewState): TextComposerViewModel } - companion object : MvRxViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index fd1dd2d0ad..ebcce425ea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.composer +import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MvRxState import im.vector.app.features.home.room.detail.RoomDetailArgs import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -47,7 +48,7 @@ data class TextComposerViewState( val isVoiceRecording: Boolean = false, val isSendButtonVisible: Boolean = false, val sendMode: SendMode = SendMode.REGULAR("", false) -) : MvRxState { +) : MavericksState { val isComposerVisible: Boolean get() = canSendMessage && !isVoiceRecording 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 d3d4771692..345c33ec18 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 @@ -43,8 +43,8 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index daa5d54dc0..fd06614950 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -30,10 +30,7 @@ import im.vector.app.features.userdirectory.PendingSelection import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index 598cf9b885..a02cfe7517 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -32,8 +32,6 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.toOptional import javax.inject.Inject class PermalinkHandler @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, diff --git a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt index a335f1a1f2..430be6bc1f 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinActivity.kt @@ -18,8 +18,8 @@ package im.vector.app.features.pin import android.content.Context import android.content.Intent -import com.google.android.material.appbar.MaterialToolbar import com.airbnb.mvrx.Mavericks +import com.google.android.material.appbar.MaterialToolbar import im.vector.app.R import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.ToolbarConfigurable diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 7184b97d0a..44a6963a5d 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -27,19 +27,14 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import io.reactivex.Observable import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asFlow 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 -import kotlinx.coroutines.flow.subscribe -import kotlinx.coroutines.flow.switchMap import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -86,7 +81,7 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( roomIdFlow .unwrap() .flatMapLatest { roomId -> - val room = session.getRoom(roomId) ?: return@flatMapLatest flow{ + val room = session.getRoom(roomId) ?: return@flatMapLatest flow { val emptyResult = Optional.empty() emit(emptyResult) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index be559f9859..e99c8dde64 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -31,8 +31,8 @@ import im.vector.app.R import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.displayname.getBestName +import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index 03c32e31e5..f16353353c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -28,7 +28,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsFlowFactory -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -37,7 +36,6 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse @@ -99,11 +97,9 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) .mapOptional { it.content.toModel() } .unwrap() - ) - { roomMembers, powerLevelsContent -> + ) { roomMembers, powerLevelsContent -> buildRoomMemberSummaries(powerLevelsContent, roomMembers) } - .execute { async -> copy(roomMemberSummaries = async) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 2b10d1c132..c3c8ca7e2f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -301,7 +301,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: _viewEvents.post(RoomSettingsViewEvents.Success) } catch (failure: Throwable) { _viewEvents.post(RoomSettingsViewEvents.Failure(failure)) - }finally { + } finally { updateLoadingState(isLoading = false) } } 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 82d4e590c1..7e60e69379 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 @@ -58,7 +58,6 @@ import im.vector.app.features.pin.PinMode import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.themes.ThemeUtils -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index c6139f66f5..033d9cf716 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -58,8 +58,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor( combine( session.flow().liveMyDevicesInfo(), session.flow().liveCrossSigningInfo(session.myUserId) - ) - { myDevicesInfo, mxCrossSigningInfo -> + ) { myDevicesInfo, mxCrossSigningInfo -> myDevicesInfo to mxCrossSigningInfo } .execute { data -> 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 b2b4c0c396..a8154c3e11 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 @@ -125,8 +125,7 @@ class DevicesViewModel @AssistedInject constructor( combine( session.flow().liveUserCryptoDevices(session.myUserId), session.flow().liveMyDevicesInfo() - ) - { cryptoList, infoList -> + ) { cryptoList, infoList -> infoList .sortedByDescending { it.lastSeenTs } .map { deviceInfo -> diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt index 920d1ab7d0..fd09b38919 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt @@ -33,7 +33,6 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.rx.asObservable data class GossipingEventsPaperTrailState( val events: Async> = Uninitialized diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt index 07b2addccf..37decc4a12 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestListViewModel.kt @@ -35,7 +35,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest import org.matrix.android.sdk.internal.crypto.OutgoingRoomKeyRequest -import org.matrix.android.sdk.rx.asObservable data class KeyRequestListViewState( val incomingRequests: Async> = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index ba789cbd4a..fbbaeafe72 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -33,13 +33,11 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.observeOn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch @@ -60,8 +58,6 @@ import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.asObservable -import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, private val appStateHandler: AppStateHandler, @@ -92,7 +88,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .asFlow() .setOnEach { copy( - myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() + myMxItem = it.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() ) } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 5c238040e2..db1bc3056a 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -24,8 +24,8 @@ import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import com.airbnb.mvrx.Mavericks import androidx.fragment.app.FragmentManager +import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import im.vector.app.R From 506dfe5fea8dbe6d2e84b365388c7a7e26d43b77 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 11 Oct 2021 11:46:37 +0300 Subject: [PATCH 029/172] Adding trailing space " " or ": " if the user started a sentence by mentioning someone, --- .../app/features/home/room/detail/AutoCompleter.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index d4430730d7..1fcac59b00 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -220,8 +220,16 @@ class AutoCompleter @AssistedInject constructor( // Replace the word by its completion val displayName = matrixItem.getBestName() - // with a trailing space - editable.replace(startIndex, endIndex, "$displayName ") + + // Adding trailing space " " or ": " if the user started mention someone + val displayNameSuffix = + if (firstChar == "@" && editText.length() == 1) { + ": " + } else { + " " + } + + editable.replace(startIndex, endIndex, "$displayName$displayNameSuffix") // Add the span val span = PillImageSpan( From 3a6259fd299285b5ebb75bcd07f47285b5c5d86c Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 11 Oct 2021 11:53:43 +0300 Subject: [PATCH 030/172] Add changelog file --- changelog.d/908.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/908.bugfix diff --git a/changelog.d/908.bugfix b/changelog.d/908.bugfix new file mode 100644 index 0000000000..f43b03e892 --- /dev/null +++ b/changelog.d/908.bugfix @@ -0,0 +1 @@ +Issue #908 Adding trailing space " " or ": " if the user started a sentence by mentioning someone, From a2c790b4a168433f62ee77aa4a9c60cd8f314b74 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 11 Oct 2021 12:29:00 +0300 Subject: [PATCH 031/172] Update to support the whole typing name --- .../im/vector/app/features/home/room/detail/AutoCompleter.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index 1fcac59b00..1d6530218d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -220,10 +220,9 @@ class AutoCompleter @AssistedInject constructor( // Replace the word by its completion val displayName = matrixItem.getBestName() - // Adding trailing space " " or ": " if the user started mention someone val displayNameSuffix = - if (firstChar == "@" && editText.length() == 1) { + if (firstChar == "@" && startIndex == 0) { ": " } else { " " From 343783f8070ba7aaec25ea61832d4dfad64175fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 12:06:51 +0200 Subject: [PATCH 032/172] Version++ --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 96bec7a2fb..86a257c7d6 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -31,7 +31,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.3.3\"" + buildConfigField "String", "SDK_VERSION", "\"1.3.4\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" resValue "string", "git_sdk_revision", "\"${gitRevision()}\"" diff --git a/vector/build.gradle b/vector/build.gradle index 4bcbd5d1fa..c6350a2812 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -14,7 +14,7 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 3 -ext.versionPatch = 3 +ext.versionPatch = 4 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From fadaaa5b188fd89138d840879636290d34022762 Mon Sep 17 00:00:00 2001 From: koh6uawi <89084173+koh6uawi@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:07:28 +0200 Subject: [PATCH 033/172] Add a changelog entry --- changelog.d/4201.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4201.bugfix diff --git a/changelog.d/4201.bugfix b/changelog.d/4201.bugfix new file mode 100644 index 0000000000..fa8e506015 --- /dev/null +++ b/changelog.d/4201.bugfix @@ -0,0 +1 @@ +Make the font size selection dialog scrollable From 6dd0de612315bde5305d65e32c2e7d6db11d8e21 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 13:29:29 +0200 Subject: [PATCH 034/172] Mavericks 2.4.0 --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 9ab7bdc7b8..4d1fdb6a60 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -19,7 +19,7 @@ def moshi = "1.12.0" def lifecycle = "2.2.0" def rxBinding = "3.1.0" def epoxy = "4.6.2" -def mavericks = "2.3.0" +def mavericks = "2.4.0" def glide = "4.12.0" def bigImageViewer = "1.8.1" def jjwt = "0.11.2" From 6721669d1d6983d77ae1e96f6af8adfaa3853b83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 14:13:42 +0200 Subject: [PATCH 035/172] Fixes false positive "This is an internal Mavericks API. It is not intended for external use." of MvRx `by viewModel()` calls. Maybe due to the inlining of code... This is a temporary fix... --- vector/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index d766cb947f..493dcb6bd8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -298,6 +298,12 @@ android { kotlinOptions { jvmTarget = "11" + freeCompilerArgs += [ + "-Xopt-in=kotlin.RequiresOptIn", + // Fixes false positive "This is an internal Mavericks API. It is not intended for external use." + // of MvRx `by viewModel()` calls. Maybe due to the inlining of code... This is a temporary fix... + "-Xopt-in=com.airbnb.mvrx.InternalMavericksApi", + ] } sourceSets { From f89a32da1ff474ae9af240b125fa069bc9ed35e6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 14:27:55 +0200 Subject: [PATCH 036/172] Add opt-in for kotlinx.coroutines annotations --- vector/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index 493dcb6bd8..55e03de3f3 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -303,6 +303,10 @@ android { // Fixes false positive "This is an internal Mavericks API. It is not intended for external use." // of MvRx `by viewModel()` calls. Maybe due to the inlining of code... This is a temporary fix... "-Xopt-in=com.airbnb.mvrx.InternalMavericksApi", + // Opt in for kotlinx.coroutines.FlowPreview too + "-Xopt-in=kotlinx.coroutines.FlowPreview", + // Opt in for kotlinx.coroutines.ExperimentalCoroutinesApi too + "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", ] } From caf2c2c487fcf90fa9f183c854efdc140ea035b9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 14:29:58 +0200 Subject: [PATCH 037/172] Use same values for all modules --- matrix-sdk-android-flow/build.gradle | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle index 4aecec169b..fd2e2e0824 100644 --- a/matrix-sdk-android-flow/build.gradle +++ b/matrix-sdk-android-flow/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 31 + compileSdk versions.compileSdk defaultConfig { - minSdk 21 - targetSdk 31 + minSdk versions.minSdk + targetSdk versions.targetSdk testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -22,11 +22,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility versions.sourceCompat + targetCompatibility versions.targetCompat } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = "11" } } @@ -45,5 +45,4 @@ dependencies { // Logging implementation libs.jakewharton.timber - -} \ No newline at end of file +} From 6520729343461aef42fbf99a9973b0e68df8d14f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 14:41:40 +0200 Subject: [PATCH 038/172] ktlint --- .../features/home/room/detail/composer/TextComposerViewState.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index ebcce425ea..3110aa8dc3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -17,7 +17,6 @@ package im.vector.app.features.home.room.detail.composer import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MvRxState import im.vector.app.features.home.room.detail.RoomDetailArgs import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent From bde129ddced3599e915b19caa4a2d1cc22db8197 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 11 Oct 2021 14:42:06 +0200 Subject: [PATCH 039/172] Remove not compiling sample tests --- .../sdk/flow/ExampleInstrumentedTest.kt | 38 ------------------- .../android/sdk/flow/ExampleUnitTest.kt | 32 ---------------- 2 files changed, 70 deletions(-) delete mode 100644 matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt delete mode 100644 matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt diff --git a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt b/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt deleted file mode 100644 index ca3502a211..0000000000 --- a/matrix-sdk-android-flow/src/androidTest/java/org/matrix/android/sdk/flow/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import org.junit.Assert.* -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("org.matrix.android.sdk.flow.test", appContext.packageName) - } -} diff --git a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt b/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt deleted file mode 100644 index 59e0856576..0000000000 --- a/matrix-sdk-android-flow/src/test/java/org/matrix/android/sdk/flow/ExampleUnitTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.flow - -import org.junit.Assert.* -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} From 13aee7d162d5284bcff91a282b847a26b2c35523 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 11 Oct 2021 16:49:15 +0300 Subject: [PATCH 040/172] Do not delete voice message file to be able to resend. --- changelog.d/4006.bugfix | 1 + .../sdk/internal/session/content/UploadContentWorker.kt | 5 +++++ .../app/features/home/room/detail/RoomDetailFragment.kt | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/4006.bugfix diff --git a/changelog.d/4006.bugfix b/changelog.d/4006.bugfix new file mode 100644 index 0000000000..61ac98ccb8 --- /dev/null +++ b/changelog.d/4006.bugfix @@ -0,0 +1 @@ +Voice Message not sendable if recorded while flight mode was on \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 11c200c54b..6fd92047b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -291,6 +291,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter filesToDelete.forEach { tryOrNull { it.delete() } } + + // Delete the temporary voice message file + if (params.attachment.type == ContentAttachmentData.Type.AUDIO && params.attachment.mimeType == MimeTypes.Ogg) { + context.contentResolver.delete(params.attachment.queryUri, null, null) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index b3fe6fcbf4..2066bf9d08 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1095,8 +1095,6 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) - // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. - roomDetailViewModel.handle(RoomDetailAction.EndAllVoiceActions) views.voiceMessageRecorderView.initVoiceRecordingViews() } From ccc4a43737c85c8f8efccf8413a352e0a3f34109 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 11 Oct 2021 14:13:08 +0000 Subject: [PATCH 041/172] Sync Emojis --- .../emoji_picker_datasource_formatted.json | 1147 +++++++++++++---- .../main/res/raw/emoji_picker_datasource.json | 2 +- 2 files changed, 893 insertions(+), 256 deletions(-) diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json index 33fc9be128..4f3038d53c 100644 --- a/tools/emojis/emoji_picker_datasource_formatted.json +++ b/tools/emojis/emoji_picker_datasource_formatted.json @@ -15,6 +15,7 @@ "face-with-tears-of-joy", "slightly-smiling-face", "upsidedown-face", + "melting-face", "winking-face", "smiling-face-with-smiling-eyes", "smiling-face-with-halo", @@ -33,15 +34,19 @@ "zany-face", "squinting-face-with-tongue", "moneymouth-face", - "hugging-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", @@ -63,7 +68,7 @@ "hot-face", "cold-face", "woozy-face", - "knockedout-face", + "face-with-crossedout-eyes", "face-with-spiral-eyes", "exploding-head", "cowboy-hat-face", @@ -73,6 +78,7 @@ "nerd-face", "face-with-monocle", "confused-face", + "face-with-diagonal-mouth", "worried-face", "slightly-frowning-face", "frowning-face", @@ -81,6 +87,7 @@ "astonished-face", "flushed-face", "pleading-face", + "face-holding-back-tears", "frowning-face-with-open-mouth", "anguished-face", "fearful-face", @@ -172,11 +179,16 @@ "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", @@ -186,6 +198,7 @@ "middle-finger", "backhand-index-pointing-down", "index-pointing-up", + "index-pointing-at-the-viewer", "thumbs-up", "thumbs-down", "raised-fist", @@ -194,6 +207,7 @@ "rightfacing-fist", "clapping-hands", "raising-hands", + "heart-hands", "open-hands", "palms-up-together", "handshake", @@ -218,6 +232,7 @@ "eye", "tongue", "mouth", + "biting-lip", "baby", "child", "boy", @@ -337,6 +352,7 @@ "construction-worker", "man-construction-worker", "woman-construction-worker", + "person-with-crown", "prince", "princess", "person-wearing-turban", @@ -351,6 +367,8 @@ "man-with-veil", "woman-with-veil", "pregnant-woman", + "pregnant-man", + "pregnant-person", "breastfeeding", "woman-feeding-baby", "man-feeding-baby", @@ -386,6 +404,7 @@ "zombie", "man-zombie", "woman-zombie", + "troll", "person-getting-massage", "man-getting-massage", "woman-getting-massage", @@ -623,6 +642,7 @@ "shark", "octopus", "spiral-shell", + "coral", "snail", "butterfly", "bug", @@ -642,6 +662,7 @@ "bouquet", "cherry-blossom", "white-flower", + "lotus", "rosette", "rose", "wilted-flower", @@ -661,7 +682,9 @@ "four-leaf-clover", "maple-leaf", "fallen-leaf", - "leaf-fluttering-in-wind" + "leaf-fluttering-in-wind", + "empty-nest", + "nest-with-eggs" ] }, { @@ -701,6 +724,7 @@ "onion", "mushroom", "peanuts", + "beans", "chestnut", "bread", "croissant", @@ -786,6 +810,7 @@ "clinking-beer-mugs", "clinking-glasses", "tumbler-glass", + "pouring-liquid", "cup-with-straw", "bubble-tea", "beverage-box", @@ -796,6 +821,7 @@ "fork-and-knife", "spoon", "kitchen-knife", + "jar", "amphora" ] }, @@ -864,6 +890,7 @@ "bridge-at-night", "hot-springs", "carousel-horse", + "playground-slide", "ferris-wheel", "roller-coaster", "barber-pole", @@ -912,12 +939,14 @@ "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", @@ -1085,6 +1114,7 @@ "crystal-ball", "magic-wand", "nazar-amulet", + "hamsa", "video-game", "joystick", "slot-machine", @@ -1092,6 +1122,7 @@ "puzzle-piece", "teddy-bear", "piata", + "mirror-ball", "nesting-dolls", "spade-suit", "heart-suit", @@ -1193,6 +1224,7 @@ "pager", "fax-machine", "battery", + "low-battery", "electric-plug", "laptop", "desktop-computer", @@ -1333,7 +1365,9 @@ "drop-of-blood", "pill", "adhesive-bandage", + "crutch", "stethoscope", + "xray", "door", "elevator", "mirror", @@ -1354,6 +1388,7 @@ "roll-of-paper", "bucket", "soap", + "bubbles", "toothbrush", "sponge", "fire-extinguisher", @@ -1363,7 +1398,8 @@ "headstone", "funeral-urn", "moai", - "placard" + "placard", + "identification-card" ] }, { @@ -1473,6 +1509,7 @@ "plus", "minus", "divide", + "heavy-equals-sign", "infinity", "double-exclamation-mark", "exclamation-question-mark", @@ -2012,6 +2049,16 @@ "smile" ] }, + "melting-face": { + "a": "⊛ Melting Face", + "b": "1FAE0", + "j": [ + "disappear", + "dissolve", + "liquid", + "melt" + ] + }, "winking-face": { "a": "Winking Face", "b": "1F609", @@ -2271,13 +2318,16 @@ "dollar" ] }, - "hugging-face": { - "a": "Hugging Face", + "smiling-face-with-open-hands": { + "a": "Smiling Face with Open Hands", "b": "1F917", "j": [ "face", "hug", "hugging", + "open hands", + "smiling face", + "hugging_face", "smile" ] }, @@ -2292,6 +2342,27 @@ "face" ] }, + "face-with-open-eyes-and-hand-over-mouth": { + "a": "⊛ Face with Open Eyes and Hand over Mouth", + "b": "1FAE2", + "j": [ + "amazement", + "awe", + "disbelief", + "embarrass", + "scared", + "surprise" + ] + }, + "face-with-peeking-eye": { + "a": "⊛ Face with Peeking Eye", + "b": "1FAE3", + "j": [ + "captivated", + "peep", + "stare" + ] + }, "shushing-face": { "a": "Shushing Face", "b": "1F92B", @@ -2313,6 +2384,17 @@ "consider" ] }, + "saluting-face": { + "a": "⊛ Saluting Face", + "b": "1FAE1", + "j": [ + "ok", + "salute", + "sunny", + "troops", + "yes" + ] + }, "zippermouth-face": { "a": "Zipper-Mouth Face", "b": "1F910", @@ -2377,8 +2459,19 @@ "hellokitty" ] }, + "dotted-line-face": { + "a": "⊛ Dotted Line Face", + "b": "1FAE5", + "j": [ + "depressed", + "disappear", + "hide", + "introvert", + "invisible" + ] + }, "face-in-clouds": { - "a": "⊛ Face in Clouds", + "a": "Face in Clouds", "b": "1F636-200D-1F32B-FE0F", "j": [ "absentminded", @@ -2439,7 +2532,7 @@ ] }, "face-exhaling": { - "a": "⊛ Face Exhaling", + "a": "Face Exhaling", "b": "1F62E-200D-1F4A8", "j": [ "exhale", @@ -2631,14 +2724,15 @@ "wavy" ] }, - "knockedout-face": { - "a": "Knocked-out Face", + "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", - "knocked-out face", "dizzy_face", "spent", "unconscious", @@ -2647,7 +2741,7 @@ ] }, "face-with-spiral-eyes": { - "a": "⊛ Face with Spiral Eyes", + "a": "Face with Spiral Eyes", "b": "1F635-200D-1F4AB", "j": [ "dizzy", @@ -2754,6 +2848,16 @@ ":/" ] }, + "face-with-diagonal-mouth": { + "a": "⊛ Face with Diagonal Mouth", + "b": "1FAE4", + "j": [ + "disappointed", + "meh", + "skeptical", + "unsure" + ] + }, "worried-face": { "a": "Worried Face", "b": "1F61F", @@ -2849,6 +2953,17 @@ "face" ] }, + "face-holding-back-tears": { + "a": "⊛ Face Holding Back Tears", + "b": "1F979", + "j": [ + "angry", + "cry", + "proud", + "resist", + "sad" + ] + }, "frowning-face-with-open-mouth": { "a": "Frowning Face with Open Mouth", "b": "1F626", @@ -3584,7 +3699,7 @@ ] }, "heart-on-fire": { - "a": "⊛ Heart on Fire", + "a": "Heart on Fire", "b": "2764-FE0F-200D-1F525", "j": [ "burn", @@ -3596,7 +3711,7 @@ ] }, "mending-heart": { - "a": "⊛ Mending Heart", + "a": "Mending Heart", "b": "2764-FE0F-200D-1FA79", "j": [ "healthier", @@ -3933,6 +4048,43 @@ "star trek" ] }, + "rightwards-hand": { + "a": "⊛ Rightwards Hand", + "b": "1FAF1", + "j": [ + "hand", + "right", + "rightward" + ] + }, + "leftwards-hand": { + "a": "⊛ Leftwards Hand", + "b": "1FAF2", + "j": [ + "hand", + "left", + "leftward" + ] + }, + "palm-down-hand": { + "a": "⊛ Palm Down Hand", + "b": "1FAF3", + "j": [ + "dismiss", + "drop", + "shoo" + ] + }, + "palm-up-hand": { + "a": "⊛ Palm Up Hand", + "b": "1FAF4", + "j": [ + "beckon", + "catch", + "come", + "offer" + ] + }, "ok-hand": { "a": "Ok Hand", "b": "1F44C", @@ -3995,6 +4147,17 @@ "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", @@ -4110,6 +4273,14 @@ "direction" ] }, + "index-pointing-at-the-viewer": { + "a": "⊛ Index Pointing at the Viewer", + "b": "1FAF5", + "j": [ + "point", + "you" + ] + }, "thumbs-up": { "a": "Thumbs Up", "b": "1F44D", @@ -4217,6 +4388,13 @@ "hands" ] }, + "heart-hands": { + "a": "⊛ Heart Hands", + "b": "1FAF6", + "j": [ + "love" + ] + }, "open-hands": { "a": "Open Hands", "b": "1F450", @@ -4463,6 +4641,18 @@ "kiss" ] }, + "biting-lip": { + "a": "⊛ Biting Lip", + "b": "1FAE6", + "j": [ + "anxious", + "fear", + "flirting", + "nervous", + "uncomfortable", + "worried" + ] + }, "baby": { "a": "Baby", "b": "1F476", @@ -4552,7 +4742,7 @@ ] }, "man-beard": { - "a": "⊛ Man: Beard", + "a": "Man: Beard", "b": "1F9D4-200D-2642-FE0F", "j": [ "beard", @@ -4561,7 +4751,7 @@ ] }, "woman-beard": { - "a": "⊛ Woman: Beard", + "a": "Woman: Beard", "b": "1F9D4-200D-2640-FE0F", "j": [ "beard", @@ -5847,6 +6037,16 @@ "labor" ] }, + "person-with-crown": { + "a": "⊛ Person with Crown", + "b": "1FAC5", + "j": [ + "monarch", + "noble", + "regal", + "royalty" + ] + }, "prince": { "a": "Prince", "b": "1F934", @@ -6010,6 +6210,26 @@ "baby" ] }, + "pregnant-man": { + "a": "⊛ Pregnant Man", + "b": "1FAC3", + "j": [ + "belly", + "bloated", + "full", + "pregnant" + ] + }, + "pregnant-person": { + "a": "⊛ Pregnant Person", + "b": "1FAC4", + "j": [ + "belly", + "bloated", + "full", + "pregnant" + ] + }, "breastfeeding": { "a": "Breast-Feeding", "b": "1F931", @@ -6395,6 +6615,15 @@ "female" ] }, + "troll": { + "a": "⊛ Troll", + "b": "1F9CC", + "j": [ + "fairy tale", + "fantasy", + "monster" + ] + }, "person-getting-massage": { "a": "Person Getting Massage", "b": "1F486", @@ -8625,7 +8854,8 @@ "a": "Koala", "b": "1F428", "j": [ - "bear", + "face", + "marsupial", "animal", "nature" ] @@ -9125,6 +9355,14 @@ "beach" ] }, + "coral": { + "a": "⊛ Coral", + "b": "1FAB8", + "j": [ + "ocean", + "reef" + ] + }, "snail": { "a": "Snail", "b": "1F40C", @@ -9326,6 +9564,18 @@ "spring" ] }, + "lotus": { + "a": "⊛ Lotus", + "b": "1FAB7", + "j": [ + "Buddhism", + "flower", + "Hinduism", + "India", + "purity", + "Vietnam" + ] + }, "rosette": { "a": "Rosette", "b": "1F3F5", @@ -9564,6 +9814,20 @@ "spring" ] }, + "empty-nest": { + "a": "⊛ Empty Nest", + "b": "1FAB9", + "j": [ + "nesting" + ] + }, + "nest-with-eggs": { + "a": "⊛ Nest with Eggs", + "b": "1FABA", + "j": [ + "nesting" + ] + }, "grapes": { "a": "Grapes", "b": "1F347", @@ -9894,6 +10158,15 @@ "vegetable" ] }, + "beans": { + "a": "⊛ Beans", + "b": "1FAD8", + "j": [ + "food", + "kidney", + "legume" + ] + }, "chestnut": { "a": "Chestnut", "b": "1F330", @@ -10893,6 +11166,16 @@ "scotch" ] }, + "pouring-liquid": { + "a": "⊛ Pouring Liquid", + "b": "1FAD7", + "j": [ + "drink", + "empty", + "glass", + "spill" + ] + }, "cup-with-straw": { "a": "Cup with Straw", "b": "1F964", @@ -11010,6 +11293,17 @@ "kitchen" ] }, + "jar": { + "a": "⊛ Jar", + "b": "1FAD9", + "j": [ + "condiment", + "container", + "empty", + "sauce", + "store" + ] + }, "amphora": { "a": "Amphora", "b": "1F3FA", @@ -11690,6 +11984,14 @@ "carnival" ] }, + "playground-slide": { + "a": "⊛ Playground Slide", + "b": "1F6DD", + "j": [ + "amusement park", + "play" + ] + }, "ferris-wheel": { "a": "Ferris Wheel", "b": "1F3A1", @@ -12164,7 +12466,6 @@ "b": "1F68F", "j": [ "bus", - "busstop", "stop", "transportation", "wait" @@ -12212,6 +12513,15 @@ "petroleum" ] }, + "wheel": { + "a": "⊛ Wheel", + "b": "1F6DE", + "j": [ + "circle", + "tire", + "turn" + ] + }, "police-car-light": { "a": "Police Car Light", "b": "1F6A8", @@ -12283,6 +12593,17 @@ "boat" ] }, + "ring-buoy": { + "a": "⊛ Ring Buoy", + "b": "1F6DF", + "j": [ + "float", + "life preserver", + "life saver", + "rescue", + "safety" + ] + }, "sailboat": { "a": "Sailboat", "b": "26F5", @@ -14322,6 +14643,18 @@ "talisman" ] }, + "hamsa": { + "a": "⊛ Hamsa", + "b": "1FAAC", + "j": [ + "amulet", + "Fatima", + "hand", + "Mary", + "Miriam", + "protection" + ] + }, "video-game": { "a": "Video Game", "b": "1F3AE", @@ -14402,6 +14735,16 @@ "candy" ] }, + "mirror-ball": { + "a": "⊛ Mirror Ball", + "b": "1FAA9", + "j": [ + "dance", + "disco", + "glitter", + "party" + ] + }, "nesting-dolls": { "a": "Nesting Dolls", "b": "1FA86", @@ -15502,6 +15845,14 @@ "sustain" ] }, + "low-battery": { + "a": "⊛ Low Battery", + "b": "1FAAB", + "j": [ + "electronic", + "low energy" + ] + }, "electric-plug": { "a": "Electric Plug", "b": "1F50C", @@ -17135,6 +17486,17 @@ "heal" ] }, + "crutch": { + "a": "⊛ Crutch", + "b": "1FA7C", + "j": [ + "cane", + "disability", + "hurt", + "mobility aid", + "stick" + ] + }, "stethoscope": { "a": "Stethoscope", "b": "1FA7A", @@ -17145,6 +17507,16 @@ "health" ] }, + "xray": { + "a": "⊛ X-Ray", + "b": "1FA7B", + "j": [ + "bones", + "doctor", + "medical", + "skeleton" + ] + }, "door": { "a": "Door", "b": "1F6AA", @@ -17340,6 +17712,16 @@ "soapdish" ] }, + "bubbles": { + "a": "⊛ Bubbles", + "b": "1FAE7", + "j": [ + "burp", + "clean", + "soap", + "underwater" + ] + }, "toothbrush": { "a": "Toothbrush", "b": "1FAA5", @@ -17453,6 +17835,16 @@ "announcement" ] }, + "identification-card": { + "a": "⊛ Identification Card", + "b": "1FAAA", + "j": [ + "credentials", + "ID", + "license", + "security" + ] + }, "atm-sign": { "a": "Atm Sign", "b": "1F3E7", @@ -18715,6 +19107,14 @@ "calculation" ] }, + "heavy-equals-sign": { + "a": "⊛ Heavy Equals Sign", + "b": "1F7F0", + "j": [ + "equality", + "math" + ] + }, "infinity": { "a": "Infinity", "b": "267E", @@ -20247,7 +20647,8 @@ "ad", "nation", "country", - "banner" + "banner", + "andorra" ] }, "flag-united-arab-emirates": { @@ -20260,7 +20661,8 @@ "emirates", "nation", "country", - "banner" + "banner", + "united_arab_emirates" ] }, "flag-afghanistan": { @@ -20271,7 +20673,8 @@ "af", "nation", "country", - "banner" + "banner", + "afghanistan" ] }, "flag-antigua--barbuda": { @@ -20284,7 +20687,8 @@ "barbuda", "nation", "country", - "banner" + "banner", + "antigua_barbuda" ] }, "flag-anguilla": { @@ -20295,7 +20699,8 @@ "ai", "nation", "country", - "banner" + "banner", + "anguilla" ] }, "flag-albania": { @@ -20306,7 +20711,8 @@ "al", "nation", "country", - "banner" + "banner", + "albania" ] }, "flag-armenia": { @@ -20317,7 +20723,8 @@ "am", "nation", "country", - "banner" + "banner", + "armenia" ] }, "flag-angola": { @@ -20328,7 +20735,8 @@ "ao", "nation", "country", - "banner" + "banner", + "angola" ] }, "flag-antarctica": { @@ -20339,7 +20747,8 @@ "aq", "nation", "country", - "banner" + "banner", + "antarctica" ] }, "flag-argentina": { @@ -20350,7 +20759,8 @@ "ar", "nation", "country", - "banner" + "banner", + "argentina" ] }, "flag-american-samoa": { @@ -20362,7 +20772,8 @@ "ws", "nation", "country", - "banner" + "banner", + "american_samoa" ] }, "flag-austria": { @@ -20373,7 +20784,8 @@ "at", "nation", "country", - "banner" + "banner", + "austria" ] }, "flag-australia": { @@ -20384,7 +20796,8 @@ "au", "nation", "country", - "banner" + "banner", + "australia" ] }, "flag-aruba": { @@ -20395,7 +20808,8 @@ "aw", "nation", "country", - "banner" + "banner", + "aruba" ] }, "flag-land-islands": { @@ -20408,7 +20822,8 @@ "islands", "nation", "country", - "banner" + "banner", + "aland_islands" ] }, "flag-azerbaijan": { @@ -20419,7 +20834,8 @@ "az", "nation", "country", - "banner" + "banner", + "azerbaijan" ] }, "flag-bosnia--herzegovina": { @@ -20432,7 +20848,8 @@ "herzegovina", "nation", "country", - "banner" + "banner", + "bosnia_herzegovina" ] }, "flag-barbados": { @@ -20443,7 +20860,8 @@ "bb", "nation", "country", - "banner" + "banner", + "barbados" ] }, "flag-bangladesh": { @@ -20454,7 +20872,8 @@ "bd", "nation", "country", - "banner" + "banner", + "bangladesh" ] }, "flag-belgium": { @@ -20465,7 +20884,8 @@ "be", "nation", "country", - "banner" + "banner", + "belgium" ] }, "flag-burkina-faso": { @@ -20477,7 +20897,8 @@ "faso", "nation", "country", - "banner" + "banner", + "burkina_faso" ] }, "flag-bulgaria": { @@ -20488,7 +20909,8 @@ "bg", "nation", "country", - "banner" + "banner", + "bulgaria" ] }, "flag-bahrain": { @@ -20499,7 +20921,8 @@ "bh", "nation", "country", - "banner" + "banner", + "bahrain" ] }, "flag-burundi": { @@ -20510,7 +20933,8 @@ "bi", "nation", "country", - "banner" + "banner", + "burundi" ] }, "flag-benin": { @@ -20521,7 +20945,8 @@ "bj", "nation", "country", - "banner" + "banner", + "benin" ] }, "flag-st-barthlemy": { @@ -20534,7 +20959,8 @@ "barthélemy", "nation", "country", - "banner" + "banner", + "st_barthelemy" ] }, "flag-bermuda": { @@ -20545,7 +20971,8 @@ "bm", "nation", "country", - "banner" + "banner", + "bermuda" ] }, "flag-brunei": { @@ -20557,7 +20984,8 @@ "darussalam", "nation", "country", - "banner" + "banner", + "brunei" ] }, "flag-bolivia": { @@ -20568,7 +20996,8 @@ "bo", "nation", "country", - "banner" + "banner", + "bolivia" ] }, "flag-caribbean-netherlands": { @@ -20579,7 +21008,8 @@ "bonaire", "nation", "country", - "banner" + "banner", + "caribbean_netherlands" ] }, "flag-brazil": { @@ -20590,7 +21020,8 @@ "br", "nation", "country", - "banner" + "banner", + "brazil" ] }, "flag-bahamas": { @@ -20601,7 +21032,8 @@ "bs", "nation", "country", - "banner" + "banner", + "bahamas" ] }, "flag-bhutan": { @@ -20612,7 +21044,8 @@ "bt", "nation", "country", - "banner" + "banner", + "bhutan" ] }, "flag-bouvet-island": { @@ -20631,7 +21064,8 @@ "bw", "nation", "country", - "banner" + "banner", + "botswana" ] }, "flag-belarus": { @@ -20642,7 +21076,8 @@ "by", "nation", "country", - "banner" + "banner", + "belarus" ] }, "flag-belize": { @@ -20653,7 +21088,8 @@ "bz", "nation", "country", - "banner" + "banner", + "belize" ] }, "flag-canada": { @@ -20664,7 +21100,8 @@ "ca", "nation", "country", - "banner" + "banner", + "canada" ] }, "flag-cocos-keeling-islands": { @@ -20678,7 +21115,8 @@ "islands", "nation", "country", - "banner" + "banner", + "cocos_islands" ] }, "flag-congo--kinshasa": { @@ -20692,7 +21130,8 @@ "republic", "nation", "country", - "banner" + "banner", + "congo_kinshasa" ] }, "flag-central-african-republic": { @@ -20705,7 +21144,8 @@ "republic", "nation", "country", - "banner" + "banner", + "central_african_republic" ] }, "flag-congo--brazzaville": { @@ -20717,7 +21157,8 @@ "congo", "nation", "country", - "banner" + "banner", + "congo_brazzaville" ] }, "flag-switzerland": { @@ -20728,7 +21169,8 @@ "ch", "nation", "country", - "banner" + "banner", + "switzerland" ] }, "flag-cte-divoire": { @@ -20741,7 +21183,8 @@ "coast", "nation", "country", - "banner" + "banner", + "cote_d_ivoire" ] }, "flag-cook-islands": { @@ -20753,7 +21196,8 @@ "islands", "nation", "country", - "banner" + "banner", + "cook_islands" ] }, "flag-chile": { @@ -20763,7 +21207,8 @@ "flag", "nation", "country", - "banner" + "banner", + "chile" ] }, "flag-cameroon": { @@ -20774,7 +21219,8 @@ "cm", "nation", "country", - "banner" + "banner", + "cameroon" ] }, "flag-china": { @@ -20798,7 +21244,8 @@ "co", "nation", "country", - "banner" + "banner", + "colombia" ] }, "flag-clipperton-island": { @@ -20817,7 +21264,8 @@ "rica", "nation", "country", - "banner" + "banner", + "costa_rica" ] }, "flag-cuba": { @@ -20828,7 +21276,8 @@ "cu", "nation", "country", - "banner" + "banner", + "cuba" ] }, "flag-cape-verde": { @@ -20840,7 +21289,8 @@ "verde", "nation", "country", - "banner" + "banner", + "cape_verde" ] }, "flag-curaao": { @@ -20852,7 +21302,8 @@ "curaçao", "nation", "country", - "banner" + "banner", + "curacao" ] }, "flag-christmas-island": { @@ -20864,7 +21315,8 @@ "island", "nation", "country", - "banner" + "banner", + "christmas_island" ] }, "flag-cyprus": { @@ -20875,7 +21327,8 @@ "cy", "nation", "country", - "banner" + "banner", + "cyprus" ] }, "flag-czechia": { @@ -20886,7 +21339,8 @@ "cz", "nation", "country", - "banner" + "banner", + "czechia" ] }, "flag-germany": { @@ -20897,7 +21351,8 @@ "german", "nation", "country", - "banner" + "banner", + "germany" ] }, "flag-diego-garcia": { @@ -20915,7 +21370,8 @@ "dj", "nation", "country", - "banner" + "banner", + "djibouti" ] }, "flag-denmark": { @@ -20926,7 +21382,8 @@ "dk", "nation", "country", - "banner" + "banner", + "denmark" ] }, "flag-dominica": { @@ -20937,7 +21394,8 @@ "dm", "nation", "country", - "banner" + "banner", + "dominica" ] }, "flag-dominican-republic": { @@ -20949,7 +21407,8 @@ "republic", "nation", "country", - "banner" + "banner", + "dominican_republic" ] }, "flag-algeria": { @@ -20960,7 +21419,8 @@ "dz", "nation", "country", - "banner" + "banner", + "algeria" ] }, "flag-ceuta--melilla": { @@ -20979,7 +21439,8 @@ "ec", "nation", "country", - "banner" + "banner", + "ecuador" ] }, "flag-estonia": { @@ -20990,7 +21451,8 @@ "ee", "nation", "country", - "banner" + "banner", + "estonia" ] }, "flag-egypt": { @@ -21001,7 +21463,8 @@ "eg", "nation", "country", - "banner" + "banner", + "egypt" ] }, "flag-western-sahara": { @@ -21013,7 +21476,8 @@ "sahara", "nation", "country", - "banner" + "banner", + "western_sahara" ] }, "flag-eritrea": { @@ -21024,7 +21488,8 @@ "er", "nation", "country", - "banner" + "banner", + "eritrea" ] }, "flag-spain": { @@ -21046,7 +21511,8 @@ "et", "nation", "country", - "banner" + "banner", + "ethiopia" ] }, "flag-european-union": { @@ -21067,7 +21533,8 @@ "fi", "nation", "country", - "banner" + "banner", + "finland" ] }, "flag-fiji": { @@ -21078,7 +21545,8 @@ "fj", "nation", "country", - "banner" + "banner", + "fiji" ] }, "flag-falkland-islands": { @@ -21091,7 +21559,8 @@ "malvinas", "nation", "country", - "banner" + "banner", + "falkland_islands" ] }, "flag-micronesia": { @@ -21116,7 +21585,8 @@ "islands", "nation", "country", - "banner" + "banner", + "faroe_islands" ] }, "flag-france": { @@ -21139,7 +21609,8 @@ "ga", "nation", "country", - "banner" + "banner", + "gabon" ] }, "flag-united-kingdom": { @@ -21160,7 +21631,8 @@ "UK", "english", "england", - "union jack" + "union jack", + "united_kingdom" ] }, "flag-grenada": { @@ -21171,7 +21643,8 @@ "gd", "nation", "country", - "banner" + "banner", + "grenada" ] }, "flag-georgia": { @@ -21182,7 +21655,8 @@ "ge", "nation", "country", - "banner" + "banner", + "georgia" ] }, "flag-french-guiana": { @@ -21194,7 +21668,8 @@ "guiana", "nation", "country", - "banner" + "banner", + "french_guiana" ] }, "flag-guernsey": { @@ -21205,7 +21680,8 @@ "gg", "nation", "country", - "banner" + "banner", + "guernsey" ] }, "flag-ghana": { @@ -21216,7 +21692,8 @@ "gh", "nation", "country", - "banner" + "banner", + "ghana" ] }, "flag-gibraltar": { @@ -21227,7 +21704,8 @@ "gi", "nation", "country", - "banner" + "banner", + "gibraltar" ] }, "flag-greenland": { @@ -21238,7 +21716,8 @@ "gl", "nation", "country", - "banner" + "banner", + "greenland" ] }, "flag-gambia": { @@ -21249,7 +21728,8 @@ "gm", "nation", "country", - "banner" + "banner", + "gambia" ] }, "flag-guinea": { @@ -21260,7 +21740,8 @@ "gn", "nation", "country", - "banner" + "banner", + "guinea" ] }, "flag-guadeloupe": { @@ -21271,7 +21752,8 @@ "gp", "nation", "country", - "banner" + "banner", + "guadeloupe" ] }, "flag-equatorial-guinea": { @@ -21283,7 +21765,8 @@ "gn", "nation", "country", - "banner" + "banner", + "equatorial_guinea" ] }, "flag-greece": { @@ -21294,7 +21777,8 @@ "gr", "nation", "country", - "banner" + "banner", + "greece" ] }, "flag-south-georgia--south-sandwich-islands": { @@ -21309,7 +21793,8 @@ "islands", "nation", "country", - "banner" + "banner", + "south_georgia_south_sandwich_islands" ] }, "flag-guatemala": { @@ -21320,7 +21805,8 @@ "gt", "nation", "country", - "banner" + "banner", + "guatemala" ] }, "flag-guam": { @@ -21331,7 +21817,8 @@ "gu", "nation", "country", - "banner" + "banner", + "guam" ] }, "flag-guineabissau": { @@ -21344,7 +21831,8 @@ "bissau", "nation", "country", - "banner" + "banner", + "guinea_bissau" ] }, "flag-guyana": { @@ -21355,7 +21843,8 @@ "gy", "nation", "country", - "banner" + "banner", + "guyana" ] }, "flag-hong-kong-sar-china": { @@ -21367,7 +21856,8 @@ "kong", "nation", "country", - "banner" + "banner", + "hong_kong_sar_china" ] }, "flag-heard--mcdonald-islands": { @@ -21386,7 +21876,8 @@ "hn", "nation", "country", - "banner" + "banner", + "honduras" ] }, "flag-croatia": { @@ -21397,7 +21888,8 @@ "hr", "nation", "country", - "banner" + "banner", + "croatia" ] }, "flag-haiti": { @@ -21408,7 +21900,8 @@ "ht", "nation", "country", - "banner" + "banner", + "haiti" ] }, "flag-hungary": { @@ -21419,7 +21912,8 @@ "hu", "nation", "country", - "banner" + "banner", + "hungary" ] }, "flag-canary-islands": { @@ -21431,7 +21925,8 @@ "islands", "nation", "country", - "banner" + "banner", + "canary_islands" ] }, "flag-indonesia": { @@ -21441,7 +21936,8 @@ "flag", "nation", "country", - "banner" + "banner", + "indonesia" ] }, "flag-ireland": { @@ -21452,7 +21948,8 @@ "ie", "nation", "country", - "banner" + "banner", + "ireland" ] }, "flag-israel": { @@ -21463,7 +21960,8 @@ "il", "nation", "country", - "banner" + "banner", + "israel" ] }, "flag-isle-of-man": { @@ -21475,7 +21973,8 @@ "man", "nation", "country", - "banner" + "banner", + "isle_of_man" ] }, "flag-india": { @@ -21486,7 +21985,8 @@ "in", "nation", "country", - "banner" + "banner", + "india" ] }, "flag-british-indian-ocean-territory": { @@ -21500,7 +22000,8 @@ "territory", "nation", "country", - "banner" + "banner", + "british_indian_ocean_territory" ] }, "flag-iraq": { @@ -21511,7 +22012,8 @@ "iq", "nation", "country", - "banner" + "banner", + "iraq" ] }, "flag-iran": { @@ -21535,7 +22037,8 @@ "is", "nation", "country", - "banner" + "banner", + "iceland" ] }, "flag-italy": { @@ -21557,7 +22060,8 @@ "je", "nation", "country", - "banner" + "banner", + "jersey" ] }, "flag-jamaica": { @@ -21568,7 +22072,8 @@ "jm", "nation", "country", - "banner" + "banner", + "jamaica" ] }, "flag-jordan": { @@ -21579,7 +22084,8 @@ "jo", "nation", "country", - "banner" + "banner", + "jordan" ] }, "flag-japan": { @@ -21590,7 +22096,8 @@ "japanese", "nation", "country", - "banner" + "banner", + "japan" ] }, "flag-kenya": { @@ -21601,7 +22108,8 @@ "ke", "nation", "country", - "banner" + "banner", + "kenya" ] }, "flag-kyrgyzstan": { @@ -21612,7 +22120,8 @@ "kg", "nation", "country", - "banner" + "banner", + "kyrgyzstan" ] }, "flag-cambodia": { @@ -21623,7 +22132,8 @@ "kh", "nation", "country", - "banner" + "banner", + "cambodia" ] }, "flag-kiribati": { @@ -21634,7 +22144,8 @@ "ki", "nation", "country", - "banner" + "banner", + "kiribati" ] }, "flag-comoros": { @@ -21645,7 +22156,8 @@ "km", "nation", "country", - "banner" + "banner", + "comoros" ] }, "flag-st-kitts--nevis": { @@ -21659,7 +22171,8 @@ "nevis", "nation", "country", - "banner" + "banner", + "st_kitts_nevis" ] }, "flag-north-korea": { @@ -21671,7 +22184,8 @@ "korea", "nation", "country", - "banner" + "banner", + "north_korea" ] }, "flag-south-korea": { @@ -21683,7 +22197,8 @@ "korea", "nation", "country", - "banner" + "banner", + "south_korea" ] }, "flag-kuwait": { @@ -21694,7 +22209,8 @@ "kw", "nation", "country", - "banner" + "banner", + "kuwait" ] }, "flag-cayman-islands": { @@ -21706,7 +22222,8 @@ "islands", "nation", "country", - "banner" + "banner", + "cayman_islands" ] }, "flag-kazakhstan": { @@ -21717,7 +22234,8 @@ "kz", "nation", "country", - "banner" + "banner", + "kazakhstan" ] }, "flag-laos": { @@ -21730,7 +22248,8 @@ "republic", "nation", "country", - "banner" + "banner", + "laos" ] }, "flag-lebanon": { @@ -21741,7 +22260,8 @@ "lb", "nation", "country", - "banner" + "banner", + "lebanon" ] }, "flag-st-lucia": { @@ -21753,7 +22273,8 @@ "lucia", "nation", "country", - "banner" + "banner", + "st_lucia" ] }, "flag-liechtenstein": { @@ -21764,7 +22285,8 @@ "li", "nation", "country", - "banner" + "banner", + "liechtenstein" ] }, "flag-sri-lanka": { @@ -21776,7 +22298,8 @@ "lanka", "nation", "country", - "banner" + "banner", + "sri_lanka" ] }, "flag-liberia": { @@ -21787,7 +22310,8 @@ "lr", "nation", "country", - "banner" + "banner", + "liberia" ] }, "flag-lesotho": { @@ -21798,7 +22322,8 @@ "ls", "nation", "country", - "banner" + "banner", + "lesotho" ] }, "flag-lithuania": { @@ -21809,7 +22334,8 @@ "lt", "nation", "country", - "banner" + "banner", + "lithuania" ] }, "flag-luxembourg": { @@ -21820,7 +22346,8 @@ "lu", "nation", "country", - "banner" + "banner", + "luxembourg" ] }, "flag-latvia": { @@ -21831,7 +22358,8 @@ "lv", "nation", "country", - "banner" + "banner", + "latvia" ] }, "flag-libya": { @@ -21842,7 +22370,8 @@ "ly", "nation", "country", - "banner" + "banner", + "libya" ] }, "flag-morocco": { @@ -21853,7 +22382,8 @@ "ma", "nation", "country", - "banner" + "banner", + "morocco" ] }, "flag-monaco": { @@ -21864,7 +22394,8 @@ "mc", "nation", "country", - "banner" + "banner", + "monaco" ] }, "flag-moldova": { @@ -21887,7 +22418,8 @@ "me", "nation", "country", - "banner" + "banner", + "montenegro" ] }, "flag-st-martin": { @@ -21905,7 +22437,8 @@ "mg", "nation", "country", - "banner" + "banner", + "madagascar" ] }, "flag-marshall-islands": { @@ -21917,7 +22450,8 @@ "islands", "nation", "country", - "banner" + "banner", + "marshall_islands" ] }, "flag-north-macedonia": { @@ -21928,7 +22462,8 @@ "macedonia", "nation", "country", - "banner" + "banner", + "north_macedonia" ] }, "flag-mali": { @@ -21939,7 +22474,8 @@ "ml", "nation", "country", - "banner" + "banner", + "mali" ] }, "flag-myanmar-burma": { @@ -21951,7 +22487,8 @@ "mm", "nation", "country", - "banner" + "banner", + "myanmar" ] }, "flag-mongolia": { @@ -21962,7 +22499,8 @@ "mn", "nation", "country", - "banner" + "banner", + "mongolia" ] }, "flag-macao-sar-china": { @@ -21973,7 +22511,8 @@ "macao", "nation", "country", - "banner" + "banner", + "macao_sar_china" ] }, "flag-northern-mariana-islands": { @@ -21986,7 +22525,8 @@ "islands", "nation", "country", - "banner" + "banner", + "northern_mariana_islands" ] }, "flag-martinique": { @@ -21997,7 +22537,8 @@ "mq", "nation", "country", - "banner" + "banner", + "martinique" ] }, "flag-mauritania": { @@ -22008,7 +22549,8 @@ "mr", "nation", "country", - "banner" + "banner", + "mauritania" ] }, "flag-montserrat": { @@ -22019,7 +22561,8 @@ "ms", "nation", "country", - "banner" + "banner", + "montserrat" ] }, "flag-malta": { @@ -22030,7 +22573,8 @@ "mt", "nation", "country", - "banner" + "banner", + "malta" ] }, "flag-mauritius": { @@ -22041,7 +22585,8 @@ "mu", "nation", "country", - "banner" + "banner", + "mauritius" ] }, "flag-maldives": { @@ -22052,7 +22597,8 @@ "mv", "nation", "country", - "banner" + "banner", + "maldives" ] }, "flag-malawi": { @@ -22063,7 +22609,8 @@ "mw", "nation", "country", - "banner" + "banner", + "malawi" ] }, "flag-mexico": { @@ -22074,7 +22621,8 @@ "mx", "nation", "country", - "banner" + "banner", + "mexico" ] }, "flag-malaysia": { @@ -22085,7 +22633,8 @@ "my", "nation", "country", - "banner" + "banner", + "malaysia" ] }, "flag-mozambique": { @@ -22096,7 +22645,8 @@ "mz", "nation", "country", - "banner" + "banner", + "mozambique" ] }, "flag-namibia": { @@ -22107,7 +22657,8 @@ "na", "nation", "country", - "banner" + "banner", + "namibia" ] }, "flag-new-caledonia": { @@ -22119,7 +22670,8 @@ "caledonia", "nation", "country", - "banner" + "banner", + "new_caledonia" ] }, "flag-niger": { @@ -22130,7 +22682,8 @@ "ne", "nation", "country", - "banner" + "banner", + "niger" ] }, "flag-norfolk-island": { @@ -22142,7 +22695,8 @@ "island", "nation", "country", - "banner" + "banner", + "norfolk_island" ] }, "flag-nigeria": { @@ -22152,7 +22706,8 @@ "flag", "nation", "country", - "banner" + "banner", + "nigeria" ] }, "flag-nicaragua": { @@ -22163,7 +22718,8 @@ "ni", "nation", "country", - "banner" + "banner", + "nicaragua" ] }, "flag-netherlands": { @@ -22174,7 +22730,8 @@ "nl", "nation", "country", - "banner" + "banner", + "netherlands" ] }, "flag-norway": { @@ -22185,7 +22742,8 @@ "no", "nation", "country", - "banner" + "banner", + "norway" ] }, "flag-nepal": { @@ -22196,7 +22754,8 @@ "np", "nation", "country", - "banner" + "banner", + "nepal" ] }, "flag-nauru": { @@ -22207,7 +22766,8 @@ "nr", "nation", "country", - "banner" + "banner", + "nauru" ] }, "flag-niue": { @@ -22218,7 +22778,8 @@ "nu", "nation", "country", - "banner" + "banner", + "niue" ] }, "flag-new-zealand": { @@ -22230,7 +22791,8 @@ "zealand", "nation", "country", - "banner" + "banner", + "new_zealand" ] }, "flag-oman": { @@ -22241,7 +22803,8 @@ "om_symbol", "nation", "country", - "banner" + "banner", + "oman" ] }, "flag-panama": { @@ -22252,7 +22815,8 @@ "pa", "nation", "country", - "banner" + "banner", + "panama" ] }, "flag-peru": { @@ -22263,7 +22827,8 @@ "pe", "nation", "country", - "banner" + "banner", + "peru" ] }, "flag-french-polynesia": { @@ -22275,7 +22840,8 @@ "polynesia", "nation", "country", - "banner" + "banner", + "french_polynesia" ] }, "flag-papua-new-guinea": { @@ -22288,7 +22854,8 @@ "guinea", "nation", "country", - "banner" + "banner", + "papua_new_guinea" ] }, "flag-philippines": { @@ -22299,7 +22866,8 @@ "ph", "nation", "country", - "banner" + "banner", + "philippines" ] }, "flag-pakistan": { @@ -22310,7 +22878,8 @@ "pk", "nation", "country", - "banner" + "banner", + "pakistan" ] }, "flag-poland": { @@ -22321,7 +22890,8 @@ "pl", "nation", "country", - "banner" + "banner", + "poland" ] }, "flag-st-pierre--miquelon": { @@ -22335,7 +22905,8 @@ "miquelon", "nation", "country", - "banner" + "banner", + "st_pierre_miquelon" ] }, "flag-pitcairn-islands": { @@ -22346,7 +22917,8 @@ "pitcairn", "nation", "country", - "banner" + "banner", + "pitcairn_islands" ] }, "flag-puerto-rico": { @@ -22358,7 +22930,8 @@ "rico", "nation", "country", - "banner" + "banner", + "puerto_rico" ] }, "flag-palestinian-territories": { @@ -22371,7 +22944,8 @@ "territories", "nation", "country", - "banner" + "banner", + "palestinian_territories" ] }, "flag-portugal": { @@ -22382,7 +22956,8 @@ "pt", "nation", "country", - "banner" + "banner", + "portugal" ] }, "flag-palau": { @@ -22393,7 +22968,8 @@ "pw", "nation", "country", - "banner" + "banner", + "palau" ] }, "flag-paraguay": { @@ -22404,7 +22980,8 @@ "py", "nation", "country", - "banner" + "banner", + "paraguay" ] }, "flag-qatar": { @@ -22415,7 +22992,8 @@ "qa", "nation", "country", - "banner" + "banner", + "qatar" ] }, "flag-runion": { @@ -22427,7 +23005,8 @@ "réunion", "nation", "country", - "banner" + "banner", + "reunion" ] }, "flag-romania": { @@ -22438,7 +23017,8 @@ "ro", "nation", "country", - "banner" + "banner", + "romania" ] }, "flag-serbia": { @@ -22449,7 +23029,8 @@ "rs", "nation", "country", - "banner" + "banner", + "serbia" ] }, "flag-russia": { @@ -22461,7 +23042,8 @@ "federation", "nation", "country", - "banner" + "banner", + "russia" ] }, "flag-rwanda": { @@ -22472,7 +23054,8 @@ "rw", "nation", "country", - "banner" + "banner", + "rwanda" ] }, "flag-saudi-arabia": { @@ -22482,7 +23065,8 @@ "flag", "nation", "country", - "banner" + "banner", + "saudi_arabia" ] }, "flag-solomon-islands": { @@ -22494,7 +23078,8 @@ "islands", "nation", "country", - "banner" + "banner", + "solomon_islands" ] }, "flag-seychelles": { @@ -22505,7 +23090,8 @@ "sc", "nation", "country", - "banner" + "banner", + "seychelles" ] }, "flag-sudan": { @@ -22516,7 +23102,8 @@ "sd", "nation", "country", - "banner" + "banner", + "sudan" ] }, "flag-sweden": { @@ -22527,7 +23114,8 @@ "se", "nation", "country", - "banner" + "banner", + "sweden" ] }, "flag-singapore": { @@ -22538,7 +23126,8 @@ "sg", "nation", "country", - "banner" + "banner", + "singapore" ] }, "flag-st-helena": { @@ -22553,7 +23142,8 @@ "cunha", "nation", "country", - "banner" + "banner", + "st_helena" ] }, "flag-slovenia": { @@ -22564,7 +23154,8 @@ "si", "nation", "country", - "banner" + "banner", + "slovenia" ] }, "flag-svalbard--jan-mayen": { @@ -22583,7 +23174,8 @@ "sk", "nation", "country", - "banner" + "banner", + "slovakia" ] }, "flag-sierra-leone": { @@ -22595,7 +23187,8 @@ "leone", "nation", "country", - "banner" + "banner", + "sierra_leone" ] }, "flag-san-marino": { @@ -22607,7 +23200,8 @@ "marino", "nation", "country", - "banner" + "banner", + "san_marino" ] }, "flag-senegal": { @@ -22618,7 +23212,8 @@ "sn", "nation", "country", - "banner" + "banner", + "senegal" ] }, "flag-somalia": { @@ -22629,7 +23224,8 @@ "so", "nation", "country", - "banner" + "banner", + "somalia" ] }, "flag-suriname": { @@ -22640,7 +23236,8 @@ "sr", "nation", "country", - "banner" + "banner", + "suriname" ] }, "flag-south-sudan": { @@ -22652,7 +23249,8 @@ "sd", "nation", "country", - "banner" + "banner", + "south_sudan" ] }, "flag-so-tom--prncipe": { @@ -22666,7 +23264,8 @@ "principe", "nation", "country", - "banner" + "banner", + "sao_tome_principe" ] }, "flag-el-salvador": { @@ -22678,7 +23277,8 @@ "salvador", "nation", "country", - "banner" + "banner", + "el_salvador" ] }, "flag-sint-maarten": { @@ -22691,7 +23291,8 @@ "dutch", "nation", "country", - "banner" + "banner", + "sint_maarten" ] }, "flag-syria": { @@ -22704,7 +23305,8 @@ "republic", "nation", "country", - "banner" + "banner", + "syria" ] }, "flag-eswatini": { @@ -22715,7 +23317,8 @@ "sz", "nation", "country", - "banner" + "banner", + "eswatini" ] }, "flag-tristan-da-cunha": { @@ -22736,7 +23339,8 @@ "islands", "nation", "country", - "banner" + "banner", + "turks_caicos_islands" ] }, "flag-chad": { @@ -22747,7 +23351,8 @@ "td", "nation", "country", - "banner" + "banner", + "chad" ] }, "flag-french-southern-territories": { @@ -22760,7 +23365,8 @@ "territories", "nation", "country", - "banner" + "banner", + "french_southern_territories" ] }, "flag-togo": { @@ -22771,7 +23377,8 @@ "tg", "nation", "country", - "banner" + "banner", + "togo" ] }, "flag-thailand": { @@ -22782,7 +23389,8 @@ "th", "nation", "country", - "banner" + "banner", + "thailand" ] }, "flag-tajikistan": { @@ -22793,7 +23401,8 @@ "tj", "nation", "country", - "banner" + "banner", + "tajikistan" ] }, "flag-tokelau": { @@ -22804,7 +23413,8 @@ "tk", "nation", "country", - "banner" + "banner", + "tokelau" ] }, "flag-timorleste": { @@ -22817,7 +23427,8 @@ "leste", "nation", "country", - "banner" + "banner", + "timor_leste" ] }, "flag-turkmenistan": { @@ -22827,7 +23438,8 @@ "flag", "nation", "country", - "banner" + "banner", + "turkmenistan" ] }, "flag-tunisia": { @@ -22838,7 +23450,8 @@ "tn", "nation", "country", - "banner" + "banner", + "tunisia" ] }, "flag-tonga": { @@ -22849,7 +23462,8 @@ "to", "nation", "country", - "banner" + "banner", + "tonga" ] }, "flag-turkey": { @@ -22873,7 +23487,8 @@ "tobago", "nation", "country", - "banner" + "banner", + "trinidad_tobago" ] }, "flag-tuvalu": { @@ -22883,7 +23498,8 @@ "flag", "nation", "country", - "banner" + "banner", + "tuvalu" ] }, "flag-taiwan": { @@ -22894,7 +23510,8 @@ "tw", "nation", "country", - "banner" + "banner", + "taiwan" ] }, "flag-tanzania": { @@ -22918,7 +23535,8 @@ "ua", "nation", "country", - "banner" + "banner", + "ukraine" ] }, "flag-uganda": { @@ -22929,7 +23547,8 @@ "ug", "nation", "country", - "banner" + "banner", + "uganda" ] }, "flag-us-outlying-islands": { @@ -22959,7 +23578,8 @@ "america", "nation", "country", - "banner" + "banner", + "united_states" ] }, "flag-uruguay": { @@ -22970,7 +23590,8 @@ "uy", "nation", "country", - "banner" + "banner", + "uruguay" ] }, "flag-uzbekistan": { @@ -22981,7 +23602,8 @@ "uz", "nation", "country", - "banner" + "banner", + "uzbekistan" ] }, "flag-vatican-city": { @@ -22993,7 +23615,8 @@ "city", "nation", "country", - "banner" + "banner", + "vatican_city" ] }, "flag-st-vincent--grenadines": { @@ -23007,7 +23630,8 @@ "grenadines", "nation", "country", - "banner" + "banner", + "st_vincent_grenadines" ] }, "flag-venezuela": { @@ -23020,7 +23644,8 @@ "republic", "nation", "country", - "banner" + "banner", + "venezuela" ] }, "flag-british-virgin-islands": { @@ -23034,7 +23659,8 @@ "bvi", "nation", "country", - "banner" + "banner", + "british_virgin_islands" ] }, "flag-us-virgin-islands": { @@ -23048,7 +23674,8 @@ "us", "nation", "country", - "banner" + "banner", + "u_s_virgin_islands" ] }, "flag-vietnam": { @@ -23060,7 +23687,8 @@ "nam", "nation", "country", - "banner" + "banner", + "vietnam" ] }, "flag-vanuatu": { @@ -23071,7 +23699,8 @@ "vu", "nation", "country", - "banner" + "banner", + "vanuatu" ] }, "flag-wallis--futuna": { @@ -23084,7 +23713,8 @@ "futuna", "nation", "country", - "banner" + "banner", + "wallis_futuna" ] }, "flag-samoa": { @@ -23095,7 +23725,8 @@ "ws", "nation", "country", - "banner" + "banner", + "samoa" ] }, "flag-kosovo": { @@ -23106,7 +23737,8 @@ "xk", "nation", "country", - "banner" + "banner", + "kosovo" ] }, "flag-yemen": { @@ -23117,7 +23749,8 @@ "ye", "nation", "country", - "banner" + "banner", + "yemen" ] }, "flag-mayotte": { @@ -23128,7 +23761,8 @@ "yt", "nation", "country", - "banner" + "banner", + "mayotte" ] }, "flag-south-africa": { @@ -23140,7 +23774,8 @@ "africa", "nation", "country", - "banner" + "banner", + "south_africa" ] }, "flag-zambia": { @@ -23151,7 +23786,8 @@ "zm", "nation", "country", - "banner" + "banner", + "zambia" ] }, "flag-zimbabwe": { @@ -23162,7 +23798,8 @@ "zw", "nation", "country", - "banner" + "banner", + "zimbabwe" ] }, "flag-england": { diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index c2def98ebc..8843e7759a 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","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","hugging-face","face-with-hand-over-mouth","shushing-face","thinking-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","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","knockedout-face","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","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","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","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","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-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","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","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","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","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","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","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"]},{"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","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","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","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","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","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","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","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","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","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","stethoscope","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","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard"]},{"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","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"]},"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"]},"hugging-face":{"a":"Hugging Face","b":"1F917","j":["face","hug","hugging","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"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"]},"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"]},"face-in-clouds":{"a":"⊛ Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in clouds","face in the fog","head in clouds"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"⊛ Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","face exhaling","gasp","groan","relief","whisper","whistle"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"knockedout-face":{"a":"Knocked-out Face","b":"1F635","j":["dead","face","knocked out","knocked-out face","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"⊛ Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","face with spiral eyes","hypnotized","spiral","trouble","whoa"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"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"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"⊛ Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","heart on fire","love","lust","sacred heart"]},"mending-heart":{"a":"⊛ Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","mending heart","recovering","recuperating","well"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"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"]},"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"]},"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"]},"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"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"⊛ Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard"]},"woman-beard":{"a":"⊛ Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"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"]},"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"]},"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":["bear","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"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"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"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"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"]},"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"]},"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","busstop","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"]},"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"]},"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"]},"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"]},"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"]},"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"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"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"]},"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"]},"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"]},"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"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner"]},"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"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner"]},"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"]},"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"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner"]},"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"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner"]},"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"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner"]},"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"]},"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"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner"]},"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"]},"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"]},"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"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner"]},"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"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner"]},"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"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner"]},"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"]},"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"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner"]},"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"]},"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"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner"]},"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"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner"]},"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"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner"]},"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"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner"]},"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"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner"]},"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"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner"]},"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"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner"]},"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"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file +{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","melting-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","smiling-face-with-open-hands","face-with-hand-over-mouth","face-with-open-eyes-and-hand-over-mouth","face-with-peeking-eye","shushing-face","thinking-face","saluting-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","dotted-line-face","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","face-with-crossedout-eyes","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","face-with-diagonal-mouth","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","face-holding-back-tears","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","rightwards-hand","leftwards-hand","palm-down-hand","palm-up-hand","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","hand-with-index-finger-and-thumb-crossed","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","index-pointing-at-the-viewer","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","heart-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","biting-lip","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","person-with-crown","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","pregnant-man","pregnant-person","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","troll","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","lotus","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind","empty-nest","nest-with-eggs"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","pouring-liquid","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","jar","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","playground-slide","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","wheel","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","ring-buoy","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","hamsa","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","mirror-ball","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","low-battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","crutch","stethoscope","xray","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","bubbles","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard","identification-card"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","heavy-equals-sign","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"melting-face":{"a":"⊛ Melting Face","b":"1FAE0","j":["disappear","dissolve","liquid","melt"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"smiling-face-with-open-hands":{"a":"Smiling Face with Open Hands","b":"1F917","j":["face","hug","hugging","open hands","smiling face","hugging_face","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"face-with-open-eyes-and-hand-over-mouth":{"a":"⊛ Face with Open Eyes and Hand over Mouth","b":"1FAE2","j":["amazement","awe","disbelief","embarrass","scared","surprise"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"saluting-face":{"a":"⊛ Saluting Face","b":"1FAE1","j":["ok","salute","sunny","troops","yes"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"dotted-line-face":{"a":"⊛ Dotted Line Face","b":"1FAE5","j":["depressed","disappear","hide","introvert","invisible"]},"face-in-clouds":{"a":"Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in clouds","face in the fog","head in clouds"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","face exhaling","gasp","groan","relief","whisper","whistle"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"face-with-crossedout-eyes":{"a":"Face with Crossed-out Eyes","b":"1F635","j":["crossed-out eyes","dead","face","face with crossed-out eyes","knocked out","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","face with spiral eyes","hypnotized","spiral","trouble","whoa"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"face-with-diagonal-mouth":{"a":"⊛ Face with Diagonal Mouth","b":"1FAE4","j":["disappointed","meh","skeptical","unsure"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"face-holding-back-tears":{"a":"⊛ Face Holding Back Tears","b":"1F979","j":["angry","cry","proud","resist","sad"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","heart on fire","love","lust","sacred heart"]},"mending-heart":{"a":"Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","mending heart","recovering","recuperating","well"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"rightwards-hand":{"a":"⊛ Rightwards Hand","b":"1FAF1","j":["hand","right","rightward"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"hand-with-index-finger-and-thumb-crossed":{"a":"⊛ Hand with Index Finger and Thumb Crossed","b":"1FAF0","j":["expensive","heart","love","money","snap"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"index-pointing-at-the-viewer":{"a":"⊛ Index Pointing at the Viewer","b":"1FAF5","j":["point","you"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"heart-hands":{"a":"⊛ Heart Hands","b":"1FAF6","j":["love"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"biting-lip":{"a":"⊛ Biting Lip","b":"1FAE6","j":["anxious","fear","flirting","nervous","uncomfortable","worried"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard"]},"woman-beard":{"a":"Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"person-with-crown":{"a":"⊛ Person with Crown","b":"1FAC5","j":["monarch","noble","regal","royalty"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"pregnant-man":{"a":"⊛ Pregnant Man","b":"1FAC3","j":["belly","bloated","full","pregnant"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"troll":{"a":"⊛ Troll","b":"1F9CC","j":["fairy tale","fantasy","monster"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["face","marsupial","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"coral":{"a":"⊛ Coral","b":"1FAB8","j":["ocean","reef"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"lotus":{"a":"⊛ Lotus","b":"1FAB7","j":["Buddhism","flower","Hinduism","India","purity","Vietnam"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"empty-nest":{"a":"⊛ Empty Nest","b":"1FAB9","j":["nesting"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"beans":{"a":"⊛ Beans","b":"1FAD8","j":["food","kidney","legume"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"pouring-liquid":{"a":"⊛ Pouring Liquid","b":"1FAD7","j":["drink","empty","glass","spill"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"jar":{"a":"⊛ Jar","b":"1FAD9","j":["condiment","container","empty","sauce","store"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"playground-slide":{"a":"⊛ Playground Slide","b":"1F6DD","j":["amusement park","play"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"wheel":{"a":"⊛ Wheel","b":"1F6DE","j":["circle","tire","turn"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"ring-buoy":{"a":"⊛ Ring Buoy","b":"1F6DF","j":["float","life preserver","life saver","rescue","safety"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"hamsa":{"a":"⊛ Hamsa","b":"1FAAC","j":["amulet","Fatima","hand","Mary","Miriam","protection"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"mirror-ball":{"a":"⊛ Mirror Ball","b":"1FAA9","j":["dance","disco","glitter","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"low-battery":{"a":"⊛ Low Battery","b":"1FAAB","j":["electronic","low energy"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"crutch":{"a":"⊛ Crutch","b":"1FA7C","j":["cane","disability","hurt","mobility aid","stick"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"bubbles":{"a":"⊛ Bubbles","b":"1FAE7","j":["burp","clean","soap","underwater"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"identification-card":{"a":"⊛ Identification Card","b":"1FAAA","j":["credentials","ID","license","security"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"heavy-equals-sign":{"a":"⊛ Heavy Equals Sign","b":"1F7F0","j":["equality","math"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner","andorra"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner","united_arab_emirates"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner","afghanistan"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner","antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner","anguilla"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner","albania"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner","armenia"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner","angola"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner","antarctica"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner","argentina"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner","american_samoa"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner","austria"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner","australia"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner","aruba"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner","aland_islands"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner","azerbaijan"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner","bosnia_herzegovina"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner","barbados"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner","bangladesh"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner","belgium"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner","burkina_faso"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner","bulgaria"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner","bahrain"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner","burundi"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner","benin"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner","st_barthelemy"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner","bermuda"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner","brunei"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner","bolivia"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner","caribbean_netherlands"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner","brazil"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner","bahamas"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner","bhutan"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner","botswana"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner","belarus"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner","belize"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner","canada"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner","cocos_islands"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner","congo_kinshasa"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner","central_african_republic"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner","congo_brazzaville"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner","switzerland"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner","cote_d_ivoire"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner","cook_islands"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner","chile"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner","cameroon"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner","colombia"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner","costa_rica"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner","cuba"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner","cape_verde"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner","curacao"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner","christmas_island"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner","cyprus"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner","czechia"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner","germany"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner","djibouti"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner","denmark"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner","dominica"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner","dominican_republic"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner","algeria"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner","ecuador"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner","estonia"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner","egypt"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner","western_sahara"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner","eritrea"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner","ethiopia"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner","finland"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner","fiji"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner","falkland_islands"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner","faroe_islands"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner","gabon"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack","united_kingdom"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner","grenada"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner","georgia"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner","french_guiana"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner","guernsey"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner","ghana"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner","gibraltar"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner","greenland"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner","gambia"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner","guinea"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner","guadeloupe"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner","equatorial_guinea"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner","greece"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner","south_georgia_south_sandwich_islands"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner","guatemala"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner","guam"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner","guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner","guyana"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner","hong_kong_sar_china"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner","honduras"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner","croatia"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner","haiti"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner","hungary"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner","canary_islands"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner","indonesia"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner","ireland"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner","israel"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner","isle_of_man"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner","india"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner","british_indian_ocean_territory"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner","iraq"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner","iceland"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner","jersey"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner","jamaica"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner","jordan"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner","japan"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner","kenya"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner","kyrgyzstan"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner","cambodia"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner","kiribati"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner","comoros"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner","st_kitts_nevis"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner","north_korea"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner","south_korea"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner","kuwait"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner","cayman_islands"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner","kazakhstan"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner","laos"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner","lebanon"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner","st_lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner","liechtenstein"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner","sri_lanka"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner","liberia"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner","lesotho"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner","lithuania"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner","luxembourg"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner","latvia"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner","libya"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner","morocco"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner","monaco"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner","montenegro"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner","madagascar"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner","marshall_islands"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner","north_macedonia"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner","mali"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner","myanmar"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner","mongolia"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner","macao_sar_china"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner","northern_mariana_islands"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner","martinique"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner","mauritania"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner","montserrat"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner","malta"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner","mauritius"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner","maldives"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner","malawi"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner","mexico"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner","malaysia"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner","mozambique"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner","namibia"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner","new_caledonia"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner","niger"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner","norfolk_island"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner","nigeria"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner","nicaragua"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner","netherlands"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner","norway"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner","nepal"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner","nauru"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner","niue"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner","new_zealand"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner","oman"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner","panama"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner","peru"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner","french_polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner","papua_new_guinea"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner","philippines"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner","pakistan"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner","poland"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner","st_pierre_miquelon"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner","pitcairn_islands"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner","puerto_rico"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner","palestinian_territories"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner","portugal"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner","palau"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner","paraguay"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner","qatar"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner","reunion"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner","romania"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner","serbia"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner","russia"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner","rwanda"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner","saudi_arabia"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner","solomon_islands"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner","seychelles"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner","sudan"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner","sweden"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner","singapore"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner","st_helena"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner","slovenia"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner","slovakia"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner","sierra_leone"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner","san_marino"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner","senegal"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner","somalia"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner","suriname"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner","south_sudan"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner","sao_tome_principe"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner","el_salvador"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner","sint_maarten"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner","syria"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner","eswatini"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner","turks_caicos_islands"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner","chad"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner","french_southern_territories"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner","togo"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner","thailand"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner","tajikistan"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner","tokelau"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner","timor_leste"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner","turkmenistan"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner","tunisia"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner","tonga"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner","trinidad_tobago"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner","tuvalu"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner","taiwan"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner","ukraine"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner","uganda"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner","united_states"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner","uruguay"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner","uzbekistan"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner","vatican_city"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner","st_vincent_grenadines"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner","venezuela"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner","british_virgin_islands"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner","u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner","vietnam"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner","vanuatu"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner","wallis_futuna"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner","samoa"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner","kosovo"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner","yemen"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner","mayotte"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner","south_africa"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner","zambia"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner","zimbabwe"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file From 6cee8871f3a618cebab2b5d5dd065d983743a048 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 11 Oct 2021 16:34:32 +0300 Subject: [PATCH 042/172] Create a new cron Github Action workflow for syncing emojis & sas strings. It will run every Monday at 00:00. It will open two PRs and will be able to optimal update/delete them according to changes with the base branch --- .../workflows/sync-from-external-soruces.yml | 70 +++++++++++++++++++ changelog.d/4216.misc | 1 + 2 files changed, 71 insertions(+) create mode 100644 .github/workflows/sync-from-external-soruces.yml create mode 100644 changelog.d/4216.misc diff --git a/.github/workflows/sync-from-external-soruces.yml b/.github/workflows/sync-from-external-soruces.yml new file mode 100644 index 0000000000..d5207d4b96 --- /dev/null +++ b/.github/workflows/sync-from-external-soruces.yml @@ -0,0 +1,70 @@ +name: Sync Data From External Sources +on: + schedule: + # At 00:00 on every Monday UTC + - cron: '0 0 * * 1' + +jobs: + sync-emojis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Prerequisite dependencies + run: | + pip install BeautifulSoup4 + pip install requests + - name: Run Emoji script + run: ./tools/import_emojis.py + - name: Create Pull Request for Emojis + uses: peter-evans/create-pull-request@v3 + with: + commit-message: Sync Emojis + title: Sync Emojis + body: | + - Update Emojis from Unicode.org + branch: sync-emojis + base: develop + + sync-sas-strings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + - name: Install Prerequisite dependencies + run: | + pip install BeautifulSoup4 + pip install requests + - name: Run SAS String script + run: ./tools/import_sas_strings.py + - name: Create Pull Request for SAS Strings + uses: peter-evans/create-pull-request@v3 + with: + commit-message: Sync SAS Strings + title: Sync SAS Strings + body: | + - Update SAS Strings from matrix-doc. + branch: sync-sas-strings + base: develop \ No newline at end of file diff --git a/changelog.d/4216.misc b/changelog.d/4216.misc new file mode 100644 index 0000000000..7a3ec23ee5 --- /dev/null +++ b/changelog.d/4216.misc @@ -0,0 +1 @@ +Implement a new github action workflow to generate two PRs for emoji and sas string sync, issue 3902 | PR 4216 From a26e43e90cfe5c09f6b0edafada369c6f1948451 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 11 Oct 2021 17:31:27 +0200 Subject: [PATCH 043/172] Mavericks 2: clean after PR review --- changelog.d/3890.misc | 1 + docs/mavericks_migration.md | 11 +++++++++++ .../java/org/matrix/android/sdk/flow/FlowExt.kt | 13 ++++++------- .../java/org/matrix/android/sdk/flow/FlowSession.kt | 6 +++--- .../vector/app/core/platform/VectorBaseActivity.kt | 4 ++-- .../vector/app/core/platform/VectorBaseFragment.kt | 4 ++-- .../crypto/quads/SharedSecureStorageViewModel.kt | 1 - .../vector/app/features/home/HomeDetailFragment.kt | 4 ++-- .../login2/created/AccountCreatedFragment.kt | 2 +- .../spaces/create/ChoosePrivateSpaceTypeFragment.kt | 2 +- .../app/features/userdirectory/UserListFragment.kt | 2 +- 11 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 changelog.d/3890.misc create mode 100644 docs/mavericks_migration.md diff --git a/changelog.d/3890.misc b/changelog.d/3890.misc new file mode 100644 index 0000000000..3bace80fa7 --- /dev/null +++ b/changelog.d/3890.misc @@ -0,0 +1 @@ +Migrate to MvRx2 (Mavericks) \ No newline at end of file diff --git a/docs/mavericks_migration.md b/docs/mavericks_migration.md new file mode 100644 index 0000000000..a36ae8261a --- /dev/null +++ b/docs/mavericks_migration.md @@ -0,0 +1,11 @@ +Useful links: +- https://airbnb.io/mavericks/#/new-2x + +Mavericks 2 is replacing MvRx, by removing usage of Rx by Flow, both internally and in the API. +See the link ^ to have more intel, but basically, the changes are: + +session.rx() => session.flow() +room.rx() => room.flow() +subscribe { }.disposeOnClear() => onEach { }.launchIn(viewModelScope) + +Only using manually onEach requires to add launchIn,any other methods provided by Mavericks on viewModel and activity/fragment are already taking care of lifecycle. \ No newline at end of file diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt index dd8a5d7750..f974d89d45 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt @@ -22,11 +22,10 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext internal fun Flow.startWith(supplier: suspend () -> T): Flow { - return this - .onStart { - val value = withContext(Dispatchers.IO) { - supplier() - } - emit(value) - } + return onStart { + val value = withContext(Dispatchers.IO) { + supplier() + } + emit(value) + } } 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 563ae30b45..75f482adfd 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 @@ -42,7 +42,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo -class RxFlow(private val session: Session) { +class FlowSession(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { return session.getRoomSummariesLive(queryParams).asFlow() @@ -175,6 +175,6 @@ class RxFlow(private val session: Session) { } } -fun Session.flow(): RxFlow { - return RxFlow(this) +fun Session.flow(): FlowSession { + return FlowSession(this) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index e9973e82b8..fbfba10d21 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -40,7 +40,7 @@ import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.MvRxView +import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.snackbar.Snackbar @@ -89,7 +89,7 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis -abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector, MvRxView { +abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector, MavericksView { /* ========================================================================================== * View * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 5077982460..64b55291fb 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -30,7 +30,7 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -import com.airbnb.mvrx.MvRxView +import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -50,7 +50,7 @@ import io.reactivex.disposables.Disposable import timber.log.Timber import java.util.concurrent.TimeUnit -abstract class VectorBaseFragment : Fragment(), MvRxView, HasScreenInjector { +abstract class VectorBaseFragment : Fragment(), MavericksView, HasScreenInjector { protected val vectorBaseActivity: VectorBaseActivity<*> by lazy { activity as VectorBaseActivity<*> diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index e175715710..bb4d4ce99a 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.crypto.quads -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index a69b9060d6..c8fff5605b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -161,7 +161,7 @@ class HomeDetailFragment @Inject constructor( } } - unknownDeviceDetectorSharedViewModel.subscribe { state -> + unknownDeviceDetectorSharedViewModel.onEach { state -> state.unknownSessions.invoke()?.let { unknownDevices -> // Timber.v("## Detector Triggerred in fragment - ${unknownDevices.firstOrNull()}") if (unknownDevices.firstOrNull()?.currentSessionTrust == true) { @@ -179,7 +179,7 @@ class HomeDetailFragment @Inject constructor( } } - unreadMessagesSharedViewModel.subscribe { state -> + unreadMessagesSharedViewModel.onEach { state -> views.drawerUnreadCounterBadgeView.render( UnreadCounterBadgeView.State( count = state.otherSpacesUnread.totalCount, diff --git a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt index e3d25ef283..5668214b50 100644 --- a/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login2/created/AccountCreatedFragment.kt @@ -72,7 +72,7 @@ class AccountCreatedFragment @Inject constructor( setupSubmitButton() observeViewEvents() - viewModel.subscribe { invalidateState(it) } + viewModel.onEach { invalidateState(it) } views.loginAccountCreatedTime.text = dateFormatter.format(System.currentTimeMillis(), DateFormatKind.MESSAGE_SIMPLE) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt index 4f079551eb..6d3003dfcf 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChoosePrivateSpaceTypeFragment.kt @@ -49,7 +49,7 @@ class ChoosePrivateSpaceTypeFragment @Inject constructor( sharedViewModel.handle(CreateSpaceAction.SetSpaceTopology(SpaceTopology.MeAndTeammates)) } - sharedViewModel.subscribe { state -> + sharedViewModel.onEach { state -> views.accessInfoHelpText.text = stringProvider.getString(R.string.create_spaces_make_sure_access, state.name ?: "") } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 8d649eaa71..8adabf9f02 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -81,7 +81,7 @@ class UserListFragment @Inject constructor( setupRecyclerView() setupSearchView() - homeServerCapabilitiesViewModel.subscribe { + homeServerCapabilitiesViewModel.onEach { views.userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } From 42cbdf0a6c15dfa5fb0b8e8c915c8a73315bfb9d Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Mon, 11 Oct 2021 14:27:08 -0500 Subject: [PATCH 044/172] Dont set person on sent message notification Signed-off-by: Alex Baker --- changelog.d/4221.bugfix | 1 + .../notifications/NotificationDrawerManager.kt | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelog.d/4221.bugfix diff --git a/changelog.d/4221.bugfix b/changelog.d/4221.bugfix new file mode 100644 index 0000000000..76c66898b1 --- /dev/null +++ b/changelog.d/4221.bugfix @@ -0,0 +1 @@ +Fix conversation notification for sent messages diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 37de7b1a75..7d75fd27d5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -325,11 +325,15 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } roomEventGroupInfo.hasNewEvent = roomEventGroupInfo.hasNewEvent || !event.hasBeenDisplayed - val senderPerson = Person.Builder() - .setName(event.senderName) - .setIcon(iconLoader.getUserIcon(event.senderAvatarPath)) - .setKey(event.senderId) - .build() + val senderPerson = if (event.outGoingMessage) { + null + } else { + Person.Builder() + .setName(event.senderName) + .setIcon(iconLoader.getUserIcon(event.senderAvatarPath)) + .setKey(event.senderId) + .build() + } if (event.outGoingMessage && event.outGoingMessageFailed) { style.addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson) From 3265c604cfa56cb44f44e7b192d34b04c814511d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 23:05:08 +0000 Subject: [PATCH 045/172] Bump gradle from 7.0.2 to 7.0.3 Bumps gradle from 7.0.2 to 7.0.3. --- updated-dependencies: - dependency-name: com.android.tools.build:gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 92358952db..882a4fde09 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.0.2" +def gradle = "7.0.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.5.31" def kotlinCoroutines = "1.5.2" From dcf98d93e637f4195080613e7164531a4adad522 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 12 Oct 2021 11:37:00 +0300 Subject: [PATCH 046/172] Remove BeautifulSoup4 dependency --- .github/workflows/sync-from-external-soruces.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sync-from-external-soruces.yml b/.github/workflows/sync-from-external-soruces.yml index d5207d4b96..6a4f8ef147 100644 --- a/.github/workflows/sync-from-external-soruces.yml +++ b/.github/workflows/sync-from-external-soruces.yml @@ -55,7 +55,6 @@ jobs: ${{ runner.os }}- - name: Install Prerequisite dependencies run: | - pip install BeautifulSoup4 pip install requests - name: Run SAS String script run: ./tools/import_sas_strings.py From 0d85299c5748bd3c85e29cafc7134431ed525587 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 8 Oct 2021 20:23:16 +0200 Subject: [PATCH 047/172] Try to fix #4007 Wait for Realm instance to be effectively closed before deleting Realm files --- .../session/cleanup/CleanupSession.kt | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt index d4374e0702..113e0f218a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.cleanup import io.realm.Realm import io.realm.RealmConfiguration -import org.matrix.android.sdk.BuildConfig +import kotlinx.coroutines.delay import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.crypto.CryptoModule @@ -51,6 +51,10 @@ internal class CleanupSession @Inject constructor( @UserMd5 private val userMd5: String ) { suspend fun handle() { + val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration) + val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration) + Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)") + Timber.d("Cleanup: delete session params...") sessionParamsStore.delete(sessionId) @@ -63,10 +67,6 @@ internal class CleanupSession @Inject constructor( Timber.d("Cleanup: clear crypto data...") clearCryptoDataTask.execute(Unit) - Timber.d("Cleanup: clear file system") - sessionFiles.deleteRecursively() - sessionCache.deleteRecursively() - Timber.d("Cleanup: clear the database keys") realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) @@ -74,14 +74,33 @@ internal class CleanupSession @Inject constructor( Timber.d("Cleanup: release session...") sessionManager.releaseSession(sessionId) - // Sanity check - if (BuildConfig.DEBUG) { - Realm.getGlobalInstanceCount(realmSessionConfiguration) - .takeIf { it > 0 } - ?.let { Timber.e("All realm instance for session has not been closed ($it)") } - Realm.getGlobalInstanceCount(realmCryptoConfiguration) - .takeIf { it > 0 } - ?.let { Timber.e("All realm instance for crypto has not been closed ($it)") } - } + // Wait for all the Realm instance to be released properly. Closing Realm instance is async. + // After that we can safely delete the Realm files + waitRealmRelease() + + Timber.d("Cleanup: clear file system") + sessionFiles.deleteRecursively() + sessionCache.deleteRecursively() + } + + private suspend fun waitRealmRelease() { + var timeToWaitMillis = MAX_TIME_TO_WAIT_MILLIS + do { + val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration) + val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration) + Timber.d("Wait for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)") + if (sessionRealmCount > 0 || cryptoRealmCount > 0) { + Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms") + delay(TIME_TO_WAIT_MILLIS) + timeToWaitMillis -= TIME_TO_WAIT_MILLIS + } else { + timeToWaitMillis = 0 + } + } while (timeToWaitMillis > 0) + } + + companion object { + private const val MAX_TIME_TO_WAIT_MILLIS = 10_000L + private const val TIME_TO_WAIT_MILLIS = 10L } } From d26340993f0b34df94d77b4c31a18d8113d736a2 Mon Sep 17 00:00:00 2001 From: Aris Kotsomitopoulos <60798129+ariskotsomitopoulos@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:45:34 +0300 Subject: [PATCH 048/172] Update release.yml Removed ./tools/import_emojis.py and ./tools/import_sas_strings.py from the release template while now there is an automated cron job in GitHub Action to run the scripts --- .github/ISSUE_TEMPLATE/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index 903c05c5d3..7ac55427a9 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -23,8 +23,6 @@ body: ### Do the release - [ ] Create release with gitflow, branch name `release/1.1.10` - - [ ] Run `./tools/import_emojis.py` and commit the change if any. - - [ ] Run `./tools/import_sas_strings.py` and commit the change if any. If there is no change since a while, ping Travis - [ ] Check the crashes from the PlayStore - [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.1.10-dev - [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()` From b7a54ead681c0396662d47b2df70605c16f9e0ad Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 09:47:17 +0100 Subject: [PATCH 049/172] delaying the first sync until the first process onStart event - fixes push notifications starting the polling sync thread when the application is created due to push --- .../main/java/im/vector/app/VectorApplication.kt | 16 +++++++++++++++- .../im/vector/app/core/extensions/Session.kt | 6 ++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index 240bbca908..f57c143aa3 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -43,6 +43,7 @@ import im.vector.app.core.di.DaggerVectorComponent import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.di.VectorComponent import im.vector.app.core.extensions.configureAndStart +import im.vector.app.core.extensions.startSyncing import im.vector.app.core.rx.RxConfig import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration @@ -162,11 +163,15 @@ class VectorApplication : // Do not display the name change popup doNotShowDisclaimerDialog(this) } + if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) - lastAuthenticatedSession.configureAndStart(applicationContext) + lastAuthenticatedSession.configureAndStart(applicationContext, startSyncing = false) } + + ProcessLifecycleOwner.get().lifecycle.addObserver(startSyncOnFirstStart) + ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { @@ -199,6 +204,15 @@ class VectorApplication : EmojiManager.install(GoogleEmojiProvider()) } + private val startSyncOnFirstStart = object : LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun onStart() { + Timber.i("App process started") + authenticationService.getLastAuthenticatedSession()?.startSyncing(appContext) + ProcessLifecycleOwner.get().lifecycle.removeObserver(this) + } + } + private fun enableStrictModeIfNeeded() { if (BuildConfig.ENABLE_STRICT_MODE_LOGS) { StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index 215c421291..f066fd6784 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt @@ -26,11 +26,13 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.sync.FilterService import timber.log.Timber -fun Session.configureAndStart(context: Context) { +fun Session.configureAndStart(context: Context, startSyncing: Boolean = true) { Timber.i("Configure and start session for $myUserId") open() setFilter(FilterService.FilterPreset.ElementFilter) - startSyncing(context) + if (startSyncing) { + startSyncing(context) + } refreshPushers() context.vectorComponent().webRtcCallManager().checkForProtocolsSupportIfNeeded() } From d1d66c3406327c09c7e25a920dc04051f5b68171 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 09:49:23 +0100 Subject: [PATCH 050/172] adding changelog entry --- changelog.d/4167.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4167.bugfix diff --git a/changelog.d/4167.bugfix b/changelog.d/4167.bugfix new file mode 100644 index 0000000000..8df264d8f6 --- /dev/null +++ b/changelog.d/4167.bugfix @@ -0,0 +1 @@ +Fixing push notifications starting the looping background sync when the push notification causes the application to be created. From fc753fe11ec03c85a8a3e72f5587c0917135d807 Mon Sep 17 00:00:00 2001 From: Aris Kotsomitopoulos <60798129+ariskotsomitopoulos@users.noreply.github.com> Date: Tue, 12 Oct 2021 11:52:52 +0300 Subject: [PATCH 051/172] Update 4216.misc --- changelog.d/4216.misc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/4216.misc b/changelog.d/4216.misc index 7a3ec23ee5..acf5f1dbcb 100644 --- a/changelog.d/4216.misc +++ b/changelog.d/4216.misc @@ -1 +1 @@ -Implement a new github action workflow to generate two PRs for emoji and sas string sync, issue 3902 | PR 4216 +Implement a new github action workflow to generate two PRs for emoji and sas string sync From 8cea340100cbb4438bfa9384e799eba24d121c5d Mon Sep 17 00:00:00 2001 From: Ekaterina Gerasimova Date: Tue, 12 Oct 2021 10:03:41 +0100 Subject: [PATCH 052/172] Update defect issue template to improve wording Improve wording around rageshakes based on feedback. Signed-off-by: Ekaterina Gerasimova --- .github/ISSUE_TEMPLATE/bug.yml | 4 ++-- changelog.d/4226.misc | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/4226.misc diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 38885e9cc7..c1ab98e85d 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -64,9 +64,9 @@ body: - type: dropdown id: rageshake attributes: - label: Have you submitted a rageshake? + label: Will you send logs? description: | - Did you know that you can shake your phone to submit logs for this issue? Trigger the defect, then shake your phone and you will see a popup asking if you would like to open the bug report screen. Click YES, and describe the issue, mentioning that you have also filed a bug. Submit the report to send anonymous logs to the developers. + Did you know that you can shake your phone to submit logs for this issue? Trigger the defect, then shake your phone and you will see a popup asking if you would like to open the bug report screen. Click YES, and describe the issue, mentioning that you have also filed a bug (it's helpful if you can include a link to the bug). Send the report to submit anonymous logs to the developers. options: - 'Yes' - 'No' diff --git a/changelog.d/4226.misc b/changelog.d/4226.misc new file mode 100644 index 0000000000..ac7a294f35 --- /dev/null +++ b/changelog.d/4226.misc @@ -0,0 +1 @@ +Improve wording around rageshakes in the defect issue template. From 73c08e2eeb3e57bf4886880832bc9c28f7edc53d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 11:38:16 +0200 Subject: [PATCH 053/172] Avoid code duplication --- .../internal/crypto/store/IMXCryptoStore.kt | 3 ++- .../crypto/store/db/RealmCryptoStore.kt | 19 +------------------ 2 files changed, 3 insertions(+), 19 deletions(-) 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 238d06738c..9b75f88f91 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 @@ -379,7 +379,8 @@ internal interface IMXCryptoStore { fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? - fun saveGossipingEvent(event: Event) + fun saveGossipingEvent(event: Event) = saveGossipingEvents(listOf(event)) + fun saveGossipingEvents(events: List) fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) { 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 3c8f74d419..a8be0b5578 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 @@ -1163,8 +1163,8 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveGossipingEvents(events: List) { - val now = System.currentTimeMillis() monarchy.writeAsync { realm -> + val now = System.currentTimeMillis() events.forEach { event -> val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now val entity = GossipingEventEntity( @@ -1182,23 +1182,6 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun saveGossipingEvent(event: Event) { - monarchy.writeAsync { realm -> - val now = System.currentTimeMillis() - val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now - val entity = GossipingEventEntity( - type = event.type, - sender = event.senderId, - ageLocalTs = ageLocalTs, - content = ContentMapper.map(event.content) - ).apply { - sendState = SendState.SYNCED - decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) - decryptionErrorCode = event.mCryptoError?.name - } - realm.insertOrUpdate(entity) - } - } // override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { // val statesIndex = states.map { it.ordinal }.toTypedArray() // return doRealmQueryAndCopy(realmConfiguration) { realm -> From 4a7e0a5d95c6dd6f3fbe18fe7f779be1a9dc40f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 11:57:07 +0200 Subject: [PATCH 054/172] CleanupSession: start by releasing the session, then empty the databases --- .../android/sdk/internal/session/cleanup/CleanupSession.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt index 113e0f218a..e8d3eb1a78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt @@ -61,6 +61,9 @@ internal class CleanupSession @Inject constructor( Timber.d("Cleanup: cancel pending works...") workManagerProvider.cancelAllWorks() + Timber.d("Cleanup: release session...") + sessionManager.releaseSession(sessionId) + Timber.d("Cleanup: clear session data...") clearSessionDataTask.execute(Unit) @@ -71,9 +74,6 @@ internal class CleanupSession @Inject constructor( realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) - Timber.d("Cleanup: release session...") - sessionManager.releaseSession(sessionId) - // Wait for all the Realm instance to be released properly. Closing Realm instance is async. // After that we can safely delete the Realm files waitRealmRelease() From 2d9764037218ddc7661c1412831411b9c73966c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 12:08:14 +0200 Subject: [PATCH 055/172] Ensure no async transaction will occurs if the store is closed --- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 8 ++++++++ 1 file changed, 8 insertions(+) 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 a8be0b5578..f5e3916bb9 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 @@ -100,6 +100,7 @@ import org.matrix.olm.OlmAccount import org.matrix.olm.OlmException import org.matrix.olm.OlmOutboundGroupSession import timber.log.Timber +import java.util.concurrent.Executors import javax.inject.Inject import kotlin.collections.set @@ -137,8 +138,11 @@ internal class RealmCryptoStore @Inject constructor( newSessionListeners.remove(listener) } + private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor() + private val monarchy = Monarchy.Builder() .setRealmConfiguration(realmConfiguration) + .setWriteAsyncExecutor(monarchyWriteAsyncExecutor) .build() init { @@ -199,6 +203,10 @@ internal class RealmCryptoStore @Inject constructor( } override fun close() { + // Ensure no async request will be run later + val tasks = monarchyWriteAsyncExecutor.shutdownNow() + Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled") + olmSessionsToRelease.forEach { it.value.olmSession.releaseSession() } From 6c9fcc0d939e8c4b2286e3498e9ca9f7cc4a172a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 1 Oct 2021 19:04:41 +0100 Subject: [PATCH 056/172] extracting the add pusher logic for the worker and delegating to the task from the worker --- .../sdk/api/session/pushers/PushersService.kt | 50 ++++--- .../internal/session/pushers/AddPusherTask.kt | 79 +++++++++++ .../session/pushers/AddPusherWorker.kt | 50 +------ .../session/pushers/DefaultPushersService.kt | 123 ++++++++++-------- .../internal/session/pushers/JsonPusher.kt | 10 +- .../internal/session/pushers/PushersModule.kt | 3 + .../vector/app/core/pushers/PushersManager.kt | 4 +- 7 files changed, 201 insertions(+), 118 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 2cd17952c6..03391ff8ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -52,15 +52,33 @@ interface PushersService { * (LiveData status = workManager.getWorkInfoByIdLiveData()) * @throws [InvalidParameterException] if a parameter is not correct */ - fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean): UUID + suspend fun addHttpPusher(pushkey: String, + appId: String, + profileTag: String, + lang: String, + appDisplayName: String, + deviceDisplayName: String, + url: String, + append: Boolean, + withEventIdOnly: Boolean) + + /** + * Enqueues a new HTTP pusher via the WorkManager API. + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set + * + * @return A work request uuid. Can be used to listen to the status + * (LiveData status = workManager.getWorkInfoByIdLiveData()) + * @throws [InvalidParameterException] if a parameter is not correct + */ + fun enqueueAddHttpPusher(pushkey: String, + appId: String, + profileTag: String, + lang: String, + appDisplayName: String, + deviceDisplayName: String, + url: String, + append: Boolean, + withEventIdOnly: Boolean): UUID /** * Add a new Email pusher. @@ -75,16 +93,14 @@ interface PushersService { * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers * with the same App ID and pushkey for different users. Typically We always want to append for * email pushers since we don't want to stop other accounts notifying to the same email address. - * @return A work request uuid. Can be used to listen to the status - * (LiveData status = workManager.getWorkInfoByIdLiveData()) * @throws [InvalidParameterException] if a parameter is not correct */ - fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean = true): UUID + suspend fun addEmailPusher(email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean = true) /** * Directly ask the push gateway to send a push to this device diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt new file mode 100644 index 0000000000..b4a5ccd383 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.pushers + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.pushers.PusherState +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface AddPusherTask : Task { + data class Params(val pusher: JsonPusher) +} + +internal class DefaultAddPusherTask @Inject constructor( + private val pushersAPI: PushersAPI, + @SessionDatabase private val monarchy: Monarchy, + private val globalErrorReceiver: GlobalErrorReceiver +) : AddPusherTask { + override suspend fun execute(params: AddPusherTask.Params) { + val pusher = params.pusher + try { + setPusher(pusher) + } catch (error: Throwable) { + monarchy.awaitTransaction { realm -> + PusherEntity.where(realm, pusher.pushKey).findFirst()?.let { + // update it + it.state = PusherState.FAILED_TO_REGISTER + } + } + throw error + } + } + + private suspend fun setPusher(pusher: JsonPusher) { + executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) + } + monarchy.awaitTransaction { realm -> + val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() + if (echo != null) { + // update it + echo.appDisplayName = pusher.appDisplayName + echo.appId = pusher.appId + echo.kind = pusher.kind + echo.lang = pusher.lang + echo.profileTag = pusher.profileTag + echo.data?.format = pusher.data?.format + echo.data?.url = pusher.data?.url + echo.state = PusherState.REGISTERED + } else { + pusher.toEntity().also { + it.state = PusherState.REGISTERED + realm.insertOrUpdate(it) + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt index 63fd855c08..4df42b2cfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt @@ -18,17 +18,8 @@ package org.matrix.android.sdk.internal.session.pushers import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.pushers.PusherState -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.PusherEntity -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent -import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject @@ -43,9 +34,7 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : override val lastFailureMessage: String? = null ) : SessionWorkerParams - @Inject lateinit var pushersAPI: PushersAPI - @Inject @SessionDatabase lateinit var monarchy: Monarchy - @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver + @Inject lateinit var addPusherTask: AddPusherTask override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -58,20 +47,12 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : return Result.failure() } return try { - setPusher(pusher) + addPusherTask.execute(AddPusherTask.Params(pusher)) Result.success() } catch (exception: Throwable) { when (exception) { is Failure.NetworkConnection -> Result.retry() - else -> { - monarchy.awaitTransaction { realm -> - PusherEntity.where(realm, pusher.pushKey).findFirst()?.let { - // update it - it.state = PusherState.FAILED_TO_REGISTER - } - } - Result.failure() - } + else -> Result.failure() } } } @@ -79,29 +60,4 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : override fun buildErrorParams(params: Params, message: String): Params { return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) } - - private suspend fun setPusher(pusher: JsonPusher) { - executeRequest(globalErrorReceiver) { - pushersAPI.setPusher(pusher) - } - monarchy.awaitTransaction { realm -> - val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() - if (echo != null) { - // update it - echo.appDisplayName = pusher.appDisplayName - echo.appId = pusher.appId - echo.kind = pusher.kind - echo.lang = pusher.lang - echo.profileTag = pusher.profileTag - echo.data?.format = pusher.data?.format - echo.data?.url = pusher.data?.url - echo.state = PusherState.REGISTERED - } else { - pusher.toEntity().also { - it.state = PusherState.REGISTERED - realm.insertOrUpdate(it) - } - } - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index 9a50abfe35..8a510969e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotify import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.worker.WorkerParamsFactory -import java.security.InvalidParameterException import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -41,6 +40,7 @@ internal class DefaultPushersService @Inject constructor( @SessionId private val sessionId: String, private val getPusherTask: GetPushersTask, private val pushGatewayNotifyTask: PushGatewayNotifyTask, + private val addPusherTask: AddPusherTask, private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor ) : PushersService { @@ -58,51 +58,79 @@ internal class DefaultPushersService @Inject constructor( .executeBy(taskExecutor) } - override fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean - ) = addPusher( - JsonPusher( - pushKey = pushkey, - kind = Pusher.KIND_HTTP, - appId = appId, - profileTag = profileTag, - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append - ) - ) + override fun enqueueAddHttpPusher(pushkey: String, + appId: String, + profileTag: String, + lang: String, + appDisplayName: String, + deviceDisplayName: String, + url: String, + append: Boolean, + withEventIdOnly: Boolean + ): UUID { + return enqueueAddPusher( + JsonPusher( + pushKey = pushkey, + kind = "http", + appId = appId, + profileTag = profileTag, + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), + append = append + ) + ) + } - override fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean - ) = addPusher( - JsonPusher( - pushKey = email, - kind = Pusher.KIND_EMAIL, - appId = Pusher.APP_ID_EMAIL, - profileTag = "", - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(brand = emailBranding), - append = append - ) - ) + override suspend fun addHttpPusher(pushkey: String, + appId: String, + profileTag: String, + lang: String, + appDisplayName: String, + deviceDisplayName: String, + url: String, + append: Boolean, + withEventIdOnly: Boolean) { + addPusherTask.execute( + AddPusherTask.Params( + JsonPusher( + pushKey = pushkey, + kind = "http", + appId = appId, + profileTag = profileTag, + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), + append = append + ) + ) + ) + } - private fun addPusher(pusher: JsonPusher): UUID { - pusher.validateParameters() + override suspend fun addEmailPusher(email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean) { + addPusherTask.execute( + AddPusherTask.Params(JsonPusher( + pushKey = email, + kind = Pusher.KIND_EMAIL, + appId = Pusher.APP_ID_EMAIL, + profileTag = "", + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(brand = emailBranding), + append = append + )) + ) + } + + private fun enqueueAddPusher(pusher: JsonPusher): UUID { val params = AddPusherWorker.Params(sessionId, pusher) val request = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) @@ -113,13 +141,6 @@ internal class DefaultPushersService @Inject constructor( return request.id } - private fun JsonPusher.validateParameters() { - // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem - if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars") - if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars") - data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") } - } - override suspend fun removePusher(pusher: Pusher) { removePusher(pusher.pushKey, pusher.appId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index a594675e28..8dc0954694 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.pushers import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.di.SerializeNulls +import java.security.InvalidParameterException /** * Example: @@ -112,4 +113,11 @@ internal data class JsonPusher( */ @Json(name = "append") val append: Boolean? = false -) +) { + init { + // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem + if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars") + if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars") + data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 4030c63514..d53a4eed65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -65,6 +65,9 @@ internal abstract class PushersModule { @Binds abstract fun bindSavePushRulesTask(task: DefaultSavePushRulesTask): SavePushRulesTask + @Binds + abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask + @Binds abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index a27765bf4f..0ed7c91540 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -48,7 +48,7 @@ class PushersManager @Inject constructor( val currentSession = activeSessionHolder.getActiveSession() val profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(currentSession.myUserId.hashCode()) - return currentSession.addHttpPusher( + return currentSession.enqueueAddHttpPusher( pushKey, stringProvider.getString(R.string.pusher_app_id), profileTag, @@ -61,7 +61,7 @@ class PushersManager @Inject constructor( ) } - fun registerEmailForPush(email: String) { + suspend fun registerEmailForPush(email: String) { val currentSession = activeSessionHolder.getActiveSession() val appName = appNameProvider.getAppName() currentSession.addEmailPusher( From e24329e1393d1fc8977ffc7f2e4b3371324052c3 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 21 Sep 2021 15:09:57 +0100 Subject: [PATCH 057/172] reusing the transactional logic for the current session notifications toggle - uses the synchronous token registering which also means we get error handling --- .../sdk/api/session/pushers/PushersService.kt | 99 +++++++++++-------- .../session/pushers/DefaultPushersService.kt | 63 +++--------- .../troubleshoot/TestTokenRegistration.kt | 2 +- .../fcm/VectorFirebaseMessagingService.kt | 2 +- .../java/im/vector/app/push/fcm/FcmHelper.kt | 2 +- .../vector/app/core/pushers/PushersManager.kt | 34 ++++--- ...rSettingsNotificationPreferenceFragment.kt | 45 +++------ 7 files changed, 114 insertions(+), 133 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 03391ff8ba..757b55a3fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -29,38 +29,9 @@ interface PushersService { * Add a new HTTP pusher. * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set * - * @param pushkey This is a unique identifier for this pusher. The value you should use for - * this is the routing or destination address information for the notification, - * for example, the APNS token for APNS or the Registration ID for GCM. If your - * notification client has no such concept, use any unique identifier. Max length, 512 chars. - * @param appId the application id - * This is a reverse-DNS style identifier for the application. It is recommended - * that this end with the platform, such that different platform versions get - * different app identifiers. Max length, 64 chars. - * @param profileTag This string determines which set of device specific rules this pusher executes. - * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US"). - * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher. - * @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher. - * @param url The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. - * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition - * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers - * with the same App ID and pushkey for different users. - * @param withEventIdOnly true to limit the push content to only id and not message content - * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour - * - * @return A work request uuid. Can be used to listen to the status - * (LiveData status = workManager.getWorkInfoByIdLiveData()) * @throws [InvalidParameterException] if a parameter is not correct */ - suspend fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean) + suspend fun addHttpPusher(httpPusher: HttpPusher) /** * Enqueues a new HTTP pusher via the WorkManager API. @@ -70,15 +41,7 @@ interface PushersService { * (LiveData status = workManager.getWorkInfoByIdLiveData()) * @throws [InvalidParameterException] if a parameter is not correct */ - fun enqueueAddHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean): UUID + fun enqueueAddHttpPusher(httpPusher: HttpPusher): UUID /** * Add a new Email pusher. @@ -144,4 +107,62 @@ interface PushersService { * Get the current pushers */ fun getPushers(): List + + data class HttpPusher( + + /** + * This is a unique identifier for this pusher. The value you should use for + * this is the routing or destination address information for the notification, + * for example, the APNS token for APNS or the Registration ID for GCM. If your + * notification client has no such concept, use any unique identifier. Max length, 512 chars. + * If the kind is "email", this is the email address to send notifications to. + */ + val pushkey: String, + + /** + * The application id + * This is a reverse-DNS style identifier for the application. It is recommended + * that this end with the platform, such that different platform versions get + * different app identifiers. Max length, 64 chars. + */ + val appId: String, + + /** + * This string determines which set of device specific rules this pusher executes. + */ + val profileTag: String, + + /** + * The preferred language for receiving notifications (e.g. "en" or "en-US"). + */ + val lang: String, + + /** + * A human readable string that will allow the user to identify what application owns this pusher. + */ + val appDisplayName: String, + + /** + * A human readable string that will allow the user to identify what device owns this pusher. + */ + val deviceDisplayName: String, + + /** + * The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. + */ + val url: String, + + /** + * If true, the homeserver should add another pusher with the given pushkey and App ID in addition + * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers + * with the same App ID and pushkey for different users. + */ + val append: Boolean, + + /** + * true to limit the push content to only id and not message content + * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour + */ + val withEventIdOnly: Boolean, + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index 8a510969e7..e87c27e601 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -58,57 +58,26 @@ internal class DefaultPushersService @Inject constructor( .executeBy(taskExecutor) } - override fun enqueueAddHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean - ): UUID { - return enqueueAddPusher( - JsonPusher( - pushKey = pushkey, - kind = "http", - appId = appId, - profileTag = profileTag, - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append - ) - ) + override fun enqueueAddHttpPusher(httpPusher: PushersService.HttpPusher): UUID { + return enqueueAddPusher(httpPusher.toJsonPusher()) } - override suspend fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean) { - addPusherTask.execute( - AddPusherTask.Params( - JsonPusher( - pushKey = pushkey, - kind = "http", - appId = appId, - profileTag = profileTag, - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append - ) - ) - ) + override suspend fun addHttpPusher(httpPusher: PushersService.HttpPusher) { + addPusherTask.execute(AddPusherTask.Params(httpPusher.toJsonPusher())) } + private fun PushersService.HttpPusher.toJsonPusher() = JsonPusher( + pushKey = pushkey, + kind = "http", + appId = appId, + profileTag = profileTag, + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), + append = append + ) + override suspend fun addEmailPusher(email: String, lang: String, emailBranding: String, diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt index 966d79c59b..94d4ec8a56 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestTokenRegistration.kt @@ -57,7 +57,7 @@ class TestTokenRegistration @Inject constructor(private val context: AppCompatAc stringProvider.getString(R.string.sas_error_unknown)) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_token_registration_quick_fix) { override fun doFix() { - val workId = pushersManager.registerPusherWithFcmKey(fcmToken) + val workId = pushersManager.enqueueRegisterPusherWithFcmKey(fcmToken) WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId).observe(context, Observer { workInfo -> if (workInfo != null) { if (workInfo.state == WorkInfo.State.SUCCEEDED) { diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index ddedfb93e3..2ce51ba4c7 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -135,7 +135,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { Timber.tag(loggerTag.value).i("onNewToken: FCM Token has been updated") FcmHelper.storeFcmToken(this, refreshedToken) if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { - pusherManager.registerPusherWithFcmKey(refreshedToken) + pusherManager.enqueueRegisterPusherWithFcmKey(refreshedToken) } } diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index f3bdcafb1c..fe091ffda3 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -75,7 +75,7 @@ object FcmHelper { .addOnSuccessListener { token -> storeFcmToken(activity, token) if (registerPusher) { - pushersManager.registerPusherWithFcmKey(token) + pushersManager.enqueueRegisterPusherWithFcmKey(token) } } .addOnFailureListener { e -> diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 0ed7c91540..0f780d4504 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -21,6 +21,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.pushers.PushersService import java.util.UUID import javax.inject.Inject import kotlin.math.abs @@ -44,23 +45,28 @@ class PushersManager @Inject constructor( ) } - fun registerPusherWithFcmKey(pushKey: String): UUID { + fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { val currentSession = activeSessionHolder.getActiveSession() - val profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(currentSession.myUserId.hashCode()) - - return currentSession.enqueueAddHttpPusher( - pushKey, - stringProvider.getString(R.string.pusher_app_id), - profileTag, - localeProvider.current().language, - appNameProvider.getAppName(), - currentSession.sessionParams.deviceId ?: "MOBILE", - stringProvider.getString(R.string.pusher_http_url), - append = false, - withEventIdOnly = true - ) + return currentSession.enqueueAddHttpPusher(httpPusher(pushKey)) } + suspend fun registerPusherWithFcmKey(pushKey: String) { + val currentSession = activeSessionHolder.getActiveSession() + currentSession.addHttpPusher(httpPusher(pushKey)) + } + + private fun httpPusher(pushKey: String) = PushersService.HttpPusher( + pushKey, + stringProvider.getString(R.string.pusher_app_id), + profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()), + localeProvider.current().language, + appNameProvider.getAppName(), + activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", + stringProvider.getString(R.string.pusher_http_url), + append = false, + withEventIdOnly = true + ) + suspend fun registerEmailForPush(email: String) { val currentSession = activeSessionHolder.getActiveSession() val appName = appNameProvider.getAppName() diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 098d1b2caa..18ea2d6773 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -85,6 +85,21 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( (pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevel } + findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let { + it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked -> + if (isChecked) { + FcmHelper.getFcmToken(requireContext())?.let { + pushManager.registerPusherWithFcmKey(it) + } + } else { + FcmHelper.getFcmToken(requireContext())?.let { + pushManager.unregisterPusher(it) + session.refreshPushers() + } + } + } + } + findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { it.onPreferenceClickListener = Preference.OnPreferenceClickListener { val initialMode = vectorPreferences.getFdroidSyncBackgroundMode() @@ -324,10 +339,6 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun onPreferenceTreeClick(preference: Preference?): Boolean { return when (preference?.key) { - VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY -> { - updateEnabledForDevice(preference) - true - } VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> { updateEnabledForAccount(preference) true @@ -338,32 +349,6 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } } - private fun updateEnabledForDevice(preference: Preference?) { - val switchPref = preference as SwitchPreference - if (switchPref.isChecked) { - FcmHelper.getFcmToken(requireContext())?.let { - pushManager.registerPusherWithFcmKey(it) - } - } else { - FcmHelper.getFcmToken(requireContext())?.let { - lifecycleScope.launch { - runCatching { pushManager.unregisterPusher(it) } - .fold( - { session.refreshPushers() }, - { - if (!isAdded) { - return@fold - } - // revert the check box - switchPref.isChecked = !switchPref.isChecked - Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() - } - ) - } - } - } - } - private fun updateEnabledForAccount(preference: Preference?) { val pushRuleService = session val switchPref = preference as SwitchPreference From 46c338934eb06357703956255d90f37c96ae0b0e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 21 Sep 2021 16:26:14 +0100 Subject: [PATCH 058/172] running lint --- .../matrix/android/sdk/api/session/pushers/PushersService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 757b55a3fa..f6e241ad5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -163,6 +163,6 @@ interface PushersService { * true to limit the push content to only id and not message content * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour */ - val withEventIdOnly: Boolean, + val withEventIdOnly: Boolean ) } From 6672ab39663d8a3e5d917d095ac4c4073a7f031e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 10:14:53 +0100 Subject: [PATCH 059/172] removing comment which doesn't add additional context/information --- .../android/sdk/internal/session/pushers/AddPusherTask.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index b4a5ccd383..431a009844 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -44,7 +44,6 @@ internal class DefaultAddPusherTask @Inject constructor( } catch (error: Throwable) { monarchy.awaitTransaction { realm -> PusherEntity.where(realm, pusher.pushKey).findFirst()?.let { - // update it it.state = PusherState.FAILED_TO_REGISTER } } @@ -59,7 +58,6 @@ internal class DefaultAddPusherTask @Inject constructor( monarchy.awaitTransaction { realm -> val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() if (echo != null) { - // update it echo.appDisplayName = pusher.appDisplayName echo.appId = pusher.appId echo.kind = pusher.kind From 0a2d7d709b19301a3571b0724ad1a8c964287798 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 12:37:30 +0100 Subject: [PATCH 060/172] creating an injectable request executor to enable unit tests network request (without hitting the network) --- .../internal/session/pushers/AddPusherTask.kt | 18 ++++++++++++++++-- .../internal/session/pushers/PushersModule.kt | 7 +++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index 431a009844..2929e09a1f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject @@ -35,6 +34,7 @@ internal interface AddPusherTask : Task { internal class DefaultAddPusherTask @Inject constructor( private val pushersAPI: PushersAPI, @SessionDatabase private val monarchy: Monarchy, + private val requestExecutor: RequestExecutor, private val globalErrorReceiver: GlobalErrorReceiver ) : AddPusherTask { override suspend fun execute(params: AddPusherTask.Params) { @@ -52,7 +52,7 @@ internal class DefaultAddPusherTask @Inject constructor( } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest(globalErrorReceiver) { + requestExecutor.executeRequest(globalErrorReceiver) { pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> @@ -75,3 +75,17 @@ internal class DefaultAddPusherTask @Inject constructor( } } } + +internal interface RequestExecutor { + suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + requestBlock: suspend () -> DATA): DATA +} + +internal object DefaultRequestExecutor : RequestExecutor { + override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, canRetry: Boolean, maxDelayBeforeRetry: Long, maxRetriesCount: Int, requestBlock: suspend () -> DATA): DATA { + return executeRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index d53a4eed65..8cf4d9fcf0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotify import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask import retrofit2.Retrofit +import javax.inject.Singleton @Module internal abstract class PushersModule { @@ -48,6 +49,12 @@ internal abstract class PushersModule { fun providesPushRulesApi(retrofit: Retrofit): PushRulesApi { return retrofit.create(PushRulesApi::class.java) } + + @Provides + @JvmStatic + fun providesRequestExecutor(): RequestExecutor { + return DefaultRequestExecutor + } } @Binds From ced85964dac7d5dff802ba25cdbd9919d028a4d6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 12:39:24 +0100 Subject: [PATCH 061/172] including rx java dependency for the sdk tests because real (monarchy) tranisitive depends on rx but doesn't propagate it as an API dependency - without an explicit declaration we can't mock the realm instance --- matrix-sdk-android/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 86a257c7d6..3bcb08c02c 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -130,6 +130,8 @@ dependencies { // Database implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' + testImplementation libs.rx.rxKotlin + kapt 'dk.ilios:realmfieldnameshelper:2.0.0' // Work From 48d9dfb82dcb5fed7b5c5816c549ff05a4583c37 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 13:05:42 +0100 Subject: [PATCH 062/172] adding test for the add pusher task happy flow - introduces the concepts of Fakes for handling the dependencies, unforuntately realm/monarchy aren't very testable in their current state so we'll need to use mocks --- .../pushers/DefaultAddPusherTaskTest.kt | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt new file mode 100644 index 0000000000..47730eba49 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.pushers + +import com.zhuinden.monarchy.Monarchy +import io.mockk.MockKVerificationScope +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmQuery +import io.realm.kotlin.where +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.api.session.pushers.PusherState +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.util.awaitTransaction + +private val A_JSON_PUSHER = JsonPusher( + pushKey = "push-key", + kind = "http", + appId = "m.email", + appDisplayName = "Element", + deviceDisplayName = null, + profileTag = "", + lang = "en-GB", + data = JsonPusherData(brand = "Element") +) + +class DefaultAddPusherTaskTest { + + private val pushersAPI = FakePushersAPI() + private val monarchy = FakeMonarchy() + + private val addPusherTask = DefaultAddPusherTask( + pushersAPI = pushersAPI, + monarchy = monarchy.instance, + requestExecutor = FakeRequestExecutor(), + globalErrorReceiver = FakeGlobalErrorReceiver() + ) + + @Test + fun `given no persisted pusher when running task then fetches from api and inserts result with Registered state`() = runBlocking { + monarchy.givenWhereReturns(result = null) + + addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) + + pushersAPI.verifySetPusher(A_JSON_PUSHER) + monarchy.verifyInsertOrUpdate { + withArg { actual -> + actual.state shouldBeEqualTo PusherState.REGISTERED + } + } + } +} + +internal class FakePushersAPI : PushersAPI { + + private var setRequestPayload: JsonPusher? = null + private var error: Throwable? = null + + override suspend fun getPushers(): GetPushersResponse { + TODO("Not yet implemented") + } + + override suspend fun setPusher(jsonPusher: JsonPusher) { + error?.let { throw it } + setRequestPayload = jsonPusher + } + + fun verifySetPusher(payload: JsonPusher) { + this.setRequestPayload shouldBeEqualTo payload + } + + fun givenSetPusherErrors(error: Throwable) { + this.error = error + } +} + +internal class FakeMonarchy { + + val instance = mockk() + private val realm = mockk(relaxed = true) + + init { + mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") + coEvery { + instance.awaitTransaction(any Any>()) + } coAnswers { + secondArg Any>().invoke(realm) + } + } + + inline fun givenWhereReturns(result: T?) { + val queryResult = mockk>(relaxed = true) + every { queryResult.findFirst() } returns result + every { realm.where() } returns queryResult + } + + inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { + verify { realm.insertOrUpdate(verification()) } + } +} + +internal class FakeRequestExecutor : RequestExecutor { + + override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { + return requestBlock() + } +} + +internal class FakeGlobalErrorReceiver : GlobalErrorReceiver { + override fun handleGlobalError(globalError: GlobalError) { + // do nothing + } +} From 21479b2b28745e23f8983e1fb493e68629a2a106 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 15:51:43 +0100 Subject: [PATCH 063/172] inverting if to favour positive ordering --- .../sdk/internal/session/pushers/AddPusherTask.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index 2929e09a1f..a24202957c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -57,7 +57,12 @@ internal class DefaultAddPusherTask @Inject constructor( } monarchy.awaitTransaction { realm -> val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() - if (echo != null) { + if (echo == null) { + pusher.toEntity().also { + it.state = PusherState.REGISTERED + realm.insertOrUpdate(it) + } + } else { echo.appDisplayName = pusher.appDisplayName echo.appId = pusher.appId echo.kind = pusher.kind @@ -66,11 +71,6 @@ internal class DefaultAddPusherTask @Inject constructor( echo.data?.format = pusher.data?.format echo.data?.url = pusher.data?.url echo.state = PusherState.REGISTERED - } else { - pusher.toEntity().also { - it.state = PusherState.REGISTERED - realm.insertOrUpdate(it) - } } } } From b7c911feeefd7a8653ce069106ecdb8a4d5e470f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 22 Sep 2021 16:17:39 +0100 Subject: [PATCH 064/172] adding test cases for when adding a pusher fails and when it already exists --- .../pushers/DefaultAddPusherTaskTest.kt | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index 47730eba49..c3be7c613e 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -28,6 +28,7 @@ import io.realm.RealmModel import io.realm.RealmQuery import io.realm.kotlin.where import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertFailsWith import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.failure.GlobalError @@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.util.awaitTransaction +import java.net.SocketException private val A_JSON_PUSHER = JsonPusher( pushKey = "push-key", @@ -60,10 +62,10 @@ class DefaultAddPusherTaskTest { ) @Test - fun `given no persisted pusher when running task then fetches from api and inserts result with Registered state`() = runBlocking { + fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() { monarchy.givenWhereReturns(result = null) - addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } pushersAPI.verifySetPusher(A_JSON_PUSHER) monarchy.verifyInsertOrUpdate { @@ -72,6 +74,42 @@ class DefaultAddPusherTaskTest { } } } + + @Test + fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { + val realmResult = PusherEntity(appDisplayName = null) + monarchy.givenWhereReturns(result = realmResult) + + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + + pushersAPI.verifySetPusher(A_JSON_PUSHER) + + realmResult.appDisplayName shouldBeEqualTo A_JSON_PUSHER.appDisplayName + realmResult.state shouldBeEqualTo PusherState.REGISTERED + } + + @Test + fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows error`() { + val realmResult = PusherEntity() + monarchy.givenWhereReturns(result = realmResult) + pushersAPI.givenSetPusherErrors(SocketException()) + + assertFailsWith { + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + } + + realmResult.state shouldBeEqualTo PusherState.FAILED_TO_REGISTER + } + + @Test + fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { + monarchy.givenWhereReturns(result = null) + pushersAPI.givenSetPusherErrors(SocketException()) + + assertFailsWith { + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + } + } } internal class FakePushersAPI : PushersAPI { From 8e84aea434583184cd629c0bd7c69db256c0400b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 23 Sep 2021 09:32:02 +0100 Subject: [PATCH 065/172] removing unused import --- .../android/sdk/internal/session/pushers/AddPusherTask.kt | 6 +++++- .../android/sdk/internal/session/pushers/PushersModule.kt | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index a24202957c..61f893572d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -85,7 +85,11 @@ internal interface RequestExecutor { } internal object DefaultRequestExecutor : RequestExecutor { - override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, canRetry: Boolean, maxDelayBeforeRetry: Long, maxRetriesCount: Int, requestBlock: suspend () -> DATA): DATA { + override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { return executeRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 8cf4d9fcf0..6af8c4ea47 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotify import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask import retrofit2.Retrofit -import javax.inject.Singleton @Module internal abstract class PushersModule { From aff787bb29cc887a03db81412e51a613a5f53e59 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 28 Sep 2021 08:52:55 +0100 Subject: [PATCH 066/172] extracting the test fakes to their own package --- .../pushers/DefaultAddPusherTaskTest.kt | 83 +------------------ .../sdk/test/fakes/FakeGlobalErrorReceiver.kt | 26 ++++++ .../android/sdk/test/fakes/FakeMonarchy.kt | 55 ++++++++++++ .../android/sdk/test/fakes/FakePushersAPI.kt | 45 ++++++++++ .../sdk/test/fakes/FakeRequestExecutor.kt | 31 +++++++ 5 files changed, 161 insertions(+), 79 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index c3be7c613e..c8be0f5487 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -16,26 +16,16 @@ package org.matrix.android.sdk.internal.session.pushers -import com.zhuinden.monarchy.Monarchy -import io.mockk.MockKVerificationScope -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.verify -import io.realm.Realm -import io.realm.RealmModel -import io.realm.RealmQuery -import io.realm.kotlin.where import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertFailsWith import org.amshove.kluent.shouldBeEqualTo import org.junit.Test -import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.model.PusherEntity -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakePushersAPI +import org.matrix.android.sdk.test.fakes.FakeRequestExecutor import java.net.SocketException private val A_JSON_PUSHER = JsonPusher( @@ -111,68 +101,3 @@ class DefaultAddPusherTaskTest { } } } - -internal class FakePushersAPI : PushersAPI { - - private var setRequestPayload: JsonPusher? = null - private var error: Throwable? = null - - override suspend fun getPushers(): GetPushersResponse { - TODO("Not yet implemented") - } - - override suspend fun setPusher(jsonPusher: JsonPusher) { - error?.let { throw it } - setRequestPayload = jsonPusher - } - - fun verifySetPusher(payload: JsonPusher) { - this.setRequestPayload shouldBeEqualTo payload - } - - fun givenSetPusherErrors(error: Throwable) { - this.error = error - } -} - -internal class FakeMonarchy { - - val instance = mockk() - private val realm = mockk(relaxed = true) - - init { - mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") - coEvery { - instance.awaitTransaction(any Any>()) - } coAnswers { - secondArg Any>().invoke(realm) - } - } - - inline fun givenWhereReturns(result: T?) { - val queryResult = mockk>(relaxed = true) - every { queryResult.findFirst() } returns result - every { realm.where() } returns queryResult - } - - inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { - verify { realm.insertOrUpdate(verification()) } - } -} - -internal class FakeRequestExecutor : RequestExecutor { - - override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean, - maxDelayBeforeRetry: Long, - maxRetriesCount: Int, - requestBlock: suspend () -> DATA): DATA { - return requestBlock() - } -} - -internal class FakeGlobalErrorReceiver : GlobalErrorReceiver { - override fun handleGlobalError(globalError: GlobalError) { - // do nothing - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt new file mode 100644 index 0000000000..ebddb3fafa --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver + +internal class FakeGlobalErrorReceiver : GlobalErrorReceiver { + override fun handleGlobalError(globalError: GlobalError) { + // do nothing + } +} 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 new file mode 100644 index 0000000000..0a22ef8996 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import com.zhuinden.monarchy.Monarchy +import io.mockk.MockKVerificationScope +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.util.awaitTransaction + +internal class FakeMonarchy { + + val instance = mockk() + private val realm = mockk(relaxed = true) + + init { + mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") + coEvery { + instance.awaitTransaction(any Any>()) + } coAnswers { + secondArg Any>().invoke(realm) + } + } + + inline fun givenWhereReturns(result: T?) { + val queryResult = mockk>(relaxed = true) + every { queryResult.findFirst() } returns result + every { realm.where() } returns queryResult + } + + inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { + verify { realm.insertOrUpdate(verification()) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt new file mode 100644 index 0000000000..29f93c2faf --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.internal.session.pushers.GetPushersResponse +import org.matrix.android.sdk.internal.session.pushers.JsonPusher +import org.matrix.android.sdk.internal.session.pushers.PushersAPI + +internal class FakePushersAPI : PushersAPI { + + private var setRequestPayload: JsonPusher? = null + private var error: Throwable? = null + + override suspend fun getPushers(): GetPushersResponse { + TODO("Not yet implemented") + } + + override suspend fun setPusher(jsonPusher: JsonPusher) { + error?.let { throw it } + setRequestPayload = jsonPusher + } + + fun verifySetPusher(payload: JsonPusher) { + this.setRequestPayload shouldBeEqualTo payload + } + + fun givenSetPusherErrors(error: Throwable) { + this.error = error + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt new file mode 100644 index 0000000000..95a0d0997d --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.session.pushers.RequestExecutor + +internal class FakeRequestExecutor : RequestExecutor { + + override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { + return requestBlock() + } +} From 69bb554e208a33c1923629048b181b9d699cdefd Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 29 Sep 2021 10:10:01 +0100 Subject: [PATCH 067/172] lifting the request executor to its own file in the network package - also creates a dedicated RequestModule instead of providing the executor via the pushers module --- .../sdk/internal/network/RequestExecutor.kt | 36 +++++++++++++++++++ .../sdk/internal/network/RequestModule.kt | 29 +++++++++++++++ .../sdk/internal/session/SessionComponent.kt | 4 ++- .../internal/session/pushers/AddPusherTask.kt | 19 +--------- .../internal/session/pushers/PushersModule.kt | 6 ---- .../sdk/test/fakes/FakeRequestExecutor.kt | 2 +- 6 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt new file mode 100644 index 0000000000..5cd2d88000 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.network +import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest + +internal interface RequestExecutor { + suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + requestBlock: suspend () -> DATA): DATA +} + +internal object DefaultRequestExecutor : RequestExecutor { + override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { + return internalExecuteRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt new file mode 100644 index 0000000000..7b2bb9fcba --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.network + +import dagger.Module +import dagger.Provides + +@Module +internal object RequestModule { + + @Provides + fun providesRequestExecutor(): RequestExecutor { + return DefaultRequestExecutor + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 71031a4614..71ec6be840 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessa import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.federation.FederationModule import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker +import org.matrix.android.sdk.internal.network.RequestModule import org.matrix.android.sdk.internal.session.account.AccountModule import org.matrix.android.sdk.internal.session.cache.CacheModule import org.matrix.android.sdk.internal.session.call.CallModule @@ -94,7 +95,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule CallModule::class, SearchModule::class, ThirdPartyModule::class, - SpaceModule::class + SpaceModule::class, + RequestModule::class ] ) @SessionScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index 61f893572d..7d81e19265 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.RequestExecutor import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject @@ -75,21 +76,3 @@ internal class DefaultAddPusherTask @Inject constructor( } } } - -internal interface RequestExecutor { - suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean = false, - maxDelayBeforeRetry: Long = 32_000L, - maxRetriesCount: Int = 4, - requestBlock: suspend () -> DATA): DATA -} - -internal object DefaultRequestExecutor : RequestExecutor { - override suspend fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - canRetry: Boolean, - maxDelayBeforeRetry: Long, - maxRetriesCount: Int, - requestBlock: suspend () -> DATA): DATA { - return executeRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 6af8c4ea47..d53a4eed65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -48,12 +48,6 @@ internal abstract class PushersModule { fun providesPushRulesApi(retrofit: Retrofit): PushRulesApi { return retrofit.create(PushRulesApi::class.java) } - - @Provides - @JvmStatic - fun providesRequestExecutor(): RequestExecutor { - return DefaultRequestExecutor - } } @Binds diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt index 95a0d0997d..2f332a89a8 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.test.fakes import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.session.pushers.RequestExecutor +import org.matrix.android.sdk.internal.network.RequestExecutor internal class FakeRequestExecutor : RequestExecutor { From 13f8494072bdff1e20267368ea5ba363a1641ffc Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 8 Oct 2021 08:55:55 +0100 Subject: [PATCH 068/172] grouping with other test deps and commenting the reason for rxKotlin dependency --- matrix-sdk-android/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3bcb08c02c..b703b13c78 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -130,7 +130,6 @@ dependencies { // Database implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' - testImplementation libs.rx.rxKotlin kapt 'dk.ilios:realmfieldnameshelper:2.0.0' @@ -167,6 +166,8 @@ dependencies { implementation libs.jetbrains.coroutinesAndroid // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' + // Transitively required for mocking realm as monarchy doesn't expose Rx + testImplementation libs.rx.rxKotlin kaptAndroidTest libs.dagger.daggerCompiler androidTestImplementation libs.androidx.testCore From bdec6a3580fe85f1a7009cf6005567660d16bef4 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 8 Oct 2021 08:59:03 +0100 Subject: [PATCH 069/172] removing mention of email in the http pusher model, we have dedicated emails functions on the service instead --- .../org/matrix/android/sdk/api/session/pushers/PushersService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index f6e241ad5e..f884d3e890 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -115,7 +115,6 @@ interface PushersService { * this is the routing or destination address information for the notification, * for example, the APNS token for APNS or the Registration ID for GCM. If your * notification client has no such concept, use any unique identifier. Max length, 512 chars. - * If the kind is "email", this is the email address to send notifications to. */ val pushkey: String, From 1c1424eafc1c6502e7fcc1890d09ee523ab9ee04 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 8 Oct 2021 09:05:16 +0100 Subject: [PATCH 070/172] using verb prefix for http pusher creation function --- .../main/java/im/vector/app/core/pushers/PushersManager.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 0f780d4504..27d6d05708 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -47,15 +47,15 @@ class PushersManager @Inject constructor( fun enqueueRegisterPusherWithFcmKey(pushKey: String): UUID { val currentSession = activeSessionHolder.getActiveSession() - return currentSession.enqueueAddHttpPusher(httpPusher(pushKey)) + return currentSession.enqueueAddHttpPusher(createHttpPusher(pushKey)) } suspend fun registerPusherWithFcmKey(pushKey: String) { val currentSession = activeSessionHolder.getActiveSession() - currentSession.addHttpPusher(httpPusher(pushKey)) + currentSession.addHttpPusher(createHttpPusher(pushKey)) } - private fun httpPusher(pushKey: String) = PushersService.HttpPusher( + private fun createHttpPusher(pushKey: String) = PushersService.HttpPusher( pushKey, stringProvider.getString(R.string.pusher_app_id), profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()), From bd51eae741b62eda88010b991576aff5cf45c432 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 11:44:30 +0100 Subject: [PATCH 071/172] refreshing the threePids when entering the preference screen, afterwards we're monitoring for changes --- .../VectorSettingsNotificationPreferenceFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 18ea2d6773..1c73fbb470 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -406,7 +406,7 @@ private fun Session.getEmailsWithPushInformation(): List>> { - return getThreePidsLive(refreshData = false) + return getThreePidsLive(refreshData = true) .distinctUntilChanged() .map { threePids -> val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL } From a24a9b43fa10afcafe1b67da9c16a6306058ce12 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 12 Oct 2021 13:47:32 +0200 Subject: [PATCH 072/172] Mavericks 2: make the UT happy. Let SDK exposes MatrixCoroutineDispatchers. --- dependencies.gradle | 3 +- .../org/matrix/android/sdk/flow/FlowExt.kt | 6 +-- .../org/matrix/android/sdk/flow/FlowRoom.kt | 14 +++--- .../matrix/android/sdk/flow/FlowSession.kt | 30 ++++++------- .../sdk/SingleThreadCoroutineDispatcher.kt | 2 +- .../MatrixCoroutineDispatchers.kt | 6 +-- .../matrix/android/sdk/api/session/Session.kt | 3 ++ .../android/sdk/api/session/room/Room.kt | 3 ++ .../internal/crypto/DefaultCryptoService.kt | 2 +- .../sdk/internal/crypto/DeviceListManager.kt | 2 +- .../sdk/internal/crypto/EventDecryptor.kt | 2 +- .../crypto/InboundGroupSessionStore.kt | 2 +- .../crypto/IncomingGossipingRequestManager.kt | 2 +- .../crypto/OutgoingGossipingRequestManager.kt | 2 +- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../megolm/MXMegolmDecryptionFactory.kt | 2 +- .../algorithms/megolm/MXMegolmEncryption.kt | 2 +- .../megolm/MXMegolmEncryptionFactory.kt | 2 +- .../algorithms/olm/MXOlmEncryptionFactory.kt | 2 +- .../crypto/crosssigning/ComputeTrustTask.kt | 2 +- .../DefaultCrossSigningService.kt | 2 +- .../keysbackup/DefaultKeysBackupService.kt | 2 +- .../DefaultSharedSecretStorageService.kt | 2 +- .../DefaultVerificationService.kt | 2 +- .../sdk/internal/di/MatrixComponent.kt | 2 +- .../android/sdk/internal/di/MatrixModule.kt | 2 +- .../internal/session/DefaultFileService.kt | 2 +- .../sdk/internal/session/DefaultSession.kt | 2 + .../sdk/internal/session/SessionComponent.kt | 2 +- .../identity/DefaultIdentityService.kt | 2 +- .../session/profile/DefaultProfileService.kt | 2 +- .../sdk/internal/session/room/DefaultRoom.kt | 4 +- .../sdk/internal/session/room/RoomFactory.kt | 7 ++- .../session/room/draft/DefaultDraftService.kt | 2 +- .../internal/session/sync/job/SyncService.kt | 2 +- .../android/sdk/internal/task/TaskExecutor.kt | 2 +- vector/build.gradle | 2 + .../androidx/lifecycle/LifecycleRegistry.kt | 44 ------------------- .../quads/SharedSecureStorageViewModelTest.kt | 3 ++ .../app/test/TestCoroutineDispatchers.kt | 28 ++++++++++++ .../im/vector/app/test/fakes/FakeSession.kt | 3 ++ 41 files changed, 108 insertions(+), 102 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal/util => api}/MatrixCoroutineDispatchers.kt (85%) delete mode 100644 vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt create mode 100644 vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt diff --git a/dependencies.gradle b/dependencies.gradle index 4d1fdb6a60..d971d6fd47 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -100,7 +100,8 @@ ext.libs = [ 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", 'mavericks' : "com.airbnb.android:mavericks:$mavericks", - 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks" + 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks", + 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" ], mockk : [ 'mockk' : "io.mockk:mockk:$mockk", diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt index f974d89d45..72493325c3 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowExt.kt @@ -16,14 +16,14 @@ package org.matrix.android.sdk.flow -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.withContext -internal fun Flow.startWith(supplier: suspend () -> T): Flow { +internal fun Flow.startWith(dispatcher: CoroutineDispatcher, supplier: suspend () -> T): Flow { return onStart { - val value = withContext(Dispatchers.IO) { + val value = withContext(dispatcher) { supplier() } emit(value) 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 a78597ee46..42c1476b79 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 @@ -36,42 +36,42 @@ class FlowRoom(private val room: Room) { fun liveRoomSummary(): Flow> { return room.getRoomSummaryLive().asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.roomSummary().toOptional() } } fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow> { return room.getRoomMembersLive(queryParams).asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getRoomMembers(queryParams) } } fun liveAnnotationSummary(eventId: String): Flow> { return room.getEventAnnotationsSummaryLive(eventId).asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getEventAnnotationsSummary(eventId).toOptional() } } fun liveTimelineEvent(eventId: String): Flow> { return room.getTimeLineEventLive(eventId).asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getTimeLineEvent(eventId).toOptional() } } fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow> { return room.getStateEventLive(eventType, stateKey).asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getStateEvent(eventType, stateKey).toOptional() } } fun liveStateEvents(eventTypes: Set): Flow> { return room.getStateEventsLive(eventTypes).asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getStateEvents(eventTypes) } } @@ -90,7 +90,7 @@ class FlowRoom(private val room: Room) { fun liveDraft(): Flow> { return room.getDraftLive().asFlow() - .startWith { + .startWith(room.coroutineDispatchers.io) { room.getDraft().toOptional() } } 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 75f482adfd..13fd097bcd 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 @@ -46,35 +46,35 @@ class FlowSession(private val session: Session) { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow> { return session.getRoomSummariesLive(queryParams).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.getRoomSummaries(queryParams) } } fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Flow> { return session.getGroupSummariesLive(queryParams).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.getGroupSummaries(queryParams) } } fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Flow> { return session.spaceService().getSpaceSummariesLive(queryParams).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.spaceService().getSpaceSummaries(queryParams) } } fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Flow> { return session.getBreadcrumbsLive(queryParams).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.getBreadcrumbs(queryParams) } } fun liveMyDevicesInfo(): Flow> { return session.cryptoService().getLiveMyDevicesInfo().asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.cryptoService().getMyDevicesInfo() } } @@ -89,14 +89,14 @@ class FlowSession(private val session: Session) { fun liveUser(userId: String): Flow> { return session.getUserLive(userId).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.getUser(userId).toOptional() } } fun liveRoomMember(userId: String, roomId: String): Flow> { return session.getRoomMemberLive(userId, roomId).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.getRoomMember(userId, roomId).toOptional() } } @@ -115,45 +115,45 @@ class FlowSession(private val session: Session) { fun liveThreePIds(refreshData: Boolean): Flow> { return session.getThreePidsLive(refreshData).asFlow() - .startWith { session.getThreePids() } + .startWith(session.coroutineDispatchers.io) { session.getThreePids() } } fun livePendingThreePIds(): Flow> { return session.getPendingThreePidsLive().asFlow() - .startWith { session.getPendingThreePids() } + .startWith(session.coroutineDispatchers.io) { session.getPendingThreePids() } } fun liveUserCryptoDevices(userId: String): Flow> { return session.cryptoService().getLiveCryptoDeviceInfo(userId).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.cryptoService().getCryptoDeviceInfo(userId) } } fun liveCrossSigningInfo(userId: String): Flow> { return session.cryptoService().crossSigningService().getLiveCrossSigningKeys(userId).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.cryptoService().crossSigningService().getUserCrossSigningKeys(userId).toOptional() } } fun liveCrossSigningPrivateKeys(): Flow> { return session.cryptoService().crossSigningService().getLiveCrossSigningPrivateKeys().asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.cryptoService().crossSigningService().getCrossSigningPrivateKeys().toOptional() } } fun liveUserAccountData(types: Set): Flow> { return session.accountDataService().getLiveUserAccountDataEvents(types).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.accountDataService().getUserAccountDataEvents(types) } } fun liveRoomAccountData(types: Set): Flow> { return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.accountDataService().getRoomAccountDataEvents(types) } } @@ -165,7 +165,7 @@ class FlowSession(private val session: Session) { excludedTypes: Set? = null ): Flow> { return session.widgetService().getRoomWidgetsLive(roomId, widgetId, widgetTypes, excludedTypes).asFlow() - .startWith { + .startWith(session.coroutineDispatchers.io) { session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt index 192f6442b2..3e3af10799 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.asCoroutineDispatcher -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import java.util.concurrent.Executors internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt similarity index 85% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt index b44a543c1c..592974be74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util +package org.matrix.android.sdk.api import kotlinx.coroutines.CoroutineDispatcher -internal data class MatrixCoroutineDispatchers( +data class MatrixCoroutineDispatchers( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, val main: CoroutineDispatcher, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index bde68da9d7..124d7d9dae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -20,6 +20,7 @@ import androidx.annotation.MainThread import androidx.lifecycle.LiveData import kotlinx.coroutines.flow.SharedFlow import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService @@ -82,6 +83,8 @@ interface Session : SecureStorageService, AccountService { + val coroutineDispatchers: MatrixCoroutineDispatchers + /** * The params associated to the session */ 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 ebe96b6382..6c0e730499 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService @@ -61,6 +62,8 @@ interface Room : RoomAccountDataService, RoomVersionService { + val coroutineDispatchers: MatrixCoroutineDispatchers + /** * The roomId of this room */ 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 7115ff5db2..7b96148e2e 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 @@ -29,6 +29,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.crypto.MXCryptoConfig @@ -93,7 +94,6 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 8a91376b60..494e6d7cc7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel @@ -29,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.logLimit import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index fe17dd08e4..70081ebdd7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers 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 @@ -33,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import javax.inject.Inject import kotlin.jvm.Throws diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index 3825a5dab2..e7a46750b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.crypto import android.util.LruCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import java.util.Timer import java.util.TimerTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index e8640d5011..220f25ec80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -38,7 +39,6 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber import java.util.concurrent.Executors diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index 6fc7103668..fd60e43260 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -19,13 +19,13 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index d2f6bd0382..d7411ad0be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers 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 @@ -41,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber internal class MXMegolmDecryption(private val userId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 91640523fc..29f9d193f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager @@ -25,7 +26,6 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXMegolmDecryptionFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 63fe678229..031bb4e194 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -39,7 +40,6 @@ import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.convertToUTF8 import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt index 9f6312ea97..238d7eed88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction @@ -27,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXMegolmEncryptionFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt index 68a95e395b..50ce2d2bf3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.crypto.algorithms.olm +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForUsersAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt index 113255bb7e..b470ab34bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt @@ -16,13 +16,13 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal interface ComputeTrustTask : Task { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 8a851b1267..83de06a668 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -22,6 +22,7 @@ import androidx.work.ExistingWorkPolicy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService @@ -42,7 +43,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.olm.OlmPkSigning diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index c6e2c1217f..fe2de6aa9c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError @@ -83,7 +84,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index ad3bc012df..e6d8b5e84f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.secrets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService @@ -44,7 +45,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 768109979d..388ecb9659 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -21,6 +21,7 @@ import android.os.Looper import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -83,7 +84,6 @@ import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import java.util.UUID import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index 5bc519e960..81a067f2c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -24,6 +24,7 @@ import dagger.Component import okhttp3.OkHttpClient import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.raw.RawService @@ -35,7 +36,6 @@ import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.system.SystemModule import org.matrix.olm.OlmManager import java.io.File diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 4cd960f426..9cab307c61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -23,7 +23,7 @@ import dagger.Provides import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.createBackgroundHandler import org.matrix.olm.OlmManager import java.io.File diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 414c018074..46c5967876 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService @@ -33,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.file.AtomicFileCreator import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.writeToFile diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index d41bf8a702..7260517ec5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -22,6 +22,7 @@ import io.realm.RealmConfiguration import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService @@ -86,6 +87,7 @@ internal class DefaultSession @Inject constructor( private val globalErrorHandler: GlobalErrorHandler, @SessionId override val sessionId: String, + override val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val realmConfiguration: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 71031a4614..8ee3f44f20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session import dagger.BindsInstance import dagger.Component +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker @@ -62,7 +63,6 @@ import org.matrix.android.sdk.internal.session.user.UserModule import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataModule import org.matrix.android.sdk.internal.session.widgets.WidgetModule import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.system.SystemModule @Component(dependencies = [MatrixComponent::class], diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index da37948cd4..37d9a4e74f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleRegistry import dagger.Lazy import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull @@ -51,7 +52,6 @@ import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureProtocol import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 386fec8256..a19832c523 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.where import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService @@ -35,7 +36,6 @@ import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor, 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 8afd690f64..cb4bcdb606 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 @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room @@ -71,7 +72,8 @@ internal class DefaultRoom(override val roomId: String, private val roomVersionService: RoomVersionService, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask + private val searchTask: SearchTask, + override val coroutineDispatchers: MatrixCoroutineDispatchers ) : Room, TimelineService by timelineService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index d44eb32529..4ab06338a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope @@ -66,7 +67,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask) : + private val searchTask: SearchTask, + private val coroutineDispatchers: MatrixCoroutineDispatchers) : RoomFactory { override fun create(roomId: String): Room { @@ -92,7 +94,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomVersionService = roomVersionServiceFactory.create(roomId), sendStateTask = sendStateTask, searchTask = searchTask, - viaParameterFinder = viaParameterFinder + viaParameterFinder = viaParameterFinder, + coroutineDispatchers = coroutineDispatchers ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index 046f8ba8ba..3867e0dc8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -21,10 +21,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, private val draftRepository: DraftRepository, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index cce169c246..9480cc73f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session @@ -34,7 +35,6 @@ import org.matrix.android.sdk.internal.session.sync.SyncPresence import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt index 86848d1018..57574a96fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt @@ -21,10 +21,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.toCancelable import timber.log.Timber import javax.inject.Inject diff --git a/vector/build.gradle b/vector/build.gradle index 55e03de3f3..ffc90f4750 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -391,6 +391,7 @@ dependencies { //TODO: remove when entirely migrated to Flow implementation libs.airbnb.mavericksRx + // Work implementation libs.androidx.work @@ -513,6 +514,7 @@ dependencies { testImplementation libs.mockk.mockk // Plant Timber tree for test testImplementation libs.tests.timberJunitRule + testImplementation libs.airbnb.mavericksTesting // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' diff --git a/vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt b/vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt deleted file mode 100644 index 15a76f5e1e..0000000000 --- a/vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.lifecycle - -/** - * Manual test override to stop BaseMvRxViewModel from interacting with the android looper/main thread - * Tests will run on their original test worker threads - * - * This has been fixed is newer versions of Mavericks via LifecycleRegistry.createUnsafe - * https://github.com/airbnb/mavericks/blob/master/mvrx-rxjava2/src/main/kotlin/com/airbnb/mvrx/BaseMvRxViewModel.kt#L61 - */ -@Suppress("UNUSED") -class LifecycleRegistry(@Suppress("UNUSED_PARAMETER") lifecycleOwner: LifecycleOwner) : Lifecycle() { - - private var state = State.INITIALIZED - - fun setCurrentState(state: State) { - this.state = state - } - - override fun addObserver(observer: LifecycleObserver) { - TODO("Not yet implemented") - } - - override fun removeObserver(observer: LifecycleObserver) { - TODO("Not yet implemented") - } - - override fun getCurrentState() = state -} diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index 8f48f10868..0fff663972 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -17,6 +17,7 @@ package im.vector.app.features.crypto.quads import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.test.MvRxTestRule import im.vector.app.test.InstantRxRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider @@ -40,6 +41,8 @@ class SharedSecureStorageViewModelTest { @get:Rule val instantRx = InstantRxRule() + @get:Rule + val mvrxTestRule = MvRxTestRule() private val stringProvider = FakeStringProvider() private val session = FakeSession() diff --git a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt new file mode 100644 index 0000000000..bf24d146e6 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test + +import kotlinx.coroutines.Dispatchers +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers + +internal val testCoroutineDispatchers = MatrixCoroutineDispatchers( + io = Dispatchers.Main, + computation = Dispatchers.Main, + main = Dispatchers.Main, + crypto = Dispatchers.Main, + dmVerif = Dispatchers.Main +) 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 f5ee51b020..91403b3b2c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -16,6 +16,7 @@ package im.vector.app.test.fakes +import im.vector.app.test.testCoroutineDispatchers import io.mockk.mockk import org.matrix.android.sdk.api.session.Session @@ -23,6 +24,8 @@ class FakeSession( val fakeCryptoService: FakeCryptoService = FakeCryptoService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService() ) : Session by mockk(relaxed = true) { + override fun cryptoService() = fakeCryptoService override val sharedSecretStorageService = fakeSharedSecretStorageService + override val coroutineDispatchers = testCoroutineDispatchers } From 786dec5dc0f7834768fa320baa261477ec4e9752 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 12:42:35 +0100 Subject: [PATCH 073/172] observing both the email pushers and email pids so that displayed email pushers are always in sync --- .../sdk/internal/extensions/LiveData.kt | 23 +++++++++++++++++++ ...rSettingsNotificationPreferenceFragment.kt | 18 +++++++-------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt index 718e7869dd..fecbb874d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.extensions import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { @@ -27,3 +28,25 @@ inline fun LiveData.observeK(owner: LifecycleOwner, crossinline observer: inline fun LiveData.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { this.observe(owner, Observer { it?.run(observer) }) } + +fun combineLatest(source1: LiveData, source2: LiveData, mapper: (T1, T2) -> R): LiveData { + val combined = MediatorLiveData() + var source1Result: T1? = null + var source2Result: T2? = null + + fun notify() { + if (source1Result != null && source2Result != null) { + combined.value = mapper(source1Result!!, source2Result!!) + } + } + + combined.addSource(source1) { + source1Result = it + notify() + } + combined.addSource(source2) { + source2Result = it + notify() + } + return combined +} diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt index 1c73fbb470..b014b3d2dc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt @@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.internal.extensions.combineLatest import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @@ -339,11 +340,11 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( override fun onPreferenceTreeClick(preference: Preference?): Boolean { return when (preference?.key) { - VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> { + VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY -> { updateEnabledForAccount(preference) true } - else -> { + else -> { return super.onPreferenceTreeClick(preference) } } @@ -406,12 +407,9 @@ private fun Session.getEmailsWithPushInformation(): List>> { - return getThreePidsLive(refreshData = true) - .distinctUntilChanged() - .map { threePids -> - val emailPushers = getPushers().filter { it.kind == Pusher.KIND_EMAIL } - threePids - .filterIsInstance() - .map { it to emailPushers.any { pusher -> pusher.pushKey == it.email } } - } + val emailThreePids = getThreePidsLive(refreshData = true).map { it.filterIsInstance() } + val emailPushers = getPushersLive().map { it.filter { pusher -> pusher.kind == Pusher.KIND_EMAIL } } + return combineLatest(emailThreePids, emailPushers) { emailThreePidsResult, emailPushersResult -> + emailThreePidsResult.map { it to emailPushersResult.any { pusher -> pusher.pushKey == it.email } } + }.distinctUntilChanged() } From b79b7f5740a674ca87f3f75e078eabfddfa1f728 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 12:59:26 +0100 Subject: [PATCH 074/172] adding changelog entry --- changelog.d/4106.bugfix | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog.d/4106.bugfix diff --git a/changelog.d/4106.bugfix b/changelog.d/4106.bugfix new file mode 100644 index 0000000000..6ca030f8c4 --- /dev/null +++ b/changelog.d/4106.bugfix @@ -0,0 +1,2 @@ +Fixes push notification emails list not refreshing the first time seeing the notifications page. +Also improves the error handling in the email notification toggling by using synchronous flows instead of the WorkManager \ No newline at end of file From d11f4e5e31071000f7be563d447e09190e6baf73 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 14:06:24 +0200 Subject: [PATCH 075/172] Add changelog --- changelog.d/4193.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4193.bugfix diff --git a/changelog.d/4193.bugfix b/changelog.d/4193.bugfix new file mode 100644 index 0000000000..a395e70cee --- /dev/null +++ b/changelog.d/4193.bugfix @@ -0,0 +1 @@ +Fix random crash when user logs out just after the log in. \ No newline at end of file From a7ec76bae3f919771ebbdc823aa2de3c10f15465 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 14:20:20 +0200 Subject: [PATCH 076/172] Also call monarchyWriteAsyncExecutor.awaitTermination --- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 f5e3916bb9..40678a6ce6 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 @@ -25,6 +25,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.where +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState @@ -101,6 +102,7 @@ import org.matrix.olm.OlmException import org.matrix.olm.OlmOutboundGroupSession import timber.log.Timber import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.collections.set @@ -206,6 +208,10 @@ internal class RealmCryptoStore @Inject constructor( // Ensure no async request will be run later val tasks = monarchyWriteAsyncExecutor.shutdownNow() Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled") + tryOrNull("Interrupted") { + // Wait 1 minute max + monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES) + } olmSessionsToRelease.forEach { it.value.olmSession.releaseSession() From 2b2f5be83e3049a207d4ac41f02212a84aff2323 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 12 Oct 2021 15:51:27 +0300 Subject: [PATCH 077/172] Fix typo in filename --- ...c-from-external-soruces.yml => sync-from-external-sources.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{sync-from-external-soruces.yml => sync-from-external-sources.yml} (100%) diff --git a/.github/workflows/sync-from-external-soruces.yml b/.github/workflows/sync-from-external-sources.yml similarity index 100% rename from .github/workflows/sync-from-external-soruces.yml rename to .github/workflows/sync-from-external-sources.yml From da28ddfabd701c444a8b3594d29117d5f211baaf Mon Sep 17 00:00:00 2001 From: koh6uawi <89084173+koh6uawi@users.noreply.github.com> Date: Tue, 12 Oct 2021 15:10:33 +0200 Subject: [PATCH 078/172] Use "wrap_content" instead of "match_parent" Fix a linter error --- vector/src/main/res/layout/dialog_select_text_size.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/dialog_select_text_size.xml b/vector/src/main/res/layout/dialog_select_text_size.xml index 0790adf88a..967386476a 100644 --- a/vector/src/main/res/layout/dialog_select_text_size.xml +++ b/vector/src/main/res/layout/dialog_select_text_size.xml @@ -6,7 +6,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/text_selection_group_view" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="?dialogPreferredPadding" android:paddingTop="12dp" From c9f69b3fc5e1ca3526ad31bc2f3e91ffbd6a6577 Mon Sep 17 00:00:00 2001 From: koh6uawi <89084173+koh6uawi@users.noreply.github.com> Date: Tue, 12 Oct 2021 15:12:26 +0200 Subject: [PATCH 079/172] Move XML namespaces to root element --- vector/src/main/res/layout/dialog_select_text_size.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/dialog_select_text_size.xml b/vector/src/main/res/layout/dialog_select_text_size.xml index 967386476a..e93f524c1d 100644 --- a/vector/src/main/res/layout/dialog_select_text_size.xml +++ b/vector/src/main/res/layout/dialog_select_text_size.xml @@ -1,9 +1,9 @@ - Date: Tue, 12 Oct 2021 16:45:31 +0200 Subject: [PATCH 080/172] Cleanup the PR about presence --- .../api/session/presence/PresenceService.kt | 14 ++++---- .../session/presence/model/PresenceEnum.kt | 9 ++--- .../session/presence/model/UserPresence.kt | 5 +-- .../session/room/model/RoomMemberSummary.kt | 2 +- .../sdk/api/session/room/model/RoomSummary.kt | 2 +- .../database/RealmSessionStoreMigration.kt | 1 + .../database/model/RoomMemberSummaryEntity.kt | 1 - .../model/presence/UserPresenceEntity.kt | 10 +++--- .../presence/model/GetPresenceResponse.kt | 1 + .../session/presence/model/PresenceContent.kt | 22 ++++++++++++- .../session/presence/model/SetPresenceBody.kt | 3 +- .../service/DefaultPresenceService.kt | 26 +++++++++++---- .../presence/service/task/SetPresenceTask.kt | 6 ++-- .../SyncResponsePostTreatmentAggregator.kt | 4 +-- .../sync/handler/PresenceSyncHandler.kt | 33 ++++++++++--------- ...cResponsePostTreatmentAggregatorHandler.kt | 4 +-- ...leMatrixItemWithPowerLevelWithPresence.kt} | 14 ++------ .../core/ui/views/PresenceStateImageView.kt | 4 +-- .../home/room/list/RoomSummaryItem.kt | 2 +- .../members/RoomMemberListController.kt | 6 ++-- .../main/res/layout/fragment_room_detail.xml | 3 +- vector/src/main/res/layout/item_room.xml | 3 +- .../view_stub_room_member_profile_header.xml | 2 +- 23 files changed, 101 insertions(+), 76 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/session/presence/model/PresenceEnum.kt (79%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/session/presence/model/UserPresence.kt (87%) rename vector/src/main/java/im/vector/app/core/epoxy/profiles/{ProfileMatrixItemWithPresence.kt => ProfileMatrixItemWithPowerLevelWithPresence.kt} (62%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt index 2316eec3b7..82a81f4b64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt @@ -17,25 +17,25 @@ package org.matrix.android.sdk.api.session.presence -import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse -import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence /** * This interface defines methods for handling user presence information. */ - interface PresenceService { - /** * Update the presence status for the current user * @param presence the new presence state - * @param message the status message to attach to this state + * @param statusMsg the status message to attach to this state */ - suspend fun setMyPresence(presence: PresenceEnum, message: String? = null) + suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String? = null) /** * Fetch the given user's presence state. * @param userId the userId whose presence state to get. */ - suspend fun fetchPresence(userId: String): GetPresenceResponse + suspend fun fetchPresence(userId: String): UserPresence + + // TODO Add live data (of Flow) of the presence of a userId } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt similarity index 79% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt index c6dd307e30..6d9994ef1c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceEnum.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,16 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.presence.model +package org.matrix.android.sdk.api.session.presence.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -/** - * Annotate enums with @JsonClass(generateAdapter = false) to prevent - * them from being removed/obfuscated from your code by R8/ProGuard. - */ - @JsonClass(generateAdapter = false) enum class PresenceEnum(val value: String) { @Json(name = "online") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt index f7cd3b3834..6b33ff07d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/UserPresence.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.matrix.android.sdk.internal.session.presence.model + +package org.matrix.android.sdk.api.session.presence.model data class UserPresence( val lastActiveAgo: Long? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt index 87036dfdd5..39177a4296 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.api.session.room.model -import org.matrix.android.sdk.internal.session.presence.model.UserPresence +import org.matrix.android.sdk.api.session.presence.model.UserPresence /** * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index d43eefbebe..10cad026bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -17,11 +17,11 @@ package org.matrix.android.sdk.api.session.room.model import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.internal.session.presence.model.UserPresence /** * This class holds some data of a room. 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 f082f19368..05137f8105 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 @@ -355,6 +355,7 @@ internal object RealmSessionStoreMigration : RealmMigration { ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java) ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true) ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java) + ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java) val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return realm.schema.get("RoomSummaryEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index a3a38aafca..a8a76d1681 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -46,7 +46,6 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = if (value != field) field = value } - fun getBestName() = displayName?.takeIf { it.isNotBlank() } ?: userId fun toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt index 64a2cdb6dc..5713337ec5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt @@ -18,14 +18,15 @@ package org.matrix.android.sdk.internal.database.model.presence import io.realm.RealmObject import io.realm.annotations.PrimaryKey -import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum -import org.matrix.android.sdk.internal.session.presence.model.UserPresence +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence internal open class UserPresenceEntity(@PrimaryKey var userId: String = "", var lastActiveAgo: Long? = null, var statusMessage: String? = null, var isCurrentlyActive: Boolean? = null, - var avatarUrl: String? = null + var avatarUrl: String? = null, + var displayName: String? = null ) : RealmObject() { var presence: PresenceEnum @@ -46,4 +47,5 @@ internal fun UserPresenceEntity.toUserPresence() = lastActiveAgo, statusMessage, isCurrentlyActive, - presence) + presence + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt index 707f0a055f..a7552f7b02 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.presence.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum @JsonClass(generateAdapter = true) data class GetPresenceResponse( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt index 86a8b31a91..45e0fcf06e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt @@ -18,15 +18,35 @@ package org.matrix.android.sdk.internal.session.presence.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum /** * Class representing the EventType.PRESENCE event content */ @JsonClass(generateAdapter = true) data class PresenceContent( + /** + * Required. The presence state for this user. One of: ["online", "offline", "unavailable"] + */ @Json(name = "presence") val presence: PresenceEnum, + /** + * The last time since this used performed some action, in milliseconds. + */ @Json(name = "last_active_ago") val lastActiveAgo: Long? = null, + /** + * An optional description to accompany the presence. + */ @Json(name = "status_msg") val statusMessage: String? = null, + /** + * Whether the user is currently active + */ @Json(name = "currently_active") val isCurrentlyActive: Boolean = false, - @Json(name = "avatar_url") val avatarUrl: String? = null + /** + * The current avatar URL for this user, if any. + */ + @Json(name = "avatar_url") val avatarUrl: String? = null, + /** + * The current display name for this user, if any. + */ + @Json(name = "displayname") val displayName: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt index 0ddb140908..0c81791ec0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt @@ -17,11 +17,12 @@ package org.matrix.android.sdk.internal.session.presence.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum @JsonClass(generateAdapter = true) internal data class SetPresenceBody( @Json(name = "presence") val presence: PresenceEnum, @Json(name = "status_msg") - val message: String? + val statusMsg: String? ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt index cb00192ef3..1083d5b4c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt @@ -18,19 +18,31 @@ package org.matrix.android.sdk.internal.session.presence.service import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask import javax.inject.Inject -internal class DefaultPresenceService @Inject constructor(@UserId private val userId: String, - private val setPresenceTask: SetPresenceTask, - private val getPresenceTask: GetPresenceTask) : PresenceService { +internal class DefaultPresenceService @Inject constructor( + @UserId private val userId: String, + private val setPresenceTask: SetPresenceTask, + private val getPresenceTask: GetPresenceTask +) : PresenceService { - override suspend fun setMyPresence(presence: PresenceEnum, message: String?) { - setPresenceTask.execute(SetPresenceTask.Params(userId, presence, message)) + override suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String?) { + setPresenceTask.execute(SetPresenceTask.Params(userId, presence, statusMsg)) } - override suspend fun fetchPresence(userId: String) = getPresenceTask.execute(GetPresenceTask.Params(userId)) + override suspend fun fetchPresence(userId: String): UserPresence { + val result = getPresenceTask.execute(GetPresenceTask.Params(userId)) + + return UserPresence( + lastActiveAgo = result.lastActiveAgo, + statusMessage = result.message, + isCurrentlyActive = result.isCurrentlyActive, + presence = result.presence + ) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt index 011ec1e41a..1b3bdabd30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.presence.service.task +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.presence.PresenceAPI -import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -29,7 +29,7 @@ internal abstract class SetPresenceTask : Task { data class Params( val userId: String, val presence: PresenceEnum, - val message: String? + val statusMsg: String? ) } @@ -40,7 +40,7 @@ internal class DefaultSetPresenceTask @Inject constructor( override suspend fun execute(params: Params): Any { return executeRequest(globalErrorReceiver) { - val setPresenceBody = SetPresenceBody(params.presence, params.message) + val setPresenceBody = SetPresenceBody(params.presence, params.statusMsg) presenceAPI.setPresence(params.userId, setPresenceBody) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index e2f9ba2f29..fe44531390 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -1,11 +1,11 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt index b395b522c8..fe173a35c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt @@ -30,23 +30,24 @@ import javax.inject.Inject internal class PresenceSyncHandler @Inject constructor() { fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) { - presenceSyncResponse?.events?.filter { event -> - event.type == EventType.PRESENCE - }?.forEach { event -> - val content = event.getPresenceContent() ?: return@forEach - val userId = event.senderId ?: return@forEach - val userPresenceEntity = UserPresenceEntity( - userId = userId, - lastActiveAgo = content.lastActiveAgo, - statusMessage = content.statusMessage, - isCurrentlyActive = content.isCurrentlyActive, - avatarUrl = content.avatarUrl - ).also { - it.presence = content.presence - } + presenceSyncResponse?.events + ?.filter { event -> event.type == EventType.PRESENCE } + ?.forEach { event -> + val content = event.getPresenceContent() ?: return@forEach + val userId = event.senderId ?: return@forEach + val userPresenceEntity = UserPresenceEntity( + userId = userId, + lastActiveAgo = content.lastActiveAgo, + statusMessage = content.statusMessage, + isCurrentlyActive = content.isCurrentlyActive, + avatarUrl = content.avatarUrl, + displayName = content.displayName + ).also { + it.presence = content.presence + } - storePresenceToDB(realm, userPresenceEntity) - } + storePresenceToDB(realm, userPresenceEntity) + } } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index 39ae94c88a..1e0e87a450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -1,11 +1,11 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt similarity index 62% rename from vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt rename to vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt index 022d3dd152..92216cbb38 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPresence.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt @@ -17,28 +17,18 @@ package im.vector.app.core.epoxy.profiles -import android.widget.TextView -import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.extensions.setTextOrHide -import org.matrix.android.sdk.internal.session.presence.model.UserPresence +import org.matrix.android.sdk.api.session.presence.model.UserPresence @EpoxyModelClass(layout = R.layout.item_profile_matrix_item) -abstract class ProfileMatrixItemWithPresence : BaseProfileMatrixItem() { +abstract class ProfileMatrixItemWithPowerLevelWithPresence : ProfileMatrixItemWithPowerLevel() { - @EpoxyAttribute var powerLevelLabel: CharSequence? = null @EpoxyAttribute var userPresence: UserPresence? = null override fun bind(holder: Holder) { super.bind(holder) - holder.powerLabel.setTextOrHide(powerLevelLabel) holder.presenceImageView.render(userPresence = userPresence) - holder.editableView.isVisible = false - } - - class Holder : ProfileMatrixItem.Holder() { - val powerLabel by bind(R.id.matrixItemPowerLevelLabel) } } diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt index 3e1b40b3c7..301f8afdc9 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt @@ -21,8 +21,8 @@ import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView import androidx.core.view.isVisible import im.vector.app.R -import org.matrix.android.sdk.internal.session.presence.model.PresenceEnum -import org.matrix.android.sdk.internal.session.presence.model.UserPresence +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence /** * Custom ImageView to dynamically render Presence state in multiple screens 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 dda6ffefde..fd0ccd17e9 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 @@ -38,8 +38,8 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.internal.session.presence.model.UserPresence @EpoxyModelClass(layout = R.layout.item_room) abstract class RoomSummaryItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt index 9a2ee04fdc..ed0b88c8d3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt @@ -21,7 +21,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.epoxy.profiles.profileMatrixItem -import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPresence +import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevelWithPresence import im.vector.app.core.extensions.join import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -29,11 +29,11 @@ import im.vector.app.features.home.AvatarRenderer import me.gujun.android.span.span import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.session.presence.model.UserPresence import javax.inject.Inject class RoomMemberListController @Inject constructor( @@ -130,7 +130,7 @@ class RoomMemberListController @Inject constructor( ) { val powerLabel = stringProvider.getString(powerLevelCategory.titleRes) - profileMatrixItemWithPresence { + profileMatrixItemWithPowerLevelWithPresence { id(roomMember.userId) matrixItem(roomMember.toMatrixItem()) avatarRenderer(host.avatarRenderer) diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index ccebeaba95..c0ac3170e5 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -64,6 +64,7 @@ app:layout_constraintCircleAngle="135" app:layout_constraintCircleRadius="20dp" tools:ignore="MissingConstraints" + tools:layout_constraintCircleRadius="8dp" tools:src="@drawable/ic_presence_offline" tools:visibility="visible" /> @@ -97,8 +98,8 @@ app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/roomToolbarDecorationImageView" app:layout_constraintTop_toTopOf="parent" - app:layout_goneMarginStart="7dp" app:layout_constraintVertical_chainStyle="packed" + app:layout_goneMarginStart="7dp" tools:text="@sample/rooms.json/data/name" /> + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml index 0aff5e7a5a..0b3e21a348 100644 --- a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml +++ b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml @@ -56,7 +56,7 @@ app:layout_constraintEnd_toStartOf="@+id/memberProfileNameView" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/memberProfileNameView"/> + app:layout_constraintTop_toTopOf="@+id/memberProfileNameView" /> Date: Tue, 12 Oct 2021 17:53:56 +0300 Subject: [PATCH 081/172] To be able to resend, stop all voice actions without deleting. --- .../app/features/home/room/detail/RoomDetailAction.kt | 2 +- .../app/features/home/room/detail/RoomDetailFragment.kt | 2 ++ .../app/features/home/room/detail/RoomDetailViewModel.kt | 6 +++--- .../home/room/detail/composer/VoiceMessageHelper.kt | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 9c5f436728..a9b9f8000b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -115,5 +115,5 @@ sealed class RoomDetailAction : VectorViewModelAction { object PauseRecordingVoiceMessage : RoomDetailAction() data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction() object PlayOrPauseRecordingPlayback : RoomDetailAction() - object EndAllVoiceActions : RoomDetailAction() + data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 2066bf9d08..a144715aa2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1095,6 +1095,8 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) + // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. + roomDetailViewModel.handle(RoomDetailAction.EndAllVoiceActions(deleteRecord = false)) views.voiceMessageRecorderView.initVoiceRecordingViews() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index bd11ec6718..7971b5123a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -343,7 +343,7 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) RoomDetailAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() RoomDetailAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() - RoomDetailAction.EndAllVoiceActions -> handleEndAllVoiceActions() + is RoomDetailAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) is RoomDetailAction.RoomUpgradeSuccess -> { setState { copy(joinUpgradedRoomAsync = Success(action.replacementRoomId)) @@ -649,8 +649,8 @@ class RoomDetailViewModel @AssistedInject constructor( voiceMessageHelper.startOrPauseRecordingPlayback() } - private fun handleEndAllVoiceActions() { - voiceMessageHelper.stopAllVoiceActions() + private fun handleEndAllVoiceActions(deleteRecord: Boolean) { + voiceMessageHelper.stopAllVoiceActions(deleteRecord) } private fun handlePauseRecordingVoiceMessage() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt index b0880623de..adcd6a3008 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/VoiceMessageHelper.kt @@ -217,10 +217,12 @@ class VoiceMessageHelper @Inject constructor( playbackTicker = null } - fun stopAllVoiceActions() { + fun stopAllVoiceActions(deleteRecord: Boolean = true) { stopRecording() stopPlayback() - deleteRecording() + if (deleteRecord) { + deleteRecording() + } playbackTracker.clear() } } From 256cb7093dfe3b3dacb18604a278ee26140eefdc Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 16:37:40 +0100 Subject: [PATCH 082/172] catching ensureOlmSessionsForDevicesAction errors during the event decryption flow - we currently can't do much but log here as we've asynchronously start the fallback flow, catching the error at least stops a hard crash --- .../sdk/internal/crypto/EventDecryptor.kt | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index 70081ebdd7..b1680d5153 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -36,7 +37,6 @@ import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject -import kotlin.jvm.Throws @SessionScope internal class EventDecryptor @Inject constructor( @@ -146,29 +146,36 @@ internal class EventDecryptor @Inject constructor( // offload this from crypto thread (?) cryptoCoroutineScope.launch(coroutineDispatchers.computation) { - val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) + runCatching { ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) }.fold( + onSuccess = { sendDummyToDevice(ensured = it, deviceInfo, senderId) }, + onFailure = { + Timber.e("## CRYPTO | markOlmSessionForUnwedging() : failed to ensure device info ${senderId}${deviceInfo.deviceId}") + } + ) + } + } - Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") + private suspend fun sendDummyToDevice(ensured: MXUsersDevicesMap, deviceInfo: CryptoDeviceInfo, senderId: String) { + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") - // Now send a blank message on that session so the other side knows about it. - // (The keyshare request is sent in the clear so that won't do) - // We send this first such that, as long as the toDevice messages arrive in the - // same order we sent them, the other end will get this first, set up the new session, - // then get the keyshare request and send the key over this new session (because it - // is the session it has most recently received a message on). - val payloadJson = mapOf("type" to EventType.DUMMY) + // Now send a blank message on that session so the other side knows about it. + // (The keyshare request is sent in the clear so that won't do) + // We send this first such that, as long as the toDevice messages arrive in the + // same order we sent them, the other end will get this first, set up the new session, + // then get the keyshare request and send the key over this new session (because it + // is the session it has most recently received a message on). + val payloadJson = mapOf("type" to EventType.DUMMY) - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) - Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") - withContext(coroutineDispatchers.io) { - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - try { - sendToDeviceTask.execute(sendToDeviceParams) - } catch (failure: Throwable) { - Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") - } + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") + withContext(coroutineDispatchers.io) { + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + try { + sendToDeviceTask.execute(sendToDeviceParams) + } catch (failure: Throwable) { + Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") } } } From ffa5e1a1bad8efdd69b3cc5762f3f3b6d43996ec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 17:48:59 +0200 Subject: [PATCH 083/172] Add changelog file --- changelog.d/4090.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4090.feature diff --git a/changelog.d/4090.feature b/changelog.d/4090.feature new file mode 100644 index 0000000000..be8aa317a6 --- /dev/null +++ b/changelog.d/4090.feature @@ -0,0 +1 @@ +Handle Presence support, for Direct Message room \ No newline at end of file From db4c4520ea07fe8b72dc46ff8beb397a53d49ee7 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 12 Oct 2021 17:37:11 +0100 Subject: [PATCH 084/172] adding changelog entry --- changelog.d/3608.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3608.bugfix diff --git a/changelog.d/3608.bugfix b/changelog.d/3608.bugfix new file mode 100644 index 0000000000..5f219ab319 --- /dev/null +++ b/changelog.d/3608.bugfix @@ -0,0 +1 @@ +Catching event decryption crash and logging when attempting to markOlmSessionForUnwedging fails \ No newline at end of file From d4f62902a685a2e05ed817d257791f6f70a3b4d9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 12 Oct 2021 21:15:45 +0200 Subject: [PATCH 085/172] Add a paragraph about Open source --- fastlane/metadata/android/en-US/full_description.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 853885944c..ef8d4e6a27 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -36,4 +36,7 @@ Real end-to-end encryption (only those in the conversation can decrypt messages) Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done. Pick up where you left off -Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io \ No newline at end of file +Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io + +Open source +Element Android is an open source project, hosted by GitHub. Please report bugs and/or contribute to its development at https://github.com/vector-im/element-android \ No newline at end of file From 7be7d505753d678090817e41db1128492d542d05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Oct 2021 23:10:59 +0000 Subject: [PATCH 086/172] Bump libphonenumber from 8.12.34 to 8.12.35 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.34 to 8.12.35. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.34...v8.12.35) --- updated-dependencies: - dependency-name: com.googlecode.libphonenumber:libphonenumber dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index b703b13c78..7f5e8eb391 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -155,7 +155,7 @@ dependencies { implementation 'com.otaliastudios:transcoder:0.10.4' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.34' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.6.1' diff --git a/vector/build.gradle b/vector/build.gradle index a26de28099..ecbffd0b8a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -372,7 +372,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.34' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35' // rx implementation libs.rx.rxKotlin From 64dce0638f7007cc508177fa62177753b6dacfaa Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 13:39:57 +0100 Subject: [PATCH 087/172] only updating the visibility of the menu option the home details fragment adds instead of changing all the parent options - fixes the debug sync options being forced to visible --- .../java/im/vector/app/features/home/HomeDetailFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index c8fff5605b..df95bb9b14 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -108,9 +108,9 @@ class HomeDetailFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { state -> - menu.iterator().forEach { it.isVisible = state.currentTab is HomeTab.RoomList } + val isRoomList = state.currentTab is HomeTab.RoomList + menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms } - menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms super.onPrepareOptionsMenu(menu) } From c6aac34c39f6aad4cfca63f14a8149105bf3552f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 13:44:24 +0100 Subject: [PATCH 088/172] adding changelog entry --- changelog.d/4234.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4234.bugfix diff --git a/changelog.d/4234.bugfix b/changelog.d/4234.bugfix new file mode 100644 index 0000000000..e7b46a6186 --- /dev/null +++ b/changelog.d/4234.bugfix @@ -0,0 +1 @@ +Fixes the developer sync options being displayed in the home menu when developer mode is disabled \ No newline at end of file From f2da047720404da59c911d66db1e3b45924c5392 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 15:36:37 +0100 Subject: [PATCH 089/172] keeping an inmemory cache of the seen ids, fixes delayed sync responses causing already dismissed notifications from being shown again - uses a simple circular buffer to limit the memory usage --- .../notifications/CircularStringCache.kt | 36 +++++++++++++++++++ .../NotificationDrawerManager.kt | 19 ++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt diff --git a/vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt b/vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt new file mode 100644 index 0000000000..97b40daf46 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.notifications + +/** + * A FIFO circular buffer of strings + */ +class CircularStringCache(cacheSize: Int) { + + private val cache = Array(cacheSize) { "" } + private var writeIndex = 0 + + fun contains(key: String): Boolean = cache.contains(key) + + fun put(key: String) { + if (writeIndex == cache.size - 1) { + writeIndex = 0 + } + cache[writeIndex] = key + writeIndex++ + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index ecb1268892..2df093b615 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -84,6 +84,13 @@ class NotificationDrawerManager @Inject constructor(private val context: Context private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat() + /** + * An in memory FIFO cache of the seen events. + * Acts as a notification debouncer to stop already dismissed push notifications from + * displaying again when the /sync response is delayed. + */ + private val seenEventIds = CircularStringCache(cacheSize = 25) + /** Should be called as soon as a new event is ready to be displayed. The notification corresponding to this event will not be displayed until @@ -140,8 +147,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // Ignore an edit of a not displayed event in the notification drawer } } else { - // Not an edit - eventList.add(notifiableEvent) + if (seenEventIds.contains(notifiableEvent.eventId)) { + // we've already seen the event, lets' skip + Timber.d("onNotifiableEventReceived(): skipping event, already seen}") + } else { + // Not an edit + seenEventIds.put(notifiableEvent.eventId) + eventList.add(notifiableEvent) + } } } } @@ -266,7 +279,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context is InviteNotifiableEvent -> { if (autoAcceptInvites.hideInvites) { // Forget this event - eventIterator.remove() + eventIterator.remove() } else { invitationEvents.add(event) } From 10b753ad610d69543b10e3c7a3d19690e8fb0bca Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 15:42:59 +0100 Subject: [PATCH 090/172] adding changelog entry --- changelog.d/3437.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3437.bugfix diff --git a/changelog.d/3437.bugfix b/changelog.d/3437.bugfix new file mode 100644 index 0000000000..11e493e1ba --- /dev/null +++ b/changelog.d/3437.bugfix @@ -0,0 +1 @@ +Fixes reappearing notifications when dismissing notifications from slow homeservers or delayed /sync responses \ No newline at end of file From b041876fa6b8d0f54ff69cc049e588fdc228cc84 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 18:00:35 +0100 Subject: [PATCH 091/172] matching the other notifications and only alerting the group notifiation once - fixes notification sounds coming through for every message despite only vibrating for the first --- .../im/vector/app/features/notifications/NotificationUtils.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 14dfe5c6ee..aecbbe78d2 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -802,6 +802,7 @@ class NotificationUtils @Inject constructor(private val context: Context, return NotificationCompat.Builder(context, if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) // used in compat < N, after summary is built based on child notifications + .setOnlyAlertOnce(true) .setWhen(lastMessageTimestamp) .setStyle(style) .setContentTitle(stringProvider.getString(R.string.app_name)) From 99de9d4a4ff94b30366b86a0846600f1b0a57cfa Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 18:01:49 +0100 Subject: [PATCH 092/172] adding changelog entry --- changelog.d/3872.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3872.bugfix diff --git a/changelog.d/3872.bugfix b/changelog.d/3872.bugfix new file mode 100644 index 0000000000..9145771bae --- /dev/null +++ b/changelog.d/3872.bugfix @@ -0,0 +1 @@ +Fixing notification sounds being triggered for every message, now they only trigger for the first, consistent with the vibrations \ No newline at end of file From 64c532e54b3247610e181feff911e99acb8561ad Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 13 Oct 2021 18:42:02 +0100 Subject: [PATCH 093/172] allowing the first notification for each child of the group to vibrate/make a sound - by having the group and child notifications alert once we can safetly always update the group and get consistent alertOnce behaviour --- .../vector/app/features/notifications/NotificationUtils.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index aecbbe78d2..774f3db2cf 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -561,7 +561,7 @@ class NotificationUtils @Inject constructor(private val context: Context, // TODO Group should be current user display name .setGroup(stringProvider.getString(R.string.app_name)) // In order to avoid notification making sound twice (due to the summary notification) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) // Set primary color (important for Wear 2.0 Notifications). .setColor(accentColor) @@ -644,7 +644,7 @@ class NotificationUtils @Inject constructor(private val context: Context, .setContentTitle(stringProvider.getString(R.string.app_name)) .setContentText(inviteNotifiableEvent.description) .setGroup(stringProvider.getString(R.string.app_name)) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) .setColor(accentColor) .apply { @@ -708,7 +708,7 @@ class NotificationUtils @Inject constructor(private val context: Context, .setContentTitle(stringProvider.getString(R.string.app_name)) .setContentText(simpleNotifiableEvent.description) .setGroup(stringProvider.getString(R.string.app_name)) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setSmallIcon(smallIcon) .setColor(accentColor) .setAutoCancel(true) From 450c8e629e36cd862f1f69972d035b171d88fb00 Mon Sep 17 00:00:00 2001 From: Didek Date: Mon, 11 Oct 2021 23:43:46 +0000 Subject: [PATCH 094/172] Translated using Weblate (Polish) Currently translated at 77.5% (2070 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- vector/src/main/res/values-pl/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 75a6938bc0..d1428300e8 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -2386,8 +2386,8 @@ Spróbuj uruchomić ponownie aplikację. \nCzy na pewno kontynuować\? Kliknij dwukrotnie w ten link Wybierz hasło. - • Dopasowanie serwera %s jest zbanowane. - • Dopasowanie serwera %s jest dozwolone. + • Dopasowanie serwerów %s jest zbanowane. + • Dopasowanie serwerów %s jest dozwolone. Ustawiłeś serwer ACL dla tego pokoju. %s ustawił(a) serwer ACL dla tego pokoju. Uaktualniłeś tutaj. From 8dcc65dc0a626221b2da92e08dd93b0388c52671 Mon Sep 17 00:00:00 2001 From: Leonidas Shear Date: Tue, 12 Oct 2021 21:48:45 +0000 Subject: [PATCH 095/172] Translated using Weblate (Russian) Currently translated at 100.0% (2669 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 57 ++++++++++++++--------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index cf25de8f27..b96f77a040 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -312,7 +312,7 @@ Просмотр расшифрованного исходного кода Удалить Переименовать - Пожаловаться на содержимое + Содержание отчёта Активный вызов Ongoing conference call. \nПрисоединиться как %1$s или %2$s @@ -1136,16 +1136,16 @@ %d мин. - %dh - %dh - %dh - %dh + %dч + %dч + %dч + %dч - %dd - %dd - %dd - %dd + %dдень + %dдня + %dдней + %dдней Сейчас %1$s %1$s %2$s назад @@ -2735,8 +2735,8 @@ Пользователи Произошла ошибка при переводе вызова Перевод - Соединиться - Сначала спросить + Подключиться + Сначала посоветуйтесь 1 активный вызов (%1$s) · 1 приостановленный вызов 1 активный вызов (%1$s) · %2$d приостановленных вызова @@ -2762,7 +2762,7 @@ Содержание события Событие статуса отправлено! Событие отправлено! - Неисправное событие + Некорректное событие Отсутствует тип сообщения Нет содержания Содержание события @@ -2775,7 +2775,7 @@ Отправить событие статуса Отправить пользовательское событие Инструменты для разработчиков - См. подтверждение получения + Смотреть подтверждение получения Не уведомлять Уведомить без звука Уведомить со звуком @@ -2784,7 +2784,7 @@ Закрыть выбор эмодзи Открыть выбор эмодзи Доверенный уровень доверия - Предупреждающий уровень доверия + Предостерегающий уровень доверия Уровень доверия по умолчанию Выбрано Видео @@ -2835,7 +2835,7 @@ Модернизировать публичную комнату Обновление Пожалуйста, будьте терпеливы, это может занять некоторое время. - Присоединиться к замещающей комнате + Присоединиться к изменённой комнате В настоящее время люди не могут присоединиться к созданным вами приватным комнатам. \n \nМы улучшим это в рамках бета-версии, но мы просто хотели сообщить вам об этом. @@ -2864,8 +2864,8 @@ Добро пожаловать в Пространства! Добавить комнаты Добавить существующие комнаты и пространство - Вы являетесь администратором этого пространства, перед уходом убедитесь, что передали права администратора другому пользователю. - Это пространство не является публичным. Вы не сможете присоединиться к нему без приглашения. + Вы единственный администратор этого пространства. Если вы его покинете, то никто не сможет управлять им. + Вы сможете присоединиться только после повторного приглашения. Вы здесь единственный человек. Если вы уйдёте, никто не сможет присоединиться в будущем, включая вас. Покинуть пространство Добавить комнаты @@ -2936,7 +2936,7 @@ Непроверенные Неизвестное лицо Перевести на %1$s - Запросить с %1$s + Консультация с %1$s Отправить видео в исходном размере Отправить видео в исходном размере @@ -3012,13 +3012,13 @@ Нажмите на запись, чтобы остановить или прослушать ее %1$d осталось Удерживайте для записи, отпустите для отправки - Удалить записанное голосовое сообщение + Удалить запись Запись голосового сообщения Приостановить голосовое сообщение Прослушать голосовое сообщение Заблокировать голосовое сообщение Проведите для отмены - Начать голосовое сообщение + Запись голосового сообщения Разрешить любому человеку в %s найти и получить доступ. Можно выбрать и другие пространства. Требуется обновление Голосовое сообщение @@ -3054,7 +3054,7 @@ Помогите участникам пространства в поиске приватных комнат Это позволяет комнатам оставаться приватными для пространства, в то же время позволяя людям в пространстве находить их и присоединяться к ним. Все новые комнаты в пространстве будут иметь эту опцию. Помогите людям в пространствах самим находить и присоединяться к приватным комнатам, без необходимости вручную приглашать всех. - Новое: Позвольте людям в пространствах находить и присоединяться к приватным комнатам + Новое: Позволяет людям в пространствах находить и присоединяться к приватным комнатам Начался групповой вызов Все комнаты, в которых вы находитесь, будут отображаться в Начале. Показать все комнаты в Начале @@ -3091,7 +3091,7 @@ Ключевые слова не могут начинаться с \'.\' Добавить новое ключевое слово Ваши ключевые слова - Ничего + Отсутствует Только упоминания и ключевые слова Завершение вызова… Нет ответа @@ -3147,4 +3147,17 @@ Выгнанный пользователь будет удалён из этого пространства. \n \nЧтобы предотвратить его повторное присоединение, вы должны заблокировать его. + Остановить запись + Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению + Правила + Отсутствие политики, предоставляемой сервером идентификации + Скрыть правила сервера идентификации + Показать правила сервера идентификации + Отображает информацию о пользователе + Изменяет ваш аватар только в этой текущей комнате + Меняет аватар текущей комнаты + Изменяет ваш отображаемый псевдоним только в текущей комнате + Устанавливает имя комнаты + Прекращение игнорирования пользователя, показывает его сообщения в дальнейшем + Игнорирует пользователя, скрывая его сообщения от вас \ No newline at end of file From 53b2f0c5e725269581b8a74c1c35f4d5b43bcaf0 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 12 Oct 2021 05:33:18 +0000 Subject: [PATCH 096/172] Translated using Weblate (Czech) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/cs-CZ/changelogs/40103020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40103020.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103010.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103010.txt new file mode 100644 index 0000000000..37f8aaa759 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Uspořádejte si místnosti pomocí Prostorů! Verze 1.3.1 opravuje pády, ke kterým může docházet ve verzi v1.3.0. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103020.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103020.txt new file mode 100644 index 0000000000..2a9dd4bb10 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Přidání podpory pro Android Auto. Spousta oprav chyb! +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.2 From a9abec8d0ac964f8cd978992af633a844e0571db Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 11 Oct 2021 18:58:54 +0000 Subject: [PATCH 097/172] Translated using Weblate (Swedish) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/sv-SE/changelogs/40103020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40103020.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103010.txt b/fastlane/metadata/android/sv-SE/changelogs/40103010.txt new file mode 100644 index 0000000000..78c2c57ae6 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Organisera dina rum med utrymmen! v1.3.1 fixar en krasch som kan hända i v1.3.0. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103020.txt b/fastlane/metadata/android/sv-SE/changelogs/40103020.txt new file mode 100644 index 0000000000..4afd54d9be --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Lägg till stöd för Android Auto. Massa buggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.2 From 9d90a897fc6d6dccdf6f28779f4876b70ec10f18 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 12 Oct 2021 10:03:58 +0000 Subject: [PATCH 098/172] Translated using Weblate (Italian) Currently translated at 99.8% (2666 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 2262774b1e..04d01e8f95 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -286,7 +286,7 @@ Vedi il codice sorgente decifrato Elimina Rinomina - Segnala questo contenuto + Segnala contenuto Chiamata in corso Avvio conferenza. \nUnisciti come %1$s o %2$s @@ -2909,10 +2909,10 @@ Tocca la registrazione per fermare o ascoltare %1$ds rimasti Tieni premuto per registrare, rilascia per inviare - Elimina messaggio vocale registrato + Elimina la registrazione Registrazione messaggio vocale Pausa messaggio vocale - Inizia messaggio vocale + Registra messaggio vocale Riproduci messaggio vocale Blocco messaggio vocale Scorri per annullare @@ -3042,4 +3042,17 @@ buttando fuori l\'utente verrà rimosso da questo spazio. \n \nPer impedire che possa rientrare, dovresti invece bandirlo. + Ferma la registrazione + Antepone ( ͡° ͜ʖ ͡°) ad un messaggio di testo + Informativa + Nessun informativa fornita dal server d\'identità + Nascondi l\'informativa del server d\'identità + Mostra l\'informativa del server d\'identità + Mostra le informazioni di un utente + Cambia il tuo avatar solo nella stanza attuale + Cambia l\'avatar della stanza attuale + Cambia il tuo nick solo nella stanza attuale + Imposta il nome della stanza + Smetti di ignorare un utente, mostrando i suoi messaggi successivi + Ignora un utente, non mostrandoti i suoi messaggi \ No newline at end of file From 961124e7f075e9d0c91417997e79a600fb9537cf Mon Sep 17 00:00:00 2001 From: Michael Mihai Date: Wed, 13 Oct 2021 10:26:55 +0000 Subject: [PATCH 099/172] Translated using Weblate (Romanian) Currently translated at 10.9% (293 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index ef390de982..8eb62c6f35 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -299,4 +299,24 @@ %1$s porniți criptarea de la un capat la altul (%2$s) Ați făcut istoria camerei viitoare vizibilă pentru %1$s %1$s a făcut istoria camerei viitoare vizibilă pentru %2$s + ** Imposibil de decriptat: %s ** + %1$s de la %2$s către %3$s + %1$s a schimbat nivelul de putere pentru %2$s. + Ați modificat nivelul de putere pentru %1$s. + Ați modificat conferința video + Conferința video a fost modificată de %1$s + Ați închis conferința video + Conferința video a fost terminată de %1$s + Ai revocat invitația de alăturare a camerei pentru %1$s + %1$s a revocat invitația către %2$s pentru a se alătura camerei + Ai trimis o invitație către %1$s pentru a se alătura camerei + %1$s a trimis o invitație către %2$s pentru a se alătura camerei + 🎉 Toate serverele sunt blocate din a participa! Această cameră nu mai poate fi folosită. + Serverele ce se potrivesc cu %s au fost eliminate din lista de permisiuni. + Serverele ce se potrivesc cu %s sunt acum permise. + Serverele ce se potrivesc cu %s sunt acum blocate. + %s Setați listele ACL pentru acestă cameră. + Ai pornit criptarea end-to-end (%1$s) + Ai deblocat %1$s + %1$s deblocat %2$s \ No newline at end of file From e0c059fcba4fb03fe9498e238a4179d47e04873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viorel-C=C4=83t=C4=83lin=20R=C4=83pi=C8=9Beanu?= Date: Wed, 13 Oct 2021 10:25:36 +0000 Subject: [PATCH 100/172] Translated using Weblate (Romanian) Currently translated at 10.9% (293 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 8eb62c6f35..77a1458797 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -319,4 +319,9 @@ Ai pornit criptarea end-to-end (%1$s) Ai deblocat %1$s %1$s deblocat %2$s + Personalizat + Personalizat (%1$d) + Implicit + Moderator + Administrator \ No newline at end of file From 256f90ae171dd412cc7f9a744e78bed4e5cb5647 Mon Sep 17 00:00:00 2001 From: tanmatsu Date: Wed, 13 Oct 2021 10:23:56 +0000 Subject: [PATCH 101/172] Translated using Weblate (Romanian) Currently translated at 10.9% (293 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 77a1458797..e9e6efadd9 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -324,4 +324,12 @@ Implicit Moderator Administrator + Ați început o conferință video + Conferință video începută de %1$s + Ați modificat widget-ul %1$s + %1$s a modifcat widget-ul %2$s + Ați înlăturat widget-ul %1$s + %1$s a înlăturat widget-ul %2$s + Ați adăugat widget-ul %1$s + %1$s a adăugat widget-ul %2$s \ No newline at end of file From af1f1e379ff8da1144cfea112d19eec4e72aa56f Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 12 Oct 2021 03:38:53 +0000 Subject: [PATCH 102/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2669 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 8de57469ee..f8ca44ab7a 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2801,13 +2801,13 @@ 點擊您的錄音以停止或收聽 剩下%1$d秒 按住以錄製,放開以傳送 - 刪除已錄製的語音訊息 + 刪除錄音 正在錄製語音訊息 暫停語音訊息 播放語音訊息 語音訊息鎖定 滑動以取消 - 開始語音訊息 + 錄製語音訊息 允許任何在 %s 中的人都可以找到並存取。您也可以選取其他空間。 必須升級 語音 @@ -2933,4 +2933,17 @@ 踢除使用者將會將他們從此空間中移除。 \n \n為了防止他們再加入,您應該封鎖他們。 + 停止錄製 + 將 ( ͡° ͜ʖ ͡°) 附加至純文字訊息 + 政策 + 身份伺服器未提供政策 + 隱藏身份伺服器政策 + 顯示身份伺服器政策 + 顯示關於使用者的資訊 + 僅在目前的聊天室中變更您的大頭照 + 變更目前聊天室的大頭照 + 僅在目前的聊天室變更您的顯示暱稱 + 設定聊天室名稱 + 停止忽略使用者,繼續顯示他們的訊息 + 忽略使用者,為您隱藏他們的訊息 \ No newline at end of file From d75e523935669081ed5d457d58868c29ccc69696 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 12 Oct 2021 10:25:54 +0000 Subject: [PATCH 103/172] Translated using Weblate (Italian) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/it-IT/changelogs/40103020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/it-IT/changelogs/40103020.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40103010.txt b/fastlane/metadata/android/it-IT/changelogs/40103010.txt new file mode 100644 index 0000000000..125cf58137 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: organizza le tue stanze usando gli Spazi! v1.3.1 corregge un errore della v1.3.0. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/it-IT/changelogs/40103020.txt b/fastlane/metadata/android/it-IT/changelogs/40103020.txt new file mode 100644 index 0000000000..c088edc3b5 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: aggiunto supporto per Android Auto. Corretti molti errori! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.2 From 58447e996646c4adce58a2cb7d5c45844e88da26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jiri=20Gr=C3=B6nroos?= Date: Wed, 13 Oct 2021 08:48:52 +0000 Subject: [PATCH 104/172] Translated using Weblate (Finnish) Currently translated at 85.1% (2272 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 427 +++++++++++++++++++++- 1 file changed, 418 insertions(+), 9 deletions(-) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 634c4027b0..0cb6e4879f 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -357,7 +357,7 @@ Muokkaa palvelinasetuksia Tarkista sähköpostisi jatkaaksesi rekisteröintiä Rekisteröityminen sähköpostiosoitteella ja puhelinnumerolla samaan aikaan ei ole mahdollista kunnes API on olemassa. Ainoastaan puhelinnumero otetaan huomioon.\n\nVoit lisätä sähköpostiosoitteen tiliisi asetuksissa. - Varmista ettet ole robotti + Tämä kotipalvelin haluaa varmistaa, ettet ole robotti Käyttäjätunnus on jo käytössä Kotipalvelin: Identiteettipalvelin: @@ -522,7 +522,7 @@ Lähetä lähettämättömät viestit Poista lähettämättömät viestit Tiedostoa ei löydy - Sinulla ei ole oikeutta lähettää viestejä tähän huoneeseen + Sinulla ei ole oikeutta lähettää viestejä tähän huoneeseen. Luota Älä luota @@ -790,7 +790,7 @@ Valitse huoneluettelo Palvelin saattaa olla tavoittamattomissa tai ylikuormitettu Syötä kotipalvelin, jolta julkiset huoneet listataan - Kotipalvelimen URL-osoite + Palvelimen nimi Kaikki huoneet palvelimella %s Kaikki alkuperäiset %s huoneet @@ -1035,7 +1035,7 @@ %1$s huone löydetty kohteelle %2$s %1$s huonetta löydetty kohteelle %2$s - Yksilöidyt ilmoitusasetukset + Edistyneet ilmoitusasetukset Ilmoituksen tärkeys tapahtumakohtaisesti Ilmoitusten yksityisyys Ratkaise ilmoituksien ongelmia @@ -1517,7 +1517,7 @@ Julkinen nimi (näkyy ihmisille, joihin olet yhteydessä) Istunnon julkinen nimi näkyy ihmisille, joihin olet yhteydessä Jatkaaksesi sinun täytyy hyväksyä palvelun käyttöehdot. - Et käytä identiteettipalvelinta + Et käytä mitään identiteettipalvelinta Identiteettipalvelinta ei ole määritetty, salasanan palautus vaaditaan. Näyttää, että yrität yhdistää toiseen kotipalvelimeen. Haluatko kirjautua ulos\? URL-osoite: @@ -1707,7 +1707,7 @@ Aloita Valitse palvelin Kuten sähköpostissa, tunnuksilla on yksi koti, mutta voit keskustella koko maailman kanssa - Liity miljoonien joukkoon suurimmalla julkisella palvelimella + Liity veloituksetta miljoonien joukkoon suurimmalla julkisella palvelimella Korkealuokkaista isännöintiä organisaatioille Lue lisää Muut @@ -1770,7 +1770,7 @@ Syötä koodi Lähetä uudelleen Seuraava - Kansainvälisten puhelinnumeroiden pitää alkaa \'+\' merkillä + Kansainvälisten puhelinnumeroiden pitää alkaa merkillä \'+\' Puhelinnumero vaikuttaa epäkelvolta. Tarkista numero Rekisteröidy palvelimelle %1$s Käyttäjätunnus tai sähköpostiosoite @@ -2099,7 +2099,7 @@ Haluatko peruuttaa tämän käyttäjän kutsun\? Tämä huone ei ole julkinen. Jos poistut, et voi liittyä takaisin ilman kutsua. Tämän asetuksen käyttöön ottaminen lisää FLAG_SECURE kaikkiin toimintoihin. Käynnistä sovellus uudestaan, jotta muutos tulee voimaan. - Estä sovelluksen kuvankaappaukset + Estä kuvakaappausten ottaminen sovelluksesta Et voi lähettää yksityisviestiä itsellesi! Hylkää muutokset Virheellinen QR-koodi (virheellinen URI)! @@ -2116,7 +2116,7 @@ Ota integraatiot asetuksista käyttöön tekeäksesi tämän. Integraatiot ovat poissa käytöstä Integraatioiden hallinta - Käytä kansainvälistä muotoa (puhelinnumeron on alettava \'+\') + Käytä kansainvälistä muotoa (puhelinnumeron on alettava merkillä \'+\') Sähköpostiosoitteet ja puhelinnumerot Hallitse Matrix-tiliisi linkitettyjä sähköpostiosoitteita ja puhelinnumeroita Aseta tilille uusi salasana… @@ -2260,4 +2260,413 @@ %1$s aloitti videopuhelun Muutit huoneen palvelimien käyttäjäoikeuslistaa. %s muutti huoneen palvelimien käyttäjäoikeuslistaa. + Linkitä tämä sähköpostiosoite tiliisi + Ääniviesti (%1$s) + Vastaaminen tai muokkaaminen ei ole mahdollista, kun ääniviesti on aktiivinen + Ääniviestiä ei voi äänittää + Tätä ääniviestiä ei voi toistaa + Käytä ääniviestiä + Napauta äänitystä pysäyttääksesi tai kuunnellaksesi + %1$d s jäljellä + Poista äänitys + Äänitetään ääniviestiä + Pysäytä äänitys + Keskeytä ääniviesti + Toista ääniviesti + Liu\'uta peruaksesi + Äänitä ääniviesti + Ryhmäpuhelu alkoi + Liittymisen yhteydessä tapahtui virhe: %s + Päivitä suositeltuun huoneversioon + Tarvitset oikeuden huoneen päivittämiseen + Kutsu käyttäjiä automaattisesti + Päivitä yksityinen huone + Päivitä julkinen huone + Päivitys vaaditaan + Päivitä + Odota hetki, tämä saattaa kestää jonkin aikaa. + Nimetön huone + Jotkin huoneet saattavat olla piilotettuja, koska ne ovat yksityisiä. Liittyäksesi tarvitset kutsun. + Jotkin huoneet saattavat olla piilotettuja, koska ne ovat yksityisiä. Liittyäksesi tarvitset kutsun. +\nSinulla ei ole oikeutta lisätä huoneita. + Tässä avaruudessa ei ole huoneita + Ole yhteydessä kotipalvelimesi ylläpitäjään saadaksesi lisätietoja + Vaikuttaa siltä, ettei kotipalvelimesi tue vielä avaruuksia + Onko olosi kokeellinen\? +\nVoit lisätä olemassa olevia avaruuksia avaruuteen. + Hallitse huoneita ja avaruuksia + Ehdotettu + Tee tästä avaruudesta julkinen + Hallitse huoneita + %s kutsuu sinut + Kokeellinen avaruus - Rajattu huone. + Sinut on kutsuttu + Avaruudet ovat uusi tapa ryhmitellä huoneita ja ihmisiä. + Tervetuloa avaruuksien pariin! + Lisää huoneita + Lisää avaruus mihin tahansa hallitsemaasi avaruuteen. + Lisää olemassa olevia avaruuksia + Lisää olemassa olevia huoneita + Et voi liittyä uudelleen, ellei sinua kutsuta uudelleen. + Olet ainoa henkilö täällä. Jos poistut, kukaan ei voi liittyä tänne tulevaisuudessa, et edes sinä. + Haluatko varmasti poistua avaruudesta %s\? + Poistu avaruudesta + Lisää huoneita + Selaa huoneita + + %d tuntemasi henkilö on jo liittynyt + %d tuntemaasi henkilöä on jo liittynyt + + Tervetuloa avaruuteen %1$s, %2$s. + Liity silti + Liity avaruuteen + Luo avaruus + Ohita toistaiseksi + Liity avaruuteeni %1$s %2$s + He voivat selata avaruutta %s + Kutsu avaruuteen %s + Jaa linkki + Kutsu käyttäjänimellä tai sähköpostilla + Kutsu käyttäjänimellä + Kutsu sähköpostitse + Kutsu avaruuteen %s + Kutsu ihmisiä + Kutsu ihmisiä avaruuteesi + Kuvaus + Luodaan avaruutta… + Satunnainen + Yleinen + Minkä asioiden parissa työskentelette\? + Anna sille nimi jatkaaksesi. + Lisää hieman tietoja, jotta ihmiset tunnistavat sen. Voit muuttaa näitä tietoja milloin tahansa. + Lisää hieman tietoja, jotta se erottuu muista. Voit muuttaa näitä tietoja milloin tahansa. + Luo avaruus + Yksityinen + Avoin kaikille, paras vaihtoehto yhteisöille + Julkinen + Yksityinen avaruus huoneiden järjestämiseen + Vain minä + Kenen kanssa työskentelet\? + Tarvitset kutsun liittyäksesi olemassa olevaan avaruuteen. + Voit muuttaa tämän myöhemmin + Minkä tyyppisen avaruuden haluat luoda\? + Avaruudet ovat uusi tapa ryhmitellä huoneita ja ihmisiä + Yksityinen avaruutesi + Julkinen avaruutesi + Lisää avaruus + Yksityinen avaruus + Julkinen avaruus + Haluatko poistaa kaikki lähettämättömät viestit tästä huoneesta\? + Poista lähettämättömät viestit + Haluatko perua viestin lähettämisen\? + Poista kaikki epäonnistuneet viestit + Epäonnistui + Lähetetty + Lähetetään + Päivittää huoneen uuteen versioon + Luo avaruus + Viestin tyyppi puuttuu + Ei sisältöä + Muokkaa sisältöä + Kehittäjätyökalut + Julkinen avaruus + Julkinen huone + Näytä lukukuittaukset + Älä ilmoita + Ilmoita ilman ääntä + Ilmoita äänen kera + Viestiä ei lähetetty virheen vuoksi + Sulje emojivalitsin + Avaa emojivalitsin + Valittu + Video + Joitain viestejä ei ole lähetetty + Kuva + Tuo avain tiedostosta + Kuvakaappaus + Tunnistautuminen epäonnistui + ${app_name} vaatii tämän toimenpiteen suorittamiseksi, että annat kirjautumistietosi. + Tunnistautuminen uudelleen vaaditaan + Liu\'uta lopettaaksesi puhelun + Tuntematon henkilö + Käyttäjät + %1$s Napauta palataksesi + Aktiivinen puhelu (%1$s) · + + Aktiivinen puhelu · + %1$d aktiivista puhelua · + + + 1 aktiivinen puhelu (%1$s) · 1 keskeytetty puhelu + 1 aktiivinen puhelu (%1$s) · %2$d keskeytettyä puhelua + + + Keskeytetty puhelu + %1$d keskeytettyä puhelua + + Aktiivinen puhelu (%1$s) + Numeronäppäimistö + Yhteys epäonnistui + Ei vastausta + Vastaamaton videopuhelu + Vastaamaton äänipuhelu + Kieltäydytty videopuhelusta + Kieltäydytty äänipuhelusta + Videopuhelu päättyi • %1$s + Äänipuhelu päättyi • %1$s + Aktiivinen videopuhelu + Aktiivinen äänipuhelu + Saapuva videopuhelu + Saapuva äänipuhelu + Soita takaisin + Puhelu on päättynyt + %1$s kieltäytyi tästä puhelusta + Kieltäydyit tästä puhelusta + Kieltäydyit puhelusta %s + Olet parhaillaan tässä puhelussa + %1$s aloitti puhelun + Aloitit puhelun + Tallentamattomia muutoksia. Hylätäänkö muutokset\? + Huonetta ei ole vielä luotu. Perutaanko huoneen luominen\? + Linkki oli muodostettu väärin + Tätä huonetta ei löydy. Varmista että se on olemassa. + Vahvista PIN-koodi poistaaksesi PIN-koodin käytöstä + Vaihda nykyinen PIN-koodisi + Vaihda PIN-koodi + PIN-koodi vaaditaan joka kerta, kun avaat sovelluksen ${app_name}. + PIN-koodi vaaditaan, kun sovellusta ${app_name} ei ole käytetty 2 minuuttiin. + Vaadi PIN-koodi 2 minuutin jälkeen + Näytä vain lukemattomien viestien määrä yksinkertaisessa ilmoituksessa. + Näytä tiedot kuten huoneiden nimet ja viestien sisältö. + Näytä sisältö ilmoituksissa + PIN-koodi on ainoa tapa avata sovelluksen ${app_name} lukitus. + Ota biometriikka käyttöön + Ota PIN-koodi käyttöön + Suojaa pääsy käyttäen PIN-koodia ja biometriikkaa. + Suojaa pääsy + Määritä suojaus + Uusi PIN-koodi + Nollaa PIN-koodi + Unohditko PIN-koodisi\? + Anna PIN-koodi + Vahvista PIN-koodi + Valitse PIN-koodi turvallisuuden parantamiseksi + Liian monta virhettä, sinut on kirjattu ulos + Varoitus! Viimeinen yrityskerta ennen uloskirjaamista! + + %d tietue + %d tietuetta + + + Väärä koodi, %d yritys jäljellä + Väärä koodi, %d yritystä jäljellä + + Katselmoi asetukset ottaaksesi push-ilmoitukset käyttöön + Push-ilmoitukset ovat pois käytöstä + Etsi yhteystietojasi Matrixista + Puhelimen osoitekirja on tyhjä + Lisää puhelimen osoitekirjasta + LUE LISÄÄ + SELVÄ + Olemme iloisia uuden nimen lanseerauksesta! Sovelluksesi on ajan tasalla ja olet kirjautunut tilillesi. + Riot on nyt Element! + Salausta ei voi purkaa + Muutit huoneen asetuksia onnistuneesti + Käynnistä kamera + Pysäytä kamera + Poista mikrofonin mykistys + Mykistä mikrofoni + Lähetä + Anna identiteettipalvelimen URL-osoite + Vaihtoehtoisesti voit kirjoittaa minkä tahansa muun identiteettipalvelimen URL-osoitteen + Käytä %1$s + Hyväksy ensin identiteettipalvelimen ehdot asetusten kautta. + Määritä ensin identiteettipalvelin. + Tämä toiminto ei ole mahdollinen. Kotipalvelin on vanhentunut. + Tämä identiteettipalvelin on vanhentunut. ${app_name} tukee vain API V2:ta. + Katkaistaanko yhteys identiteettipalvelimeen %s\? + Ladataan käytettävissä olevia kieliä… + Oma koodi + Käyttäjiä ei voitu kutsua. Tarkista käyttäjät, jotka haluat kutsua, ja yritä uudelleen. + Linkki %1$s vie sinut toiselle sivustolle: %2$s. +\n +\nHaluatko varmasti jatkaa\? + Valitse salasana. + Valitse käyttäjänimi. + Merkitse luotetuksi + Vahvista vuorovaikutteisesti emojilla + Vahvista kirjautuminen + Vahvista kaikki istuntosi varmistaaksesi, että tilisi ja viestisi ovat turvassa + Katselmoi missä olet sisäänkirjautuneena + Salattu vahvistamattomalla laitteella + Salaamaton + Käytä palautusavainta + Tuettu vain salatuissa huoneissa + Mediatiedostoa ei voitu tallentaa + %1$s (%2$s) + Anna palautusavain + Ei kelvollinen palautusavain + Käytä tiedostoa + Salauksen päivitys saatavilla + Tämä tili on deaktivoitu. + Virheellinen käyttäjänimi ja/tai salasana. Annettu salasana alkaa tai päättyy välilyönnillä, tarkista salasana. + Avainten tuonti epäonnistui + Lisää aihe + Tämä on keskustelun alku. + Loit ja määritit huoneen. + %s loi ja määritti huoneen. + Tämän huoneen käyttämä salaus ei ole tuettu + Salaus ei käytössä + Viestit tässä huoneessa ovat päästä päähän -salattuja. + Viestit tässä huoneessa ovat päästä päähän -salattuja. Lue lisää ja vahvista käyttäjät heidän profiilissaan. + Salaus käytössä + Lähetä media alkuperäisessä koossa + + Lähetä video alkuperäisessä koossa + Lähetä videot alkuperäisessä koossa + + Lentokonetila on päällä + epävakaa + vakaa + Oletusversio + Huoneversiot 👓 + Raja ei ole tiedossa. + Kotipalvelimesi hyväksyy liitteitä (tiedostoja, mediaa jne.) joiden koko on enintään %s. + Palvelimen asettama tiedoston lähetysraja + Palvelimen versio + Palvelimen nimi + Sinulla ei ole oikeutta ottaa salausta käyttöön tässä huoneessa. + Viestit täällä ovat päästä päähän -salattuja. +\n +\nViestisi turvataan lukolla ja vain sinä sekä vastaanottaja omaatte avaimet, joilla salauksen voi purkaa. + Viestit täällä eivät ole päästä päähän -salattuja. + Skannaa tällä laitteella + Ääni + Luodaan avaruutta… + Jotkin merkit eivät ole sallittuja + Anna huoneen osoite + Tämä osoite on jo käytössä + Avaruuden osoite + Lisää ( ͡° ͜ʖ ͡°) tavalliseen viestiin + Jos et muista salasanaasi, palaa nollaamaan se. + Ei vaikuta kelvolliselta sähköpostiosoitteelta + Anna sen palvelimen osoite, jota haluat käyttää + Tyhjennä historia + %1$s asetti huoneen pääsyvaatimukseksi kutsun. + Asetit huoneen pääsyvaatimukseksi kutsun. + Teit huoneen julkiseksi kaikille, jotka tuntevat linkin. + Et tehnyt muutoksia + Huoneen asetukset + Tässä huoneessa ei ole tiedostoja + Tässä huoneessa ei ole mediaa + %1$d/%2$d + Kierrä ja rajaa + Tiedosto on liian suuri lähetettäväksi. + Tämä ominaisuus on beetavaiheessa + Vahvistuskoodi ei ole oikein. + Anna hyväksyntä + Kumoa antamani hyväksyntä + Pakataan videota %d%% + Pakataan kuvaa… + Anna palautetta + Palautteen lähetys epäonnistui (%s) + Kiitos, palautteesi on lähetetty onnistuneesti + Voitte olla yhteydessä minuun, jos teillä on lisäkysymyksiä + Palaute + Palaute avaruuksista + Tätä huonetta ei voi esikatsella. Haluatko liittyä siihen\? + Luo uusi avaruus + Palautusavain on tallennettu. + Näyttää tietoja käyttäjästä + Vaihtaa näyttönimesi vain nykyisessä huoneessa + Asettaa huoneen nimen + Lisää uusi palvelin + + %1$d/%2$d avain tuotu onnistuneesti. + %1$d/%2$d avainta tuotu onnistuneesti. + + Avaimet viety onnistuneesti + Huoneen versio + Päätä kuka voi löytää huoneen ja liittyä siihen. + Napauta muokataksesi avaruuksia + Valitse avaruudet + Avaruuden %s jäsenet voivat löytää, esikatsella ja liittyä. + Vain avaruuden jäsenet + Kuka tahansa voi löytää ja liittyä avaruuteen + Kuka tahansa voi löytää ja liittyä huoneeseen + Julkinen + Vain kutsutut henkilöt voivat löytää ja liittyä + Yksityinen (vain kutsulla) + Yksityinen + Julkaise tämä osoite + Lisää paikallinen osoite + Poistetaanko osoite \"%1$s\"\? + Julkaise + Muut julkaistut osoitteet: + Salli vieraiden liittyä + Avaruuden pääsy + Huoneen pääsy + Kenellä on pääsy\? + Tilin asetukset + Lisää painike viestin lähetysikkunaan emoji-näppäimistön avaamiseksi + Huonekutsut + Avainsanat + Salatut ryhmäviestit + Ryhmäviestit + Salatut suoraviestit + Suoraviestit + Käyttäjänimeni + Näyttönimeni + Ilmoitusta on napsautettu! + Avainsanat eivät voi sisältää \'%s\' + Avainsanat eivät voi alkaa merkillä \'.\' + Lisää uusi avainsana + Avainsanoistani + Ilmoita minulle + Muu + Maininnat ja avainsanat + Oletusilmoitukset + Lähetä sähköposti-ilmoituksia osoitteeseen %s + Jotta voit vastaanottaa sähköposti-ilmoituksia, sido sähköpostiosoite Matrix-tiliisi + Sähköposti-ilmoitus + Poistetaanko %s\? + Ei mitään + Vain maininnat ja avainsanat + Hakeminen salatuista huoneista ei ole vielä tuettu. + Vaihda avaruuden nimeä + Ota avaruuden salaus käyttöön + Avaruuden käyttöoikeudet + Jatka silti + Lopetetaan puhelu… + Ei vastausta + Käyttäjä, jolle soitit, on varattu. + Käyttäjä on varattu + Laitoit puhelun pitoon + %s laittoi puhelun pitoon + Pitoon + Palaa + Äänipuhelu henkilön %s kanssa + Videopuhelu henkilön %s kanssa + + Vastaamatta jäänyt videopuhelu + %d vastaamatta jäänyttä videopuhelua + + + Vastaamatta jäänyt puhelu + %d vastaamatta jäänyttä puhelua + + Puhelu soi… + SSL-virhe: vertaisen identiteettiä ei ole vahvistettu. + Tämä puhelinnumero on jo käytössä. + Käytä oletuksena, älä kysy uudelleen + Kysy aina + Kotipalvelimen rajapinnan URL-osoite + Avaruudet + Kutsut + Huonehakemisto + Ehdotetut huoneet + Uusi arvo + Puuttuvat oikeudet + Avaruudet + Lue lisää \ No newline at end of file From 17ddcdd250216f0069f31f3d6854f5235fc062bd Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 12 Oct 2021 05:31:04 +0000 Subject: [PATCH 105/172] Translated using Weblate (Czech) Currently translated at 100.0% (2669 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index e13a903c5e..40ded266ab 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -2919,13 +2919,13 @@ Povolit hlasovou zprávu %1$ds zbývá Podržením nahrajte, uvolněním odešlete - Odstranit nahranou hlasovou zprávu + Odstranit záznam Nahrát hlasovou zprávu Pozastavit hlasovou zprávu Přehrát hlasovou zprávu Zámek hlasové zprávy Posunutím zrušíte - Zahájení hlasové zprávy + Nahrát hlasovou zprávu Hlasová zpráva Spaces, které mají přístup Umožněte členům prostoru ho najít a zpřístupnit. @@ -3045,4 +3045,17 @@ vykopnutí uživatele je z tohoto prostoru odstraní. \n \nAbyste jim zabránili v dalším vstupu, měli byste je raději vykázat. + Předsune ( ͡° ͜ʖ ͡°) do textové zprávy + Zastavit nahrávání + Zásady + Server identit neposkytuje žádné zásady + Skrýt zásady serveru identit + Zobrazit zásady serveru identit + Zobrazí informace o uživateli + Změní vašeho avatara pouze v této místnosti + Změní avatar aktuální místnosti + Změní vaši zobrazovanou přezdívku pouze v aktuální místnosti + Nastaví název místnosti + Přestane ignorovat uživatele a zobrazí jeho zprávy + Ignoruje uživatele a skrývá jeho zprávy \ No newline at end of file From b06e4002919e95190f1d0eab8aa93bec68730b1a Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 12 Oct 2021 23:14:23 +0000 Subject: [PATCH 106/172] Translated using Weblate (Ukrainian) Currently translated at 91.3% (2438 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 7d4ef86094..06906804df 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -185,7 +185,7 @@ Налаштування Інформація про учасника Історія - Сповістити про помилку + Звіт про ваду Гаразд Скасувати @@ -268,10 +268,10 @@ Надіслати журнали Надіслати журнали помилок Надіслати знімок екрана - Повідомити про помилку - Буль ласка, опишіть помилку. Що ви робили? Якої поведінки чекали? Що сталося натомість? + Повідомити про ваду + Опишіть ваду. Що ви робили\? Виконання якої дії очікували\? Що сталося натомість\? Опишіть свою проблему - Для діагностики проблеми, журнали з цього клієнту будуть приєднані до звіту про помилку. Цей звіт, включаючи журнали та скріншот, не буде доступний публічно. Якщо ви не хочете надсилати журнали, зніміть позначку: + Для визначення проблеми, журнали з цього клієнта буде долучено до звіту про ваду. Цей звіт, включно з журналами та знімком екрана, не буде доступний публічно. Якщо ви не хочете надсилати журнали, приберіть позначку: Здається, ви трясете телефон від фрустрації. Бажаєте надіслати звіт про помилку? При останньому запуску застосунок закрився з помилкою. Бажаєте надіслати звіт про помилку? Звіт про помилку вдало надіслано @@ -814,7 +814,7 @@ Запросити Спільноти Нема груп - Струснути пристрій, щоб повідомити про помилку + Струснути пристрій, щоб повідомити про ваду Ви впевнені, що бажаєте розпочати новий чат з %s\? Ви впевнені, що бажаєте розпочати голосовий виклик\? Ви впевнені, що бажаєте розпочати відео виклик\? @@ -1287,7 +1287,7 @@ Власні та розширені налаштування (Розширені) Довідка та опис - Налаштування + Пристосування Виберіть колір світлодіода, вібрацію, звук… Налаштування тихих сповіщень Налаштування сповіщень викликів @@ -1855,9 +1855,9 @@ \nКлючі не є довіреними Перехресне підписування увімкнено \nКлючі є довіреними. -\nЗакриті ключі невідомі +\nПриватні ключі невідомі Перехресне підписування увімкнено -\nЗакриті ключі на пристрої. +\nПриватні ключі на пристрої. Перехресне підписування %s приєднується. Шифрування увімкнено From ebf8c51207f21af73914070878a91a33b549144f Mon Sep 17 00:00:00 2001 From: Leonidas Shear Date: Tue, 12 Oct 2021 21:03:46 +0000 Subject: [PATCH 107/172] Translated using Weblate (Russian) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru-RU/changelogs/40103000.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40103020.txt | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40103000.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40103020.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/40103000.txt b/fastlane/metadata/android/ru-RU/changelogs/40103000.txt new file mode 100644 index 0000000000..7e87ca8524 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40103000.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Организуйте свои комнаты с помощью Пространств! +Весь список изменений: https://github.com/vector-im/element-android/releases/tag/v1.3.0 diff --git a/fastlane/metadata/android/ru-RU/changelogs/40103010.txt b/fastlane/metadata/android/ru-RU/changelogs/40103010.txt new file mode 100644 index 0000000000..e3a14e2d93 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Организуйте свои комнаты с помощью Пространств! В версии 1.3.1 исправлен сбой, который мог произойти в версии 1.3.0. +Весь список изменений: https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/ru-RU/changelogs/40103020.txt b/fastlane/metadata/android/ru-RU/changelogs/40103020.txt new file mode 100644 index 0000000000..66059d5b92 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Добавлена поддержка Android Auto. Исправлено множество ошибок! +Весь список изменений: https://github.com/vector-im/element-android/releases/tag/v1.3.2 From 4d9e348aba735dc5463a0cc3dc7da8a2a1768ef0 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 12 Oct 2021 03:30:38 +0000 Subject: [PATCH 108/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/zh-TW/changelogs/40103020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40103020.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103010.txt b/fastlane/metadata/android/zh-TW/changelogs/40103010.txt new file mode 100644 index 0000000000..95dcd59e46 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40103010.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:使用空間來整理您的聊天室!v1.3.1 修復了在 v1.3.0 中遇到的當機問題。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103020.txt b/fastlane/metadata/android/zh-TW/changelogs/40103020.txt new file mode 100644 index 0000000000..6a00bed1e7 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40103020.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:新增對 Android Auto 的支援。以及許多錯誤修復! +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.2 From 49262a19fe8901bb51e77f934d72bb0f04025655 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 13 Oct 2021 18:44:58 +0000 Subject: [PATCH 109/172] Translated using Weblate (Swedish) Currently translated at 99.1% (2645 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 35 ++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 3fc05c4d40..90852f9c3e 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2566,7 +2566,7 @@ Skicka meddelanden Förvald roll Du har inte behörighet att uppdatera rollerna som krävs för att ändra olika delar av rummet - Välj roller som krävs för att ändra olika delar av rummet + Välj de roller som krävs för att ändra olika delar av rummet Behörigheter Se och uppdatera rollerna som krävs för att ändra olika delar av rummet. Rumsbehörigheter @@ -2950,4 +2950,37 @@ att kicka användaren kommer att ta bort den från det här utrymmet. \n \nFör att hindra hen från att gå med igen så bör de banna hen istället. + Skapar utrymme… + Lägger till ( ͡° ͜ʖ ͡°) till början av ett textmeddelande + Visa lite användbar info för att hjälpa till att avbugga appen + Visa avbuggningsinfo på skärmen + Ser inte ut som en giltig e-postadress + Policy + Ingen policy försedd av identitetsservern + Dölj identitetsserverns policy + Visa identitetsserverns policy + Öppna upptäckbarhetsinställningar + Sök med namn, ID eller e-postadress + Skapa nytt utrymme + Visar information om en användare + Byter din avatar endast i det nuvarande rummet + Byter avataren för det nuvarande rummet + Byter ditt visningsnamn endast i det nuvarande rummet + Sätter rummets namn + Slutar ignorera en användare, vilket visar dess meddelanden i framtiden + Ignorerar en användare, vilket döljer dess meddelanden för dig + Vem som helst kan hitta utrymmet och gå med + Utrymmesåtkomst + Vem kan komma åt\? + Aktivera e-postaviseringar för %s + För att få e-post med aviseringar, vänligen associera en e-postadress med ditt Matrixkonto + E-postavisering + Uppgradera utrymmet + Byt utrymmets namn + Aktivera utrymmeskryptering + Byt huvudadress för det här utrymmet + Byt utrymmets avatar + Du har inte behörighet att uppdatera rollerna som krävs för att ändra diverse delar av det här utrmmet + Välj de roller som krävs för att ändra diverse delar av det här utrymmet + Se och uppdatera rollerna som krävs för att ändra diverse delar av utrymmet. \ No newline at end of file From 0a9c6673af88df59ae5dbb185f32201bcabf77f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Oct 2021 23:05:53 +0000 Subject: [PATCH 110/172] Bump media from 1.4.2 to 1.4.3 Bumps media from 1.4.2 to 1.4.3. --- updated-dependencies: - dependency-name: androidx.media:media dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index ecbffd0b8a..d06779d61c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -350,7 +350,7 @@ dependencies { implementation libs.androidx.constraintLayout implementation "androidx.sharetarget:sharetarget:1.1.0" implementation libs.androidx.core - implementation "androidx.media:media:1.4.2" + implementation "androidx.media:media:1.4.3" implementation "androidx.transition:transition:1.4.1" implementation "org.threeten:threetenbp:1.4.0:no-tzdb" From 2f0affa27e74144ea1ed9498730a22ae5a005352 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 11:56:48 +0100 Subject: [PATCH 111/172] using correct issue number in change log entry --- changelog.d/{3872.bugfix => 3774.bugfix} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{3872.bugfix => 3774.bugfix} (100%) diff --git a/changelog.d/3872.bugfix b/changelog.d/3774.bugfix similarity index 100% rename from changelog.d/3872.bugfix rename to changelog.d/3774.bugfix From 84b44f6093a69b84b537d583d3191f2f1994cc8a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 12:24:06 +0100 Subject: [PATCH 112/172] using generic list for the circular cache instead of a fixed string array --- .../{CircularStringCache.kt => CircularCache.kt} | 8 ++++---- .../features/notifications/NotificationDrawerManager.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename vector/src/main/java/im/vector/app/features/notifications/{CircularStringCache.kt => CircularCache.kt} (82%) diff --git a/vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt similarity index 82% rename from vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt rename to vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt index 97b40daf46..9a979a2ec8 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/CircularStringCache.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt @@ -19,14 +19,14 @@ package im.vector.app.features.notifications /** * A FIFO circular buffer of strings */ -class CircularStringCache(cacheSize: Int) { +class CircularCache(cacheSize: Int) { - private val cache = Array(cacheSize) { "" } + private val cache = ArrayList(initialCapacity = cacheSize) private var writeIndex = 0 - fun contains(key: String): Boolean = cache.contains(key) + fun contains(key: T): Boolean = cache.contains(key) - fun put(key: String) { + fun put(key: T) { if (writeIndex == cache.size - 1) { writeIndex = 0 } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 2df093b615..ff44ac4035 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -89,7 +89,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context * Acts as a notification debouncer to stop already dismissed push notifications from * displaying again when the /sync response is delayed. */ - private val seenEventIds = CircularStringCache(cacheSize = 25) + private val seenEventIds = CircularCache(cacheSize = 25) /** Should be called as soon as a new event is ready to be displayed. From 00beb27b56226b2f7ea653e3617869d70ffdc3d2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 12:25:13 +0100 Subject: [PATCH 113/172] updating class doc to mention its not thread safe --- .../java/im/vector/app/features/notifications/CircularCache.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt index 9a979a2ec8..de91766fc3 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt @@ -17,7 +17,8 @@ package im.vector.app.features.notifications /** - * A FIFO circular buffer of strings + * A FIFO circular buffer of T + * This class is not thread safe */ class CircularCache(cacheSize: Int) { From eb70a81afd4a5f06503fb1692a939b58352b0fc2 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 13:14:36 +0100 Subject: [PATCH 114/172] moving builder call to avoid misaligning the comment --- .../im/vector/app/features/notifications/NotificationUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 774f3db2cf..92feb3d038 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -801,8 +801,8 @@ class NotificationUtils @Inject constructor(private val context: Context, val smallIcon = R.drawable.ic_status_bar return NotificationCompat.Builder(context, if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID) - // used in compat < N, after summary is built based on child notifications .setOnlyAlertOnce(true) + // used in compat < N, after summary is built based on child notifications .setWhen(lastMessageTimestamp) .setStyle(style) .setContentTitle(stringProvider.getString(R.string.app_name)) From 0f0762954721713cb7115f04c0f147609318e248 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 12:25:54 +0100 Subject: [PATCH 115/172] moving comment position to be above the if and cleaning up log copy --- .../app/features/notifications/NotificationDrawerManager.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index ff44ac4035..1598fa060c 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -147,11 +147,11 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // Ignore an edit of a not displayed event in the notification drawer } } else { + // Not an edit if (seenEventIds.contains(notifiableEvent.eventId)) { - // we've already seen the event, lets' skip - Timber.d("onNotifiableEventReceived(): skipping event, already seen}") + // we've already seen the event, lets skip + Timber.d("onNotifiableEventReceived(): skipping event, already seen") } else { - // Not an edit seenEventIds.put(notifiableEvent.eventId) eventList.add(notifiableEvent) } From fc793c442b54d6dfa8d8c9c7c0e2cc7a4a6f6326 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 14 Oct 2021 13:12:16 +0100 Subject: [PATCH 116/172] reverting back to using an array for the circular cache, makes preloading and setting the value simpler - adds unit tests to show it working --- .../features/notifications/CircularCache.kt | 10 ++- .../NotificationDrawerManager.kt | 2 +- .../notifications/CircularCacheTest.kt | 72 +++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/notifications/CircularCacheTest.kt diff --git a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt index de91766fc3..1ac66d0ae8 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/CircularCache.kt @@ -20,15 +20,19 @@ package im.vector.app.features.notifications * A FIFO circular buffer of T * This class is not thread safe */ -class CircularCache(cacheSize: Int) { +class CircularCache(cacheSize: Int, factory: (Int) -> Array) { - private val cache = ArrayList(initialCapacity = cacheSize) + companion object { + inline fun create(cacheSize: Int) = CircularCache(cacheSize) { Array(cacheSize) { null } } + } + + private val cache = factory(cacheSize) private var writeIndex = 0 fun contains(key: T): Boolean = cache.contains(key) fun put(key: T) { - if (writeIndex == cache.size - 1) { + if (writeIndex == cache.size) { writeIndex = 0 } cache[writeIndex] = key diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 1598fa060c..5c20ce0832 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -89,7 +89,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context * Acts as a notification debouncer to stop already dismissed push notifications from * displaying again when the /sync response is delayed. */ - private val seenEventIds = CircularCache(cacheSize = 25) + private val seenEventIds = CircularCache.create(cacheSize = 25) /** Should be called as soon as a new event is ready to be displayed. diff --git a/vector/src/test/java/im/vector/app/features/notifications/CircularCacheTest.kt b/vector/src/test/java/im/vector/app/features/notifications/CircularCacheTest.kt new file mode 100644 index 0000000000..6428b1025b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/notifications/CircularCacheTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.notifications + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class CircularCacheTest { + + @Test + fun `when putting more than cache size then cache is limited to cache size`() { + val (cache, internalData) = createIntCache(cacheSize = 3) + + cache.putInOrder(1, 1, 1, 1, 1, 1) + + internalData shouldBeEqualTo arrayOf(1, 1, 1) + } + + @Test + fun `when putting more than cache then acts as FIFO`() { + val (cache, internalData) = createIntCache(cacheSize = 3) + + cache.putInOrder(1, 2, 3, 4) + + internalData shouldBeEqualTo arrayOf(4, 2, 3) + } + + @Test + fun `given empty cache when checking if contains key then is false`() { + val (cache, _) = createIntCache(cacheSize = 3) + + val result = cache.contains(1) + + result shouldBeEqualTo false + } + + @Test + fun `given cached key when checking if contains key then is true`() { + val (cache, _) = createIntCache(cacheSize = 3) + + cache.put(1) + val result = cache.contains(1) + + result shouldBeEqualTo true + } + + private fun createIntCache(cacheSize: Int): Pair, Array> { + var internalData: Array? = null + val factory: (Int) -> Array = { + Array(it) { null }.also { array -> internalData = array } + } + return CircularCache(cacheSize, factory) to internalData!! + } + + private fun CircularCache.putInOrder(vararg keys: Int) { + keys.forEach { put(it) } + } +} From 32658f66519c2531774a18bec1f9ae341f6a61eb Mon Sep 17 00:00:00 2001 From: thomcatdotrocks <37344783+thomcatdotrocks@users.noreply.github.com> Date: Thu, 14 Oct 2021 10:45:59 -0500 Subject: [PATCH 117/172] Re-enable Android Auto Commit to complete #4247 after #4222 has been merged. --- vector/src/main/AndroidManifest.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index d79d1bb969..376e0e869a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -83,10 +83,9 @@ android:name="android.max_aspect" android:value="9.9" /> - - + android:resource="@xml/automotive_app_desc" /> - \ No newline at end of file + From 1b0a127af3f674d49fe2dc46d0bc554a5c39ba97 Mon Sep 17 00:00:00 2001 From: thomcatdotrocks <37344783+thomcatdotrocks@users.noreply.github.com> Date: Thu, 14 Oct 2021 10:53:32 -0500 Subject: [PATCH 118/172] Add changelog file --- changelog.d/4247.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4247.bugfix diff --git a/changelog.d/4247.bugfix b/changelog.d/4247.bugfix new file mode 100644 index 0000000000..da8ca300b4 --- /dev/null +++ b/changelog.d/4247.bugfix @@ -0,0 +1 @@ +Restore support for Android Auto as sent messages are no longer read aloud From 59a3b84c1d146427eaba42bfd1830f1d8c78db82 Mon Sep 17 00:00:00 2001 From: Ekaterina Gerasimova Date: Thu, 14 Oct 2021 13:44:19 +0100 Subject: [PATCH 119/172] Add issue triage automation Fixes #4250 Move new issues into incoming column and move X-Needs-Info into Need info column on the vector-im/element-android/projects/4 board Signed-off-by: Ekaterina Gerasimova --- .github/workflows/triage-incoming.yml | 15 +++++++++++++++ .github/workflows/triage-needs-info.yml | 16 ++++++++++++++++ changelog.d/4250.misc | 1 + 3 files changed, 32 insertions(+) create mode 100644 .github/workflows/triage-incoming.yml create mode 100644 .github/workflows/triage-needs-info.yml create mode 100644 changelog.d/4250.misc diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml new file mode 100644 index 0000000000..40d5507415 --- /dev/null +++ b/.github/workflows/triage-incoming.yml @@ -0,0 +1,15 @@ +name: Move new issues onto Issue triage board + +on: + issues: + types: [opened] + +jobs: + automate-project-columns: + runs-on: ubuntu-latest + steps: + - uses: alex-page/github-project-automation-plus@v0.8.1 + with: + project: Issue triage + column: Incoming + repo-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.github/workflows/triage-needs-info.yml b/.github/workflows/triage-needs-info.yml new file mode 100644 index 0000000000..64de7951c6 --- /dev/null +++ b/.github/workflows/triage-needs-info.yml @@ -0,0 +1,16 @@ +name: Move X-Needs-Info into Need info column in the Issue triage board + +on: + issues: + types: [labeled] + +jobs: + Move_Labeled_Issue_On_Project_Board: + runs-on: ubuntu-latest + steps: + - uses: konradpabjan/move-labeled-or-milestoned-issue@v2.0 + with: + action-token: ${{ secrets.GITHUB_TOKEN }} + project-url: "https://github.com/vector-im/element-android/projects/4" + column-name: "Need info" + label-name: "X-Needs-Info" diff --git a/changelog.d/4250.misc b/changelog.d/4250.misc new file mode 100644 index 0000000000..c4d2fcbd6d --- /dev/null +++ b/changelog.d/4250.misc @@ -0,0 +1 @@ +Add automation to move incoming issues and X-Needs-Info into the right places on the issue triage board. From 91d2ef456e544fab36eff23f187046adab1e1d9d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 15 Oct 2021 10:27:22 +0200 Subject: [PATCH 120/172] Update location of sign off documentation --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2048b823f0..eb30c18fcf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,6 +11,6 @@ - [ ] Pull request is based on the develop branch - [ ] Pull request includes a new file under ./changelog.d. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog - [ ] Pull request includes screenshots or videos if containing UI changes -- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off) +- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off) - [ ] You've made a self review of your PR -- [ ] If you have modified the screen flow, or added new screens to the application, you have updated the test [UiAllScreensSanityTest.allScreensTest()](https://github.com/vector-im/element-android/blob/main/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt#L73) \ No newline at end of file +- [ ] If you have modified the screen flow, or added new screens to the application, you have updated the test [UiAllScreensSanityTest.allScreensTest()](https://github.com/vector-im/element-android/blob/main/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt#L73) From d1b9710fa580873e78a0a51ad740fa5edb59ab9f Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 15 Oct 2021 11:52:41 +0000 Subject: [PATCH 121/172] Translated using Weblate (French) Currently translated at 100.0% (2669 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 66 +++++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 710145c63b..483b433ecc 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2776,7 +2776,7 @@ Envoyer la vidéo en taille originale Envoyer les vidéos en taille originale - Vous êtes administrateur de cet espace, assurez-vous d’avoir transféré les droits d’administration à un autre membre avant de partir. + Vous êtes le seul administrateur de cet espace. En le quittant, plus personne n’aura le contrôle dessus. Vous utilisez une version bêta des espaces. Vos remarques aideront à concevoir les prochaines versions. Votre plateforme et votre nom d’utilisateur seront marqués pour nous aider à utiliser vos remarques autant que possible. Il se peut que certains salons soient masqués parce qu’ils sont privés et vous avez besoin d’une invitation. Il se peut que certains salons soient masqués parce qu’ils sont privés et vous avez besoin d’une invitation. @@ -2787,7 +2787,7 @@ Envie d’expérimenter \? \nVous pouvez ajouter des espaces existants à un espace. Ajouter des salons - Cet espace n’est pas public. Vous ne pourrez pas le rejoindre sans invitation. + Il vous sera impossible de revenir à moins d’y être réinvité. Vous êtes la seule personne ici. Si vous partez, personne ne pourra entrer à l’avenir, même pas vous. Inviter à %s Cette fonctionnalité est en bêta @@ -2863,13 +2863,13 @@ Touchez l’enregistrement pour l’arrêter ou l’écouter %1$ds restant Maintenir pour enregistrer, relâcher pour envoyer - Supprimer l’enregistrement du message vocal + Supprimer l’enregistrement Enregistrement du message vocal Mettre en pause le message vocal Lire le message vocal Verrou de message vocal Glisser pour annuler - Démarrer un message vocal + Enregistrer un message vocal Autoriser quiconque dans %s à trouver et venir. Vous pouvez également sélectionner d’autres espaces. Mise-à-jour nécessaire Vocal @@ -2951,4 +2951,62 @@ Appel en cours… Espaces En savoir plus + Arrêter l’enregistrement + Ajouter un espace à un espace que vous gérez. + Ajouter des espaces existants + Ajouter des salons existants + Choisissez les endroits à quitter + Quitter certains salons et espaces… + Ne quitter aucun salon ni espace + Vous allez quitter tous les salons et espaces de %s. + Quitter tous les salons et espaces + Voulez-vous vraiment quitter %s \? + Découverte (%s) + Terminer le réglage + Inviter par courriel, trouver des contacts et plus… + Terminer le réglage de la découverte. + Vous n’utilisez actuellement pas de serveur d’identité. Pour inviter des proches et qu’ils puissent vous trouver, configurez-en un ci-dessous. + Qui sont vos proches \? + Inviter par nom d’utilisateur ou courriel + Assurez-vous que l’accès à la société %s est accordé aux bonnes personnes. Vous pourrez en inviter d’autres plus tard. + Ajouter à l’espace mentionné + Création de l’espace… + Ajoute ( ͡° ͜ʖ ͡°) devant un message en texte brut + Affiche des informations nécessaire au débogage de l’application + Afficher les informations de débogage à l’écran + Ceci ne ressemble pas à une adresse de courriel valide + Politique + Aucune politique fournie par le serveur d’identité + Cacher la politique du serveur d’identité + Afficher la politique du serveur d’identité + Ouvrir les réglages de découverte + Rechercher par nom, identifiant ou courriel + Créer un nouvel espace + Affiche des informations à propos de l’utilisateur + Change votre avatar seulement dans le salon actuel + Change l’avatar du salon actuel + Change votre nom d’affichage seulement dans le salon actuel + Définit le nom du salon + Arrêter d’ignorer un utilisateur, en affichant ses messages à partir de maintenant + Ignore un utilisateur, en masquant ses messages + Tout le monde peut trouver cet espace et le rejoindre + Accès à l’espace + Qui peut accéder \? + Activer les notifications par courriel pour %s + Pour recevoir des courriels de notification, veuillez associer une adresse de courriel à votre compte Matrix + Notification par courriel + Mettre-à-jour l’espace + Changer le nom de l’espace + Activer le chiffrement de l’espace + Changer l’adresse principale de l’espace + Changer l’avatar de l’espace + Vous n’avez pas la permission de changer les rôles requis pour changer différentes parties de cet espace + Sélectionner les rôles nécessaires pour modifier les différentes parties de cet espace + Voir et mettre-à-jour les rôles requis pour changer différentes parties de l’espace. + Permissions de l’espace + Annuler l’exclusion des utilisateurs leur permettra de revenir dans cet espace. + L’exclusion des utilisateurs va les expulser de cet espace et les empêcher de revenir. + L’expulsion des utilisateurs va les supprimer de cet espace +\n +\nPour les empêcher de revenir, vous devriez les exclure. \ No newline at end of file From 745d34c722b506187799ff23c1a4555cc04719cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viorel-C=C4=83t=C4=83lin=20R=C4=83pi=C8=9Beanu?= Date: Thu, 14 Oct 2021 13:34:49 +0000 Subject: [PATCH 122/172] Translated using Weblate (Romanian) Currently translated at 12.6% (337 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index e9e6efadd9..f71ab6de13 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -118,7 +118,7 @@ Adaugă un membru Lista membrilor - Gestionați camerele și spațiile + Administrează camerele și spațiile Bine ați venit la spații! Se creează spațiul… Aleatoriu @@ -332,4 +332,12 @@ %1$s a înlăturat widget-ul %2$s Ați adăugat widget-ul %1$s %1$s a adăugat widget-ul %2$s + Administrează camerele + Revocați ignorarea utilizatorului + Video + Șterge + Părăsește + Salvează + Anulează + Verificați sesiunea \ No newline at end of file From dcc3d9846bfccb3f2760c7dbb78b93acfcc8330b Mon Sep 17 00:00:00 2001 From: tanmatsu Date: Thu, 14 Oct 2021 13:33:51 +0000 Subject: [PATCH 123/172] Translated using Weblate (Romanian) Currently translated at 12.6% (337 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index f71ab6de13..60dfbc1dbb 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -68,8 +68,8 @@ Ați răspuns la apel. %s a răspuns la apel. %1$s: %2$s - %1$s a trimis un autocolant. - Ați trimis un autocolant. + %1$s a trimis un sticker. + Ați trimis un sticker. Invită prieteni Fără schimbări. Ați acceptat invitația pentru %1$s @@ -83,7 +83,7 @@ Mesaj șters de %1$s [motiv: %2$s] Mesaj șters [motiv: %1$s] Mesaj șters de %1$s - Mesaj șters + Mesaj înlăturat Ați înlăturat avatarul camerei %1$s a înlăturat avatarul camerei Ați înlăturat subiectul acestei camere From 3d63140f481c7db031dff5fbc83d3510a9cad47e Mon Sep 17 00:00:00 2001 From: Michael Mihai Date: Thu, 14 Oct 2021 13:33:21 +0000 Subject: [PATCH 124/172] Translated using Weblate (Romanian) Currently translated at 12.6% (337 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ro/ --- vector/src/main/res/values-ro/strings.xml | 57 ++++++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-ro/strings.xml b/vector/src/main/res/values-ro/strings.xml index 60dfbc1dbb..1c45088417 100644 --- a/vector/src/main/res/values-ro/strings.xml +++ b/vector/src/main/res/values-ro/strings.xml @@ -125,7 +125,7 @@ Un spațiu privat pentru organizarea camerelor Pentru a vă alătura unui spațiu existent, aveți nevoie de o invitație. Puteți schimba acest lucru mai târziu - Spații + Spații numai pentru membri Invitații Autocolant Autocolant @@ -296,7 +296,7 @@ • Serverele care se potrivesc %s sunt permise. • Serverele care se potrivesc %s sunt interzise. Tu ai setat lista de acces in aceasta cameră. - %1$s porniți criptarea de la un capat la altul (%2$s) + %1$s porniți criptarea end-to-end (%2$s) Ați făcut istoria camerei viitoare vizibilă pentru %1$s %1$s a făcut istoria camerei viitoare vizibilă pentru %2$s ** Imposibil de decriptat: %s ** @@ -340,4 +340,57 @@ Salvează Anulează Verificați sesiunea + Invitația ta. Motivul: %1$s + Invitația trimisă de %1$s. Motivul: %2$s + Șterge coada de trimitere + Mesajul se trimite… + Mesaj trimis + Sincronizare inițială: +\nSe importă datele contului + Sincronizare inițială: +\nSe importă comunitățile + Sincronizare inițială: +\nSe importă camerele pe care le-ați părăsit + Sincronizare inițială: +\nSe importă camerele la care a-ți primit invitații + Sincronizare inițială: +\nSe importă camerele la care v-ați alăturat + Sincronizare inițială: +\nSe importa camerele + Sincronizare inițială: +\nSe importă crypto + Sincronizare inițială: +\nSe importă contul… + Sincronizare inițială: +\nSe descarcă datele… + Sincronizare inițială: +\nSe așteaptă pentru răspunsul serverului… + Cameră liberă (a fost %s) + Cameră liberă + + %1$s și un altul + %1$s și %2$d alți + %1$s și %2$d de alți + + + %1$s, %2$s, %3$s și %4$d altul + %1$s, %2$s, %3$s și %4$d altele + %1$s, %2$s, %3$s și %4$d altele + + %1$s,%2$s,%3$s și %4$s + %1$s,%2$s și %3$s + %1$s și %2$s + Invitația camerei + Invitație de la %s + În acest moment este imposibil să reintri într-o camră goală. + Eroare de aplicație + Eroare de rețea + A apărut o eroare în timpul incărcării imaginii + Nu s-a putut trimite mesajul + Nu se poate redacta + Dispozitivul expeditorului nu a trimis cheile de decriptare pentru acest mesaj. + Acum sunt blocate serverele ce au adrese IP ce se potrivesc. + Acum sunt permise serverele ce au adrese IP ce se potrivesc. + Sunt blocate serverele ce au adrese IP ce se potrivesc. + Sunt permise serverele ce au adrese IP ce se potrivesc. \ No newline at end of file From ec81920e0a369951ee0d654e171c954aec9a8c0b Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Wed, 13 Oct 2021 21:27:53 +0000 Subject: [PATCH 125/172] Translated using Weblate (Swedish) Currently translated at 99.2% (2648 of 2669 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 90852f9c3e..a3dec653a1 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2983,4 +2983,6 @@ Du har inte behörighet att uppdatera rollerna som krävs för att ändra diverse delar av det här utrmmet Välj de roller som krävs för att ändra diverse delar av det här utrymmet Se och uppdatera rollerna som krävs för att ändra diverse delar av utrymmet. + Vilka är dina lagkamrater\? + Lägg till till det angivna utrymmet \ No newline at end of file From 4ccdf65308bb0e97c8e1fd09be4d061532623c38 Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 15 Oct 2021 11:28:25 +0000 Subject: [PATCH 126/172] Translated using Weblate (French) Currently translated at 100.0% (33 of 33 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr-FR/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/fr-FR/changelogs/40103020.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40103020.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103010.txt b/fastlane/metadata/android/fr-FR/changelogs/40103010.txt new file mode 100644 index 0000000000..326319edb0 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40103010.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Organisez vos salons à l’aide des espaces ! La v1.3.1 corrige également un plantage dans la version v1.3.0 +Liste de tous les changements : https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/fr-FR/changelogs/40103020.txt b/fastlane/metadata/android/fr-FR/changelogs/40103020.txt new file mode 100644 index 0000000000..8a4868cee5 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40103020.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Ajout du support pour Android Auto. Beaucoup de corrections de bogues ! +Liste de tous les changements : https ://github.com/vector-im/element-android/releases/tag/v1.3.2 From bdbe1dd6064d77d8dcb7911a3405432415f1d313 Mon Sep 17 00:00:00 2001 From: Zet Date: Sun, 17 Oct 2021 16:03:19 +0000 Subject: [PATCH 127/172] Translated using Weblate (Arabic) Currently translated at 38.9% (1040 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/ --- vector/src/main/res/values-ar/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index a37fad08d2..8483f4d026 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -1325,4 +1325,9 @@ تصاريح ناقصة لإرسال رسائل صوتية امنح تصريح الوصول للميكريفون. لإكمال هذا الإجراء امنح تصريح الوصول للكميرا عبر إعدادات النظام. + ينشئ نسخة احتياطية + عيّن عبارة مرور + أمّن النسخ الاختياطي بعبارة مرور. + تصدير المفاتيح يدويًا + (متقدم) \ No newline at end of file From 84253586349bfc5f1e54c0fef69e8ac281e74b7e Mon Sep 17 00:00:00 2001 From: zeritti Date: Sun, 17 Oct 2021 08:49:30 +0000 Subject: [PATCH 128/172] Translated using Weblate (Czech) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 40ded266ab..c6b57e47ff 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -3058,4 +3058,7 @@ Nastaví název místnosti Přestane ignorovat uživatele a zobrazí jeho zprávy Ignoruje uživatele a skrývá jeho zprávy + Nedostupný + Offline + Online \ No newline at end of file From ee301bc35571c43ea64e1a6d6b13241f559108c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 16 Oct 2021 16:34:37 +0000 Subject: [PATCH 129/172] Translated using Weblate (Estonian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 22e967b783..27c332b605 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -3002,4 +3002,7 @@ Määrab jututoa nime Lõpeta kasutaja eiramine ja näita edaspidi tema sõnumeid Eirab kasutajat peites kõik tema sõnumid sinu eest + Pole leitav + Võrgust väljas + Võrgus \ No newline at end of file From 0c80f6b8a5872accf5d0e22cb1ef7604d7857beb Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 16 Oct 2021 11:03:07 +0000 Subject: [PATCH 130/172] Translated using Weblate (Persian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 01d8185700..ea798cac52 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -3002,4 +3002,7 @@ نام اتاق را تنظیم می‌کند به کاربری چشم گشوده و پیام‌های بعدیش را نشان می‌دهد از کاربری چشم‌پوشی کرده و پیام‌هایش را از شما پنهان می‌کند + ناموجود + برون‌خط + برخط \ No newline at end of file From ab5b907b7d95ebb4477fa9a97a67ea6e72323156 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 16 Oct 2021 09:40:29 +0000 Subject: [PATCH 131/172] Translated using Weblate (Hungarian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 64b9f8fee4..1926a40115 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -3007,4 +3007,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Szobanév beállítása A felhasználó újbóli figyelembe vétele, és az üzenetei megjelenítése a jövőben Figyelmen kívül hagy egy felhasználót, elrejtve előled az üzeneteit + Elérhetetlen + Kapcsolat nélkül + Kapcsolódva \ No newline at end of file From 1787a8a3587ab404b4ed21de72b9b30ef4e57c65 Mon Sep 17 00:00:00 2001 From: Linerly Date: Sat, 16 Oct 2021 12:12:08 +0000 Subject: [PATCH 132/172] Translated using Weblate (Indonesian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index 6dbb2a764c..ab86b35e6c 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -2950,4 +2950,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mengubah avatar ruangan saat ini Mengubah avatar Anda di ruangan saat ini saja Menampilkan + Tidak Tersedia + Offline + Online \ No newline at end of file From e425532953876d5f26c20349b9f34a298c17de0a Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Sat, 16 Oct 2021 12:22:47 +0000 Subject: [PATCH 133/172] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 7e4d837573..ff824445eb 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -3072,4 +3072,7 @@ Define o nome da sala Para de ignorar um/uma usuário(a), mostrando as mensagens dele/dela de agora em diante Ignora um/uma usuário(a), escondendo as mensagens dele/dela de você + Indisponível + Offline + Online \ No newline at end of file From 9b1b35084cc6d59c9b73575a3e08a49407235030 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 16 Oct 2021 10:23:18 +0000 Subject: [PATCH 134/172] Translated using Weblate (Russian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index b96f77a040..bfc82d9a95 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -312,7 +312,7 @@ Просмотр расшифрованного исходного кода Удалить Переименовать - Содержание отчёта + Пожаловаться на содержимое Активный вызов Ongoing conference call. \nПрисоединиться как %1$s или %2$s @@ -3150,7 +3150,7 @@ Остановить запись Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению Правила - Отсутствие политики, предоставляемой сервером идентификации + Нет правил, предоставляемых сервером идентификации Скрыть правила сервера идентификации Показать правила сервера идентификации Отображает информацию о пользователе @@ -3160,4 +3160,7 @@ Устанавливает имя комнаты Прекращение игнорирования пользователя, показывает его сообщения в дальнейшем Игнорирует пользователя, скрывая его сообщения от вас + Недоступен + Не в сети + В сети \ No newline at end of file From 41167cdc23457df48e70f4e7d6a8a2d61096a5d4 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 16 Oct 2021 09:39:49 +0000 Subject: [PATCH 135/172] Translated using Weblate (Albanian) Currently translated at 99.3% (2655 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 1426a0ea85..379cd4b1f1 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2991,4 +2991,6 @@ Cakton emrin e dhomës E ndal shpërfilljen e një përdoruesi, duke i shfaqur mesazhet e tyre pas kësaj Shpërfill një përdorues, duke fshehur mesazhet e tij prej jush + Jo në linjë + Në linjë \ No newline at end of file From b83a96e1c149d3e9b5a99def0c83fd63658aefc7 Mon Sep 17 00:00:00 2001 From: joshua Date: Sun, 17 Oct 2021 10:15:52 +0000 Subject: [PATCH 136/172] Translated using Weblate (Swedish) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index a3dec653a1..8a9e4974ce 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -747,7 +747,7 @@ Byt nätverk Alla gemenskaper Allmänt - Alternativ + Inställningar Namn eller ID (#example:matrix.org) Du kommer inte att verifiera %1$s (%2$s) om du avbryter nu. Börja igen i hens användarprofil. Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera användare i deras profiler. @@ -2778,8 +2778,8 @@ Känner du dig äventyrlig\? \nDu kan lägga till existerande utrymmen till ett utrymme. Lägg till rum - Du är administratör för det här utrymmet, se till att du har överfört administratörsrättigheter till en annan medlem innan du lämnar. - Det här utrymmet är inte offentligt. Du kommer inte att kunna gå med igen utan en inbjudan. + Du är den enda administratören av detta utrymme. Att lämna det betyder att ingen har kontroll över det. + Du kommer inte att kunna gå med igen om du inte är inbjuden igen. Du är den enda personen här. Om du lämnar så kommer ingen kunna gå med i framtiden, inklusive du. Bjud in till %s Den här funktionen är i beta @@ -2821,13 +2821,13 @@ Tryck på din inspelning för att stoppa eller lyssna %1$d lämnade Håll in för att spela in, släpp för att skicka - Radera inspelat röstmeddelande + Ta bort inspelningen Spelar in röstmeddelande Pausa röstmeddelande Spela röstmeddelande Röstmeddelandelås Svep för att avbryta - Starta röstmeddelande + Börja röstmeddelande Tyvärr så inträffade ett fel vid försök att gå med i: %s Uppgradera till rekommenderad rumsversion Det är rummet kör rumsversion %s, vilken den hör hemservern har markerat som instabil. @@ -2985,4 +2985,17 @@ Se och uppdatera rollerna som krävs för att ändra diverse delar av utrymmet. Vilka är dina lagkamrater\? Lägg till till det angivna utrymmet + Sluta spela in + Lägg till ett mellanslag till alla utrymmen du hanterar. + Lägg till befintliga utrymmen + Lägg till befintliga rum + Välj saker att lämna + Lämna specifika rum och utrymmen… + Lämna inga rum och utrymmen + Du kommer att lämna alla rum och utrymmen i %s. + Lämna alla rum och utrymmen + Är du säker på att du vill lämna %s\? + Discovery (%s) + Slutför konfigurationen + Se till att rätt personer har tillgång till %s företag. Du kan bjuda in mer senare. \ No newline at end of file From 68591f3ddde015a9ee689cbbd21a06111d883fc9 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sun, 17 Oct 2021 09:11:01 +0000 Subject: [PATCH 137/172] Translated using Weblate (Swedish) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 8a9e4974ce..f8ac8eba58 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2998,4 +2998,11 @@ Discovery (%s) Slutför konfigurationen Se till att rätt personer har tillgång till %s företag. Du kan bjuda in mer senare. + Bjud in med e-post, hitta kontakter och mer… + Slutför inställning av upptäckbarhet. + Du använder för närvarande ingen identitetsserver. För att bjuda in teammedlemmar och kunna upptäckas av dem, konfigurera en nedan. + Bjud in med användarnamn eller e-postadress + Otillgänglig + Offline + Online \ No newline at end of file From be874d99274adcfb78c8889ff7f5c7ff0d577e1b Mon Sep 17 00:00:00 2001 From: sr093906 Date: Sun, 17 Oct 2021 01:01:10 +0000 Subject: [PATCH 138/172] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 92a5c757e3..28d2c8e250 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -2956,4 +2956,7 @@ 设置聊天室名称 停止忽略用户,继续显示他们的消息 忽略用户,隐藏他们的消息 + 不可用 + 离线 + 在线 \ No newline at end of file From 964937db98b7e8c3b0f3f4dbe28d650c772a90f6 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 16 Oct 2021 09:39:55 +0000 Subject: [PATCH 139/172] Translated using Weblate (Hungarian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ --- fastlane/metadata/android/hu-HU/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/hu-HU/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40103030.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40103030.txt b/fastlane/metadata/android/hu-HU/changelogs/40103030.txt new file mode 100644 index 0000000000..8795818607 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Fő változás ebben a verzióban: Azonosítási szerver feltételek megjelenítése a beállításoknál. Ideiglenesen az Android Auto támogatás eltávolítása. +Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/hu-HU/full_description.txt b/fastlane/metadata/android/hu-HU/full_description.txt index 032346ccb8..899b4cd978 100644 --- a/fastlane/metadata/android/hu-HU/full_description.txt +++ b/fastlane/metadata/android/hu-HU/full_description.txt @@ -38,3 +38,6 @@ Igazi végpontok között titkosítás (csak a beszélgetésben résztvevők tud Vedd fel a fonalat Maradj kapcsolatban bárhol minden eszközödön a szinkronizált üzenetekkel és a weben a https://app.element.io oldallal + +Nyílt forráskód +Element Android egy nyílt forráskódú projekt a GitHubon. Küldj hibajegyet és/vagy vegyél részt a fejlesztésében itt: https://github.com/vector-im/element-android From 99bb0f994fbd9765e634c35127080c2215054776 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Sat, 16 Oct 2021 12:26:13 +0000 Subject: [PATCH 140/172] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- .../metadata/android/pt-BR/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/pt-BR/full_description.txt | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40103030.txt diff --git a/fastlane/metadata/android/pt-BR/changelogs/40103030.txt b/fastlane/metadata/android/pt-BR/changelogs/40103030.txt new file mode 100644 index 0000000000..12ababb2ce --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Fazer política(s) de servidor de identidade visível(is) nas configurações. Remover temporariamente suporte a Android Auto. +Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/pt-BR/full_description.txt b/fastlane/metadata/android/pt-BR/full_description.txt index 7bb3d0981d..fe560cdda0 100644 --- a/fastlane/metadata/android/pt-BR/full_description.txt +++ b/fastlane/metadata/android/pt-BR/full_description.txt @@ -1,4 +1,4 @@ -Element é tanto um mensageiro seguro como um app de colaboração de time de produtividade que é ideal para chats de grupo enquanto se trabalha remotamente. Este app de chat usa encriptação ponta-a-ponta para prover conferência de vídeo, compartilhamento de arquivo e chamadas de voz poderasos. +Element é tanto um mensageiro seguro como um app de colaboração de time de produtividade que é ideal para chats de grupo enquanto se trabalha remotamente. Este app de chat usa encriptação ponta-a-ponta para prover conferência de vídeo, compartilhamento de arquivo e chamadas de voz poderosos. As funções de Element incluem: - Ferramentas de comunicação online avançadas @@ -22,9 +22,9 @@ Para permitir mais controle de seus dados e conversas sensíveis, Element pode s Você decide onde manter seus dados e mensagens. Sem o risco de data mining ou acesso de terceiros. Element põe você em controle de diferentes maneiras: -1. Pegar uma conta grátis no servidor público matrix.org hospedado pelos desenvolvedores Matrix, ou escolha de milhares de servidores públicos hospedados por pessoas se voluntariando -2. Auto-hospedar sua conta ao rodar um servidor em sua própria infraestrutura de TI -3. Fazer signup para uma conta num servidor personalizado ao simplesmente assinar a plataforma de hospedagem Element Matrix Services +1. Pegue uma conta grátis no servidor público matrix.org hospedado pelos desenvolvedores Matrix, ou escolha de milhares de servidores públicos hospedados por pessoas se voluntariando +2. Auto-hospede sua conta ao rodar um servidor em sua própria infraestrutura de TI +3. Faça signup para uma conta num servidor personalizado ao simplesmente assinar a plataforma de hospedagem Element Matrix Services Mensageria e colaboração abertos Você pode fazer chat com qualquer pessoa na rede Matrix, caso ela esteja usando Element, um outro app de Matrix ou mesmo se ela estiver usando um app de mensageria diferente. @@ -37,3 +37,6 @@ Messageria, chamadas de voz e vídeo, compartilhamento de arquivo, compartilhame Continue de onde você parou Fique em contato onde quer que você esteja com histórico de mensagem completamente sincronizado por todos os seus dispositivos e na web em https://app.element.io + +Open source +Element Android é um projeto open source, hospedado por GitHub. Por favor reporte bugs e/ou contribua para seu desenvolvimento em https://github.com/vector-im/element-android From 28ccfbdc0d112256449c2ae398b8b2474a36c197 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 16 Oct 2021 10:25:45 +0000 Subject: [PATCH 141/172] Translated using Weblate (Russian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru-RU/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/ru-RU/full_description.txt | 4 ++++ 2 files changed, 6 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40103030.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/40103030.txt b/fastlane/metadata/android/ru-RU/changelogs/40103030.txt new file mode 100644 index 0000000000..94b43e5f30 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: Правила сервера идентификации теперь видимы в настройках. Временно убрана поддержка Android Auto. +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt index 3d21b20a90..3d8949e466 100644 --- a/fastlane/metadata/android/ru-RU/full_description.txt +++ b/fastlane/metadata/android/ru-RU/full_description.txt @@ -37,3 +37,7 @@ Element дает вам возможность контролировать си Восстанавливайте связь с того места, где остановились. Оставайтесь на связи, где бы вы ни находились, с полностью синхронизированной историей сообщений на всех ваших устройствах и в Интернете по адресу https://app.element.io + + +Открытый исходный код +Element Android - это проект с открытым исходным кодом, размещенный на GitHub. Пожалуйста, сообщайте об ошибках и/или вносите вклад в его развитие по адресу https://github.com/vector-im/element-android. From 5a8298194401b40897d7ad03d89b4250ee9f4ae2 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Sat, 16 Oct 2021 18:15:55 +0000 Subject: [PATCH 142/172] Translated using Weblate (Swedish) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/sv-SE/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40103030.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40103030.txt b/fastlane/metadata/android/sv-SE/changelogs/40103030.txt new file mode 100644 index 0000000000..3d55703e86 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Gör identitetsserverpolicy(er) synliga i inställningarna. Ta tillfälligt bort stöd för Android Auto. +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/sv-SE/full_description.txt b/fastlane/metadata/android/sv-SE/full_description.txt index 5302976ed7..03d837626e 100644 --- a/fastlane/metadata/android/sv-SE/full_description.txt +++ b/fastlane/metadata/android/sv-SE/full_description.txt @@ -37,3 +37,6 @@ Meddelanden, röst- och videosamtal, fildelning, skärmdelning och massa integra Fortsätt där du lämnade Håll kontakten vart du än är med fullt synkroniserad meddelandehistorik på alla dina enheter och på webben på https://app.element.io + +Öppen källkod +Element Android är projekt baserat på öppen källkod, som ligger på GitHub. Vänligen rapportera buggar och/eller bidra till dess utveckling på https://github.com/vector-im/element-android From f8850f5eb8de6d5c8d4d7f5ad2c12ba3c170578d Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sat, 16 Oct 2021 23:40:20 +0000 Subject: [PATCH 143/172] Translated using Weblate (Ukrainian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/uk/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40103030.txt diff --git a/fastlane/metadata/android/uk/changelogs/40103030.txt b/fastlane/metadata/android/uk/changelogs/40103030.txt new file mode 100644 index 0000000000..af25d23c42 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: додано політику ідентифікації сервера (IES) у налаштуваннях. Тимчасово вилучено автозаповнення Android. +Усі зміни: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt index 285f577452..3c59d860ac 100644 --- a/fastlane/metadata/android/uk/full_description.txt +++ b/fastlane/metadata/android/uk/full_description.txt @@ -37,3 +37,6 @@ Element надає такі можливості на вибір: Продовжуйте, де зупинилися Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io + +Відкритий код +Element для Android це проєкт з відкритим кодом, розміщений GitHub. Будь ласка, повідомте про помилки та/або сприяйте його розвитку на https://github.com/vector-im/element-android From 022452cd45ec7aecd00c9884069131590f9b733c Mon Sep 17 00:00:00 2001 From: sr093906 Date: Sun, 17 Oct 2021 00:58:08 +0000 Subject: [PATCH 144/172] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hans/ --- fastlane/metadata/android/zh-CN/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/zh-CN/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/zh-CN/changelogs/40103030.txt diff --git a/fastlane/metadata/android/zh-CN/changelogs/40103030.txt b/fastlane/metadata/android/zh-CN/changelogs/40103030.txt new file mode 100644 index 0000000000..1f6ff391e1 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/40103030.txt @@ -0,0 +1,2 @@ +此版本中的主要更改:使身份服务器策略在设置中可见。 暂时移除 Android Auto 支持。 +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt index fa6b00f1e4..3dae8deb67 100644 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -37,3 +37,6 @@ Element 透过不同的方式让你掌控一切: 从上次离开的地方开始 无论你身在何处,都可以透过在你所有设备与网页 https://app.element.io 间完全同步的信息历史保持联络 + +开源 + Element Android 是一个开源项目,由 GitHub 托管。 请在 https://github.com/vector-im/element-android 报告错误和/或为其开发做出贡献 From 4a1f1a9fa541be475ec2b8fb635b691e596acb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 16 Oct 2021 16:33:46 +0000 Subject: [PATCH 145/172] Translated using Weblate (Estonian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/et/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40103030.txt diff --git a/fastlane/metadata/android/et/changelogs/40103030.txt b/fastlane/metadata/android/et/changelogs/40103030.txt new file mode 100644 index 0000000000..6293ccab85 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: Isikutuvastusserveri kasutustingimused on leitavad seadistustest ja ajutiselt eemaldasime Android Auto toe. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/et/full_description.txt b/fastlane/metadata/android/et/full_description.txt index ee0adef9ac..a3a3bc37e2 100644 --- a/fastlane/metadata/android/et/full_description.txt +++ b/fastlane/metadata/android/et/full_description.txt @@ -37,3 +37,6 @@ Sõnumid, hääl- ja videokõned, failide jagamine, ekraani jagamine ja terve hu Jätka sealt, kus pooleli jäid Saad suhelda kõigis oma seadmetes ja ka veebis aadressil https://app.element.io ning sealjuures täielikult sünkroonitud sõnumite ajalooga. + +Avatud lähtekoodiga tarkvara +Element Android on Github'is hallatud avatud lähtekoodiga tarkvaraprojekt. Palun teata vigadest ja/või osale arenduses https://github.com/vector-im/element-android lehel From 87bd145e8e8e117d722b2f29de37d3ea64760769 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 16 Oct 2021 11:07:06 +0000 Subject: [PATCH 146/172] Translated using Weblate (Persian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/fa/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40103030.txt diff --git a/fastlane/metadata/android/fa/changelogs/40103030.txt b/fastlane/metadata/android/fa/changelogs/40103030.txt new file mode 100644 index 0000000000..49efae1951 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40103030.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: نمایان کردن سیاست(های) کارساز هویت در تنظیمات. برداشتن موقّتی پشتیبانی اندروید خودرو. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/fa/full_description.txt b/fastlane/metadata/android/fa/full_description.txt index cd2d4eb4c1..98055857bb 100644 --- a/fastlane/metadata/android/fa/full_description.txt +++ b/fastlane/metadata/android/fa/full_description.txt @@ -37,3 +37,6 @@ ادامه از جایی که رها کرده‌اید هر کجا که هستید، با هم‌گام سازی کامل تاریخچهٔ پیام‌ها بین همهٔ افزاره‌هایتان و وب روی https://app.element.io در دسترس باشید + +نرم‌افزار آزاد +المنت اندروید، یک پروژهٔ نرم‌افزار آزاد میزبانی‌شده روی گیت‌هاب است. لطفاً گزارش مشکلات و مشارکت‌ها را به توسه‌اش به این نشانی بفرستید: https://github.com/vector-im/element-android From 6cb254e74345487682ae89a312e7aa7be296f14e Mon Sep 17 00:00:00 2001 From: Linerly Date: Sat, 16 Oct 2021 12:54:47 +0000 Subject: [PATCH 147/172] Translated using Weblate (Indonesian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/id/full_description.txt | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/id/changelogs/40103030.txt diff --git a/fastlane/metadata/android/id/changelogs/40103030.txt b/fastlane/metadata/android/id/changelogs/40103030.txt new file mode 100644 index 0000000000..630593a107 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Membuat kebijakan server identitas terlihat di pengaturan. Menghilangkan dukungan Android Auto untuk sementara. +Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt index bd357bb161..dfa9c8c826 100644 --- a/fastlane/metadata/android/id/full_description.txt +++ b/fastlane/metadata/android/id/full_description.txt @@ -30,10 +30,13 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda: Anda dapat mengobrol dengan siapa saja di jaringan Matrix, apakah mereka menggunakan Element, aplikasi Matrix lain atau bahkan jika mereka menggunakan aplikasi perpesanan yang berbeda. Sangat aman -Enkripsi ujung-ke-ujung beneran (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat yang ditanda tangani silang. +Enkripsi ujung-ke-ujung yang nyata (hanya mereka yang dalam percakapan dapat mendekripsi pesan), dan verifikasi perangkat menggunakan penandatanganan-silang. Komunikasi dan integrasi lengkap Perpesanan, panggilan suara dan video, berbagi file, berbagi layar dan banyak integrasi, bot dan widget. Buat ruangan, komunitas, tetap terhubung dan selesaikan hal-hal. Ambil di mana Anda tinggalkan Tetap terhubung di mana pun Anda berada dengan riwayat pesan yang sepenuhnya disinkronkan di semua perangkat Anda dan di web di https://app.element.io + +Open source +Element Android adalah proyek sumber terbuka, di-host oleh GitHub. Silakan melaporkan bug dan/atau membuat kontribusi ke pengembangannya di https://github.com/vector-im/element-android From 1c119c2c5d64746d77f340e37acfc04435ca64de Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 16 Oct 2021 09:38:41 +0000 Subject: [PATCH 148/172] Translated using Weblate (Albanian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/ --- fastlane/metadata/android/sq/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/sq/full_description.txt | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/sq/changelogs/40103030.txt diff --git a/fastlane/metadata/android/sq/changelogs/40103030.txt b/fastlane/metadata/android/sq/changelogs/40103030.txt new file mode 100644 index 0000000000..e52e91eed4 --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Bërje të dukshëm e rregullit(ave) të shërbyesit të identiteteve te rregullimet. Heqje përkohësisht e mbulimit për Android Auto. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/sq/full_description.txt b/fastlane/metadata/android/sq/full_description.txt index 96cf102272..ff0ea20da7 100644 --- a/fastlane/metadata/android/sq/full_description.txt +++ b/fastlane/metadata/android/sq/full_description.txt @@ -1,4 +1,4 @@ -Element-i është si aplikacion shkëmbyes i sigurt mesazhesh, ashtu edhe bashkëpunimi prodhimtar ekipi, i cili është ideal për fjalosje në grup, teksa punohet së largët. Ky aplikacion fjalosjeje përdor fshehtëzim skaj-më-skaj për të furnizuar konferencë video, shkëmbim kartelash dhe thirrje me zë të fuqishme. +Element-i është si aplikacion shkëmbyes i sigurt mesazhesh, ashtu edhe bashkëpunimi prodhimtar ekipi, i cili është ideal për fjalosje në grup, teksa punohet së largëti. Ky aplikacion fjalosjeje përdor fshehtëzim skaj-më-skaj për të furnizuar konferencë video, shkëmbim kartelash dhe thirrje me zë të fuqishme. Në veçoritë e Element-it përfshihen: - Mjete të thelluara komunikimi internetor @@ -8,7 +8,7 @@ Element-i është si aplikacion shkëmbyes i sigurt mesazhesh, ashtu edhe bashk - Fjalosje video të llojit VoIP dhe tregim ekrani - Integrim i kollajtë me mjetet tuaja të parapëlqyera të bashkëpunimit internetor, mjete administrimi projektesh, shërbime VoIP dhe aplikacione të tjera shkëmbimi mesazhesh në ekip -Element-i është plotësisht i ndryshëm nga aplikacione të tjera shkëmbimi mesazhesh dhe bashkëpunimi. Funksionimi i tij bazohet në Matrix, një rrjet i hapët për mesazhe të siguruar dhe komunikim të decentralizuar. Lejon vetëstrehim, për t’u lejuar përdoruesve pronësi dhe kontroll maksimal të të dhënave dhe mesazheve të tyre. +Element-i është plotësisht i ndryshëm nga aplikacione të tjera shkëmbimi mesazhesh dhe bashkëpunimi. Funksionimi i tij bazohet në Matrix, një rrjet i hapët për mesazhe të siguruar dhe komunikim të decentralizuar. Lejon vetëstrehim, për t’u dhënë përdoruesve pronësi dhe kontroll maksimal të të dhënave dhe mesazheve të tyre. Privatësi dhe shkëmbim mesazhesh të fshehtëzuar Element-i ju mbron nga reklama të padëshiruara, shfrytëzim të dhënash dhe vatha dixhitale. Ai siguron gjithashtu krejt të dhënat tuaja, komunikime tek-për-tek me video dhe me zë, përmes fshehtëzimi skaj-më-skaj dhe verifikim “cross-signed” pajisjesh. @@ -37,3 +37,6 @@ Shkëmbim mesazhesh, thirrje me zë dhe me video, shkëmbim kartelash, tregim ek Rifillojani atje ku e latë Jini në dijeni, kudo ku gjendeni, me historik plotësisht të njëkohësuar mesazhesh nëpër krejt pajisjet tuaja dhe në internet te https://app.element.io + +Me burim të hapët +Element-i për Android është një projekt me burim të hapët, strehuar në GitHub. Ju lutemi, njoftoni të meta dhe/ose jepni ndihmesë në zhvillimin e tij te https://github.com/vector-im/element-android From ca79e87e004aa49d0e73b425d3c7fd1d3e977331 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 18 Oct 2021 16:13:22 +0100 Subject: [PATCH 149/172] applying a retry when attempting to send dummy payload to device --- .../org/matrix/android/sdk/internal/crypto/EventDecryptor.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index b1680d5153..57381eacfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -38,6 +38,8 @@ import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +private const val SEND_TO_DEVICE_RETRY_COUNT = 3 + @SessionScope internal class EventDecryptor @Inject constructor( private val cryptoCoroutineScope: CoroutineScope, @@ -173,7 +175,7 @@ internal class EventDecryptor @Inject constructor( withContext(coroutineDispatchers.io) { val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) try { - sendToDeviceTask.execute(sendToDeviceParams) + sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT) } catch (failure: Throwable) { Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") } From c8a8d2e0bf8446809e405036aac3d59fc58975d1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 18 Oct 2021 16:15:07 +0100 Subject: [PATCH 150/172] applying a retry when attempting to fetch one time keys, tries to catch flaky network conditions --- .../crypto/actions/EnsureOlmSessionsForDevicesAction.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index 52876b0fff..3979ff1fb4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt @@ -25,6 +25,8 @@ import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDevi import timber.log.Timber import javax.inject.Inject +private const val ONE_TIME_KEYS_RETRY_COUNT = 3 + internal class EnsureOlmSessionsForDevicesAction @Inject constructor( private val olmDevice: MXOlmDevice, private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { @@ -72,7 +74,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) - val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams) + val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT) Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys") for ((userId, deviceInfos) in devicesByUser) { for (deviceInfo in deviceInfos) { From aea22201c3da56ab7169795d45cb95405ab7b9d7 Mon Sep 17 00:00:00 2001 From: Aris Kotsomitopoulos <60798129+ariskotsomitopoulos@users.noreply.github.com> Date: Tue, 19 Oct 2021 00:20:03 +0300 Subject: [PATCH 151/172] Feature/aris/issue 465 scrub exif data (#4248) Implement ImageExifTagRemover to scrub user sensitive data while sending original size photos - Return a not scrubbed file when there is an exception while scrubbing the jpeg file - Improve error handling on image compression --- changelog.d/4264.misc | 1 + changelog.d/465.misc | 1 + dependencies.gradle | 3 + matrix-sdk-android/build.gradle | 3 + .../session/content/ImageCompressor.kt | 9 +- .../session/content/ImageExifTagRemover.kt | 86 +++++++++++++++++++ .../session/content/UploadContentWorker.kt | 9 +- 7 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 changelog.d/4264.misc create mode 100644 changelog.d/465.misc create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt diff --git a/changelog.d/4264.misc b/changelog.d/4264.misc new file mode 100644 index 0000000000..601abbf792 --- /dev/null +++ b/changelog.d/4264.misc @@ -0,0 +1 @@ +Uppon sharing image compression fails, return the original image diff --git a/changelog.d/465.misc b/changelog.d/465.misc new file mode 100644 index 0000000000..709d1f1957 --- /dev/null +++ b/changelog.d/465.misc @@ -0,0 +1 @@ +Scrub user sensitive data like gps location from images when sending on original quality diff --git a/dependencies.gradle b/dependencies.gradle index 1e3c492149..be6ce3f04f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -130,6 +130,9 @@ ext.libs = [ 'emojiMaterial' : "com.vanniktech:emoji-material:$vanniktechEmoji", 'emojiGoogle' : "com.vanniktech:emoji-google:$vanniktechEmoji" ], + apache:[ + 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" + ], tests : [ 'kluent' : "org.amshove.kluent:kluent-android:1.68", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7f5e8eb391..43ca243ec5 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -154,6 +154,9 @@ dependencies { // Video compression implementation 'com.otaliastudios:transcoder:0.10.4' + // Exif data handling + implementation libs.apache.commonsImaging + // Phone number https://github.com/google/libphonenumber implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35' diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt index 9b01d0a00e..01eb52ff22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt @@ -20,22 +20,23 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix import androidx.exifinterface.media.ExifInterface -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.TemporaryFileCreator import timber.log.Timber import java.io.File import javax.inject.Inject internal class ImageCompressor @Inject constructor( - private val temporaryFileCreator: TemporaryFileCreator + private val temporaryFileCreator: TemporaryFileCreator, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) { suspend fun compress( imageFile: File, desiredWidth: Int, desiredHeight: Int, desiredQuality: Int = 80): File { - return withContext(Dispatchers.IO) { + return withContext(coroutineDispatchers.io) { val compressedBitmap = BitmapFactory.Options().run { inJustDecodeBounds = true decodeBitmap(imageFile, this) @@ -52,6 +53,8 @@ internal class ImageCompressor @Inject constructor( destinationFile.outputStream().use { compressedBitmap.compress(Bitmap.CompressFormat.JPEG, desiredQuality, it) } + }.onFailure { + return@withContext imageFile } destinationFile diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt new file mode 100644 index 0000000000..239a768498 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.content + +import kotlinx.coroutines.withContext +import org.apache.sanselan.Sanselan +import org.apache.sanselan.formats.jpeg.JpegImageMetadata +import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter +import org.apache.sanselan.formats.tiff.constants.ExifTagConstants +import org.apache.sanselan.formats.tiff.constants.GPSTagConstants +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.util.TemporaryFileCreator +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +/** + * This class is responsible for removing Exif tags from image files + */ + +internal class ImageExifTagRemover @Inject constructor( + private val temporaryFileCreator: TemporaryFileCreator, + private val coroutineDispatchers: MatrixCoroutineDispatchers +) { + + /** + * Remove sensitive exif tags from a jpeg image file. + * Scrubbing exif tags like GPS location and user comments + * @param jpegImageFile The image file to be scrubbed + * @return the new scrubbed image file, or the original file if the operation failed + */ + suspend fun removeSensitiveJpegExifTags(jpegImageFile: File): File = withContext(coroutineDispatchers.io) { + val outputSet = tryOrNull("Unable to read JpegImageMetadata") { + (Sanselan.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet + } ?: return@withContext jpegImageFile + + tryOrNull("Unable to remove ExifData") { + outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) + outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_1) + outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_2) + outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) + } ?: return@withContext jpegImageFile + + val scrubbedFile = temporaryFileCreator.create() + return@withContext runCatching { + FileOutputStream(scrubbedFile).use { fos -> + val outputStream = BufferedOutputStream(fos) + ExifRewriter().updateExifMetadataLossless(jpegImageFile, outputStream, outputSet) + } + }.fold( + onSuccess = { + scrubbedFile + }, + onFailure = { + scrubbedFile.delete() + jpegImageFile + } + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 6fd92047b3..0c5a90ca60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -64,7 +64,7 @@ private data class NewAttachmentAttributes( * Possible next worker : Always [MultipleEventSendingDispatcherWorker] */ internal class UploadContentWorker(val context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker(context, params, Params::class.java) { + SessionSafeCoroutineWorker(context, params, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -81,6 +81,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter @Inject lateinit var fileService: DefaultFileService @Inject lateinit var cancelSendTracker: CancelSendTracker @Inject lateinit var imageCompressor: ImageCompressor + @Inject lateinit var imageExitTagRemover: ImageExifTagRemover @Inject lateinit var videoCompressor: VideoCompressor @Inject lateinit var thumbnailExtractor: ThumbnailExtractor @Inject lateinit var localEchoRepository: LocalEchoRepository @@ -114,7 +115,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } val attachment = params.attachment - val filesToDelete = mutableListOf() + val filesToDelete = hashSetOf() return try { val inputStream = context.contentResolver.openInputStream(attachment.queryUri) @@ -219,6 +220,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } } } + } else if (attachment.type == ContentAttachmentData.Type.IMAGE && !params.compressBeforeSending) { + fileToUpload = imageExitTagRemover.removeSensitiveJpegExifTags(workingFile) + .also { filesToDelete.add(it) } + newAttachmentAttributes = newAttachmentAttributes.copy(newFileSize = fileToUpload.length()) } else { fileToUpload = workingFile // Fix: OpenableColumns.SIZE may return -1 or 0 From f5eaf2f05fe7134d58e6b1fa1abd70c14ed4ba0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 11:35:49 +0200 Subject: [PATCH 152/172] Align wording with Element Web --- .../src/main/java/im/vector/app/features/command/Command.kt | 4 ++-- vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 33ccd08d22..68fcfec555 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -34,8 +34,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user, false), ROOM_NAME("/roomname", "", R.string.command_description_room_name, false), INVITE("/invite", " [reason]", R.string.command_description_invite_user, false), - JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), - PART("/part", " [reason]", R.string.command_description_part_room, false), + JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), + PART("/part", " [reason]", R.string.command_description_part_room, false), TOPIC("/topic", "", R.string.command_description_topic, false), KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7fa4918266..274753ee3f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1837,7 +1837,7 @@ Deops user with given id Sets the room name Invites user with given id to current room - Joins room with given alias + Joins room with given address Leave room Set the room topic Kicks user with given id From fe2ba2844147171b72c756716bfac5a96d10aa83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 11:55:09 +0200 Subject: [PATCH 153/172] Implement /part command, with or without parameter --- changelog.d/2909.feature | 1 + .../im/vector/app/features/command/Command.kt | 2 +- .../app/features/command/CommandParser.kt | 19 ++++--------------- .../app/features/command/ParsedCommand.kt | 2 +- .../detail/composer/TextComposerViewModel.kt | 18 ++++++++++++++++-- 5 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 changelog.d/2909.feature diff --git a/changelog.d/2909.feature b/changelog.d/2909.feature new file mode 100644 index 0000000000..4d72734192 --- /dev/null +++ b/changelog.d/2909.feature @@ -0,0 +1 @@ +Implement /part command, with or without parameter \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 68fcfec555..ccabd25ff4 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -35,7 +35,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d ROOM_NAME("/roomname", "", R.string.command_description_room_name, false), INVITE("/invite", " [reason]", R.string.command_description_invite_user, false), JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), - PART("/part", " [reason]", R.string.command_description_part_room, false), + PART("/part", "[]", R.string.command_description_part_room, false), TOPIC("/topic", "", R.string.command_description_topic, false), KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index e570033d35..3a6b005d6f 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -158,21 +158,10 @@ object CommandParser { } } Command.PART.command -> { - if (messageParts.size >= 2) { - val roomAlias = messageParts[1] - - if (roomAlias.isNotEmpty()) { - ParsedCommand.PartRoom( - roomAlias, - textMessage.substring(Command.PART.length + roomAlias.length) - .trim() - .takeIf { it.isNotBlank() } - ) - } else { - ParsedCommand.ErrorSyntax(Command.PART) - } - } else { - ParsedCommand.ErrorSyntax(Command.PART) + when (messageParts.size) { + 1 -> ParsedCommand.PartRoom(null) + 2 -> ParsedCommand.PartRoom(messageParts[1]) + else -> ParsedCommand.ErrorSyntax(Command.PART) } } Command.ROOM_NAME.command -> { diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index bafb9153e6..89aa8d9188 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -49,7 +49,7 @@ sealed class ParsedCommand { class Invite(val userId: String, val reason: String?) : ParsedCommand() class Invite3Pid(val threePid: ThreePid) : ParsedCommand() class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() - class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand() + class PartRoom(val roomAlias: String?) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand() class KickUser(val userId: String, val reason: String?) : ParsedCommand() class ChangeDisplayName(val displayName: String) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 742d2848a1..08f44f30f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -225,8 +225,8 @@ class TextComposerViewModel @AssistedInject constructor( popDraft() } is ParsedCommand.PartRoom -> { - // TODO - _viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented) + handlePartSlashCommand(slashCommandResult) + popDraft() } is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) @@ -578,6 +578,20 @@ class TextComposerViewModel @AssistedInject constructor( } } + private fun handlePartSlashCommand(command: ParsedCommand.PartRoom) { + launchSlashCommandFlowSuspendable { + if (command.roomAlias == null) { + // Leave the current room + room + } else { + session.getRoomSummary(roomIdOrAlias = command.roomAlias) + ?.roomId + ?.let { session.getRoom(it) } + } + ?.leave(reason = null) + } + } + private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) { launchSlashCommandFlowSuspendable { room.kick(kick.userId, kick.reason) From 364654b685c64b40ec2a914cac2fc6ba8c246fc8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 12:33:47 +0200 Subject: [PATCH 154/172] Fix crash on slash commands Exceptions --- changelog.d/4261.bugfix | 1 + .../features/home/room/detail/composer/TextComposerViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/4261.bugfix diff --git a/changelog.d/4261.bugfix b/changelog.d/4261.bugfix new file mode 100644 index 0000000000..4283335131 --- /dev/null +++ b/changelog.d/4261.bugfix @@ -0,0 +1 @@ +Fix crash on slash commands Exceptions \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 08f44f30f7..73b400e8d5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -709,7 +709,7 @@ class TextComposerViewModel @AssistedInject constructor( val event = try { block() TextComposerViewEvents.SlashCommandResultOk - } catch (failure: Exception) { + } catch (failure: Throwable) { TextComposerViewEvents.SlashCommandResultError(failure) } _viewEvents.post(event) From 46261997613d89bea77cf31284591d3df036e07f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 13:49:18 +0200 Subject: [PATCH 155/172] Slash commands: popDraft() only in case of success, and display a loading dialog during processing --- .../home/room/detail/RoomDetailFragment.kt | 7 +- .../detail/composer/TextComposerViewEvents.kt | 4 +- .../detail/composer/TextComposerViewModel.kt | 69 +++++++++---------- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index e9948e6cf4..473a993395 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1451,8 +1451,8 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) { when (sendMessageResult) { - is TextComposerViewEvents.SlashCommandHandled -> { - sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } + is TextComposerViewEvents.SlashCommandLoading -> { + showLoading(null) } is TextComposerViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) @@ -1461,9 +1461,12 @@ class RoomDetailFragment @Inject constructor( displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } is TextComposerViewEvents.SlashCommandResultOk -> { + dismissLoadingDialog() views.composerLayout.setTextIfDifferent("") + sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } } is TextComposerViewEvents.SlashCommandResultError -> { + dismissLoadingDialog() displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } is TextComposerViewEvents.SlashCommandNotImplemented -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt index 691ed4d93e..ff4a09ad71 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt @@ -32,8 +32,8 @@ sealed class TextComposerViewEvents : VectorViewEvents { data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult() - data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() - object SlashCommandResultOk : SendMessageResult() + object SlashCommandLoading : SendMessageResult() + data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 73b400e8d5..b635602189 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -176,19 +176,15 @@ class TextComposerViewModel @AssistedInject constructor( } is ParsedCommand.ChangeRoomName -> { handleChangeRoomNameSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.Invite -> { handleInviteSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.Invite3Pid -> { handleInvite3pidSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.SetUserPowerLevel -> { handleSetUserPowerLevel(slashCommandResult) - popDraft() } is ParsedCommand.ClearScalarToken -> { // TODO @@ -196,29 +192,24 @@ class TextComposerViewModel @AssistedInject constructor( } is ParsedCommand.SetMarkdown -> { vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled( + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk( if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) popDraft() } is ParsedCommand.BanUser -> { handleBanSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.UnbanUser -> { handleUnbanSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.IgnoreUser -> { handleIgnoreSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.UnignoreUser -> { handleUnignoreSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.KickUser -> { handleKickSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.JoinRoom -> { handleJoinToAnotherRoomSlashCommand(slashCommandResult) @@ -226,25 +217,24 @@ class TextComposerViewModel @AssistedInject constructor( } is ParsedCommand.PartRoom -> { handlePartSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendRainbow -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendRainbowEmote -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendSpoiler -> { @@ -252,61 +242,56 @@ class TextComposerViewModel @AssistedInject constructor( "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", "${slashCommandResult.message}" ) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendShrug -> { sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendLenny -> { sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendChatEffect -> { sendChatEffect(slashCommandResult) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendPoll -> { room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") }) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.ChangeDisplayName -> { handleChangeDisplayNameSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.ChangeDisplayNameForRoom -> { handleChangeDisplayNameForRoomSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.ChangeRoomAvatar -> { handleChangeRoomAvatarSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.ChangeAvatarForRoom -> { handleChangeAvatarForRoomSlashCommand(slashCommandResult) - popDraft() } is ParsedCommand.ShowUser -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) handleWhoisSlashCommand(slashCommandResult) popDraft() } is ParsedCommand.DiscardSession -> { if (room.isEncrypted()) { session.cryptoService().discardOutboundSession(room.roomId) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } else { - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) _viewEvents.post( TextComposerViewEvents .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled)) @@ -314,6 +299,7 @@ class TextComposerViewModel @AssistedInject constructor( } } is ParsedCommand.CreateSpace -> { + _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { val params = CreateSpaceParams().apply { @@ -328,14 +314,16 @@ class TextComposerViewModel @AssistedInject constructor( null, true ) + popDraft() + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) } } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) - popDraft() + Unit } is ParsedCommand.AddToSpace -> { + _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().getSpace(slashCommandResult.spaceId) @@ -345,34 +333,38 @@ class TextComposerViewModel @AssistedInject constructor( null, false ) + popDraft() + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) } } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) - popDraft() + Unit } is ParsedCommand.JoinSpace -> { + _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) + popDraft() + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) } } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) - popDraft() + Unit } is ParsedCommand.LeaveRoom -> { viewModelScope.launch(Dispatchers.IO) { try { session.getRoom(slashCommandResult.roomId)?.leave(null) + popDraft() + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) } } - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) - popDraft() + Unit } is ParsedCommand.UpgradeRoom -> { _viewEvents.post( @@ -381,7 +373,7 @@ class TextComposerViewModel @AssistedInject constructor( room.roomSummary()?.isPublic ?: false ) ) - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) popDraft() } }.exhaustive @@ -704,11 +696,12 @@ class TextComposerViewModel @AssistedInject constructor( } private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { - _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) + _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) viewModelScope.launch { val event = try { block() - TextComposerViewEvents.SlashCommandResultOk + popDraft() + TextComposerViewEvents.SlashCommandResultOk() } catch (failure: Throwable) { TextComposerViewEvents.SlashCommandResultError(failure) } From 61c64a872dc26d17cc6dd869483f1d3ed35028c8 Mon Sep 17 00:00:00 2001 From: Linerly Date: Mon, 18 Oct 2021 22:42:33 +0000 Subject: [PATCH 156/172] Translated using Weblate (Indonesian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 162 +++++++++++----------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index ab86b35e6c..f899be6bff 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -89,7 +89,7 @@ Laporan bug Aplikasi gagal saat terakhir digunakan. Apakah Anda ingin membuka halaman laporan kegagalan\? Gabung di Ruangan - URL Server Identitas + URL server identitas Mulai Panggilan Suara Masuk Buat Akun @@ -135,7 +135,7 @@ Tolak Nanti Kirim Saja - ${app_name} belum diijinkan untuk mengakses kontak lokal + ${app_name} belum diizinkan untuk mengakses kontak lokal Kirim log gangguan Raksasa Kecil @@ -157,8 +157,8 @@ \nBergabung sebagai %1$s atau %2$s suara video - Beberapa fitur tidak dapat digunakan karena aplikasi belum mendapat ijin… - Anda membutuhkan ijin mengundang untuk memulai panggilan massal di ruang ini + Beberapa fitur tidak dapat digunakan karena aplikasi belum mendapat izin… + Anda membutuhkan izin mengundang untuk memulai panggilan massal di ruang ini Panggilan massal tidak dapat diselenggarakan di ruang terenkripsi Jejak Percakapan Gandakan ke clipboard @@ -215,7 +215,7 @@ Panggilan Suara Masuk Panggilan Sedang Berlangsung… Hubungan Media Gagal - Server Identitas: + Server identitas: Tidak dapat memulai kamera panggilan terjawab di tempat lain Ambil gambar atau video @@ -375,7 +375,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan Kirim ulang pesan yang belum terkirim Hapus pesan yang belum terkirim Berkas tidak ditemukan - Anda tidak mempunyai ijin untuk mengirim di ruang ini. + Anda tidak mempunyai izin untuk mengirim di ruang ini. %d pesan baru @@ -437,7 +437,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan Hanya sebutan Diamkan Favorit - Menurunkan prioritas + Turunkan prioritas Percakapan Langsung Tinggalkan Percakapan Lupakan @@ -448,7 +448,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan Syarat & ketentuan Pemberitahuan pihak ketiga Hak Cipta - Kebijakan Pribadi + Kebijakan privasi Gambar Profil Nama Layar Email @@ -565,7 +565,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan Untuk terus menggunakan homeserver %1$s Anda harus membaca dan menyetujui syarat dan ketentuan. Avatar Baca sekarang - Deaktivasi Akun + Nonaktifkan Akun Ini akan mengakibatkan akun Anda tidak dapat digunakan secara permanen. Anda tidak akan dapat masuk dan orang lain tidak dapat mendaftar ulang dengan ID pengguna yang sama. Ini akan mengakibatkan akun Anda keluar dari semua ruang tempat Anda berpartisipasi serta menghapus semua detail akun dari server identitas Anda. Tindakan ini tidak dapat diubah. \n \nMenonaktifkan akun Anda tidak membuat kami melupakan pesan-pesan yang Anda kirim secara default. Jika Anda ingin kami melupakan pesan-pesan Anda, mohon centang kotak berikut. @@ -573,7 +573,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan \nKeterbacaan pesan di Matrix serupa dengan email. Dengan kami melupakan pesan-pesan Anda berarti pesan-pesan yang Anda kirim tidak akan dibagikan kepada pengguna baru ataupun yang belum terdaftar, tetapi pengguna yang terdaftar yang mempunyai mengakses pesan-pesan tersebut masih bisa mengakses salinan mereka. Mohon lupakan semua pesan yang telah saya kirim ketika akun saya dideaktivasi (Peringatan: ini akan mengakibatkan pengguna di masa depan melihat percakapan yang tidak lengkap) Untuk melanjutkan, masukkan kata sandi Anda: - Deaktivasi Akun + Nonaktifkan Akun Mohon masukkan kata sandi Anda. Ruangan ini telah berubah dan tidak lagi aktif. Percakapan berlanjut di sini @@ -643,7 +643,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Lanjutan ID internal ruang ini Alamat - Lab + Uji Coba Ini adalah fitur uji coba dan mungkin rusak tanpa terduga. Hati-hati menggunakannya. Enkripsi Ujung-ke-Ujung Enkripsi Ujung-ke-Ujung aktif @@ -698,13 +698,13 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mendengarkan peristiwa Pemberitahuan pihak ketiga Hak Cipta - Kebijakan Pribadi + Kebijakan privasi Bersihkan cache Bersihkan cache media Pertahankan media - Pengaturan pengguna + Pengaturan Pengguna Pemberitahuan - Pengguna yang diabaikan + Pengguna yang Diabaikan Lainnya Lanjutan Kriptografi @@ -721,16 +721,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tampilkan waktu kirim dalam format 12 jam Bergetar ketika menyebut seorang pengguna Pratinjau media sebelum dikirim - Deaktivasi akun - Deaktivasi akunku + Nonaktifkan akun + Nonaktifkan akun saya Kerahasiaan Notifikasi ${app_name} dapat beroperasi di balik layar untuk mengurus pemberitahuan Anda dengan aman dan rahasia. Ini dapat mempengaruhi masa tahan baterai. - Kabulkan permisi + Beri izin Pilih opsi lain Analitik Kirim data analitik ${app_name} mengumpulkan data analitik anonim dalam upaya kami meningkatkan aplikasi. - Mohon aktifkan analitik untuk membantu kami meningkatkan ${app_name}. + Silakan aktifkan analitik untuk membantu kami meningkatkan ${app_name}. Ya, saya ingin membantu! Mode hemat data Rincian perangkat @@ -743,10 +743,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \nUntuk melanjutkan operasi ini, mohon masukkan kata sandi anda. Otentikasi Kata Sandi: - Serahkan + Kirim Masuk sebagai Homeserver - Server Identitas + Server identitas Antarmuka pengguna Bahasa Pilih bahasa @@ -797,7 +797,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Error Mohon telaah dan terima kebijakan homeserver ini: Panggilan - Gunakan nada dering semula ${app_name} untuk panggilan masuk + Gunakan nada dering bawaan ${app_name} untuk panggilan masuk Nada dering panggilan masuk Pilih nada dering untuk panggilan: Panggilan Video Sedang Berlangsung… @@ -902,16 +902,16 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Kirim pemberitahuan mengetik Beritahu pengguna lain bahwa Anda sedang mengetik. Format markdown - Format pesan menggunakan sintaks markdown sebelum dikirim. Ini mengijinkan format lanjutan seperti menggunakan tanda bintang untuk menunjukkan teks miring. + Format pesan menggunakan sintaks markdown sebelum dikirim. Ini mengizinkan format lanjutan seperti menggunakan tanda bintang untuk menunjukkan teks miring. Tunjukkan tanda telah dibaca Klik tanda telah dibaca untuk daftar yang lebih rinci. Tunjukkan kejadian bergabung dan meninggalkan Undangan, pengeluaran, dan larangan tidak terpengaruh. Tunjukkan kejadian akun - • Server-server yang cocok dengan %s diizinkan. - • Server-server yang cocok dengan %s dilarangkan. - Anda mengatur server ACL untuk ruangan ini. - %s mengatur server ACL untuk ruangan ini. + • Server yang cocok dengan %s diizinkan. + • Server yang cocok dengan %s dilarangkan. + Anda mengatur ACL server untuk ruangan ini. + %s mengatur ACL server untuk ruangan ini. Anda meningkatkan di sini. %s meningkatkan di sini. Anda meningkatkan ruangan ini. @@ -919,69 +919,69 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Anda mengaktifkan enkripsi ujung-ke-ujung (%1$s) %1$s mengaktifkan enkripsi ujung-ke-ujung (%2$s) tidak diketahui (%s). - siapa pun. + siapa saja. semua anggota ruangan. - semua anggota ruangan, dari kapan mereka bergabung. - semua anggota ruangan, dari kapan mereka diundang. + semua anggota ruangan, sejak mereka bergabung. + semua anggota ruangan, sejak mereka diundang. Anda membuat riwayat pesanan masa depan terlihat dengan %1$s %1$s membuat riwayat pesanan masa depan terlihat dengan %2$s Anda membuat riwayat ruangan masa depan terlihat dengan %1$s %1$s membuat riwayat ruangan masa depan terlihat dengan %2$s - Anda mengakhiri panggilan. - %s mengakhiri panggilan. + Anda mengakhiri panggilan ini. + %s mengakhiri panggilan ini. %s mengirimkan data untuk mengatur panggilan. Anda mengirimkan data untuk mengatur panggilan. - %s menjawab panggilan. - Anda menjawab panggilan. - Anda menempatkan panggilan suara. - %s menempatkan panggilan suara. - Anda menempatkan panggilan video. - %s menempatkan panggilan video. + %s menjawab panggilan ini. + Anda menjawab panggilan ini. + Anda melakukan panggilan suara. + %s melakukan panggilan suara. + Anda melakukan panggilan video. + %s melakukan panggilan video. Anda mengubah nama kamar menjadi: %1$s %1$s mengubah nama ruangan menjadi: %2$s - Anda mengubah avatar ruangan - %1$s mengubah avatar ruangan + Anda mengubah avatar ruangan ini + %1$s mengubah avatar ruangan ini Anda mengubah topik menjadi: %1$s %1$s mengubah topik menjadi: %2$s Anda menghapus nama tampilan Anda (sebelumnya adalah %1$s) Anda mengubah nama tampilan Anda dari %1$s ke %2$s - %1$s menghapus nama tampilan mereka (sebelumnya adalah %2$s) - %1$s mengubah nama tampilan mereka dari %2$s ke %3$s + %1$s menghapus nama tampilannya (sebelumnya adalah %2$s) + %1$s mengubah nama tampilannya dari %2$s ke %3$s Anda menetapkan nama tampilan Anda ke %1$s %1$s menetapkan nama tampilannya ke %2$s Anda mengubah avatar Anda %1$s mengubah avatarnya - Anda menarik undangannya %1$s - %1$s menarik undangannya %2$s + Anda menghapus undangannya %1$s + %1$s menghapus undangannya %2$s Anda mencekal %1$s %1$s mencekal %2$s - Anda membatalkan pencekalan %1$s - %1$s membatalkan pencekalan %2$s + Anda menghilangkan pencekalan %1$s + %1$s menghilangkan pencekalan %2$s Anda mengeluarkan %1$s %1$s mengeluarkan %2$s - Anda menolak undangan - %1$s menolak undangan - Anda meninggalkan ruangan - %1$s meninggalkan ruangan - Anda meninggalkan ruangan - %1$s meninggalkan ruangan + Anda menolak undangannya + %1$s menolak undangannya + Anda meninggalkan ruangan ini + %1$s meninggalkan ruangan ini + Anda meninggalkan ruangan ini + %1$s meninggalkan ruangan ini Anda bergabung %1$s bergabung - Anda bergabung ruangan - %1$s bergabung ruangan + Anda bergabung ke ruangan ini + %1$s bergabung ke ruangan ini %1$s mengundang Anda Anda mengundang %1$s %1$s mengundang %2$s Anda membuat diskusi %1$s membuat diskusi - Anda membuat ruangan - %1$s menciptakan ruangan + Anda membuat ruangan ini + %1$s membuat ruangan ini Undangan Anda Undangan %s - Anda mengirim sticker. - %1$s mengirim stiker. - Anda mengirim gambar. - %1$s mengirim gambar. + Anda mengirim sebuah sticker. + %1$s mengirim sebuah stiker. + Anda mengirim sebuah gambar. + %1$s mengirim sebuah gambar. %1$s: %2$s Dioptimalkan untuk real-time ${app_name} akan mengsinkron di latar belakang dengan cara yang akan mempertahankan sumber daya (baterai) yang terbatas. @@ -1021,12 +1021,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mengubah widget Beritahu semuanya Menghapus pesan yang dikirim dari yang lain - Mengubah pengaturan + Ubah pengaturan Role bawaan Anda tidak diizinkan untuk memperbarui peran yang diperlukan untuk mengubah berbagai bagian ruangan Pilih role yang diperlukan untuk mengubah berbagai bagian ruangan Izin - Lihat dan perbarui role yang diperlukan untuk mengubah berbagai bagian ruangan. + Lihat dan perbarui peran yang diperlukan untuk mengubah berbagai bagian ruangan. Izin ruangan Membatalkan pencekalan pengguna akan mengizinkan mereka untuk bergabung ke ruangan. Batalkan pencekalan pengguna @@ -1106,7 +1106,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Panggilan gagal karena konfigurasi server yang salah URL API homeserver Kirim sejarah permintaan pemberian kunci - Spaces + Space Undangan Tampilkan semua ruangan di direktori ruangan, termasuk ruangan dengan konten eksplisit. Tampilkan ruangan dengan konten eksplisit @@ -1433,12 +1433,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Space atau ruangan lain yang mungkin tidak Anda ketahui Space yang Anda tahu berisi ruangan ini Tentukan siapa yang dapat menemukan dan bergabung ruangan ini. - Tap untuk mengedit space + Ketuk untuk mengedit space Pilih space Tentukan space mana yang dapat mengakses ruangan ini. Jika space dipilih, anggotanya dapat menemukan dan bergabung dengan nama Ruangan. Space yang dapat diakses Izinkan anggota space untuk menemukan dan akses. - Anggota Space %s bisa menemukan, pratinjau, dan bergabung. + Anggota space %s bisa menemukan, pratinjau, dan bergabung. Siapa saja di space dengan ruangan ini dapat menemukan dan bergabung. Hanya admin ruang ini yang dapat menambahkannya ke space. Anggota space saja Siapa saja dapat menemukan ruangan ini dan bergabung @@ -1446,7 +1446,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Hanya orang yang diundang yang dapat menemukan dan bergabung Pribadi (Undangan Saja) Pribadi - Setelan akses yang tidak diketahui (%s) + Pengaturan akses yang tidak diketahui (%s) Siapa saja dapat mengetuk ruangan, anggota kemudian dapat menerima atau menolak Tidak dapat mengambil visibilitas direktori ruangan saat ini (%1$s). Publikasikan ruangan ini ke publik di direktori ruangan %1$s\? @@ -1477,7 +1477,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Izinkan tamu untuk bergabung Akses ruangan Perubahan siapa yang dapat membaca riwayat hanya akan berlaku untuk pesan berikutnya di ruang ini. Visibilitas sejarah yang ada tidak akan berubah. - Setelan akun + Pengaturan akun Anda dapat mengelola notifikasi di %1$s. Harap dicatat bahwa sebutan & pemberitahuan keyword tidak tersedia di ruangan terenkripsi di ponsel. Beritahu saya untuk @@ -1491,12 +1491,12 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Terjadi kesalahan saat memverifikasi nomor telepon Anda. Sandi tidak cocok Kelola email dan nomor telepon yang ditautkan ke akun Matrix Anda - Surel dan nomor telepon + Email dan nomor telepon Sandi tidak valid Sandi Perbarui Sandi Terjadi kesalahan saat memverifikasi email Anda. - Aktifkan \'Izinkan integrasi\' di Setelan untuk melakukan ini. + Aktifkan \'Izinkan integrasi\' di Pengaturan untuk melakukan ini. Integrasi dinonaktifkan Pengelola integrasi Berikan izin @@ -1560,8 +1560,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Lainnya Sebutan dan Keyword Notifikasi Bawaan - Tidak Ada - Hanya Sebutan & Keyword + Tidak ada + Hanya sebutan & keyword Mengakhiri panggilan… Tidak ada jawaban Pengguna yang Anda panggil sedang sibuk. @@ -1569,7 +1569,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Panggilan suara dengan %s Panggilan video dengan %s Panggilan berdering… - Spaces + Space Pelajari Lebih Lanjut Kami mengirimi Anda email konfirmasi ke %s, mohon periksa email Anda dan klik tautan konfirmasi Opsi penemuan akan muncul setelah Anda menambahkan email. @@ -1745,7 +1745,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Surel yang dapat ditemukan Saat ini Anda tidak menggunakan server identitas. Untuk menemukan dan dapat ditemukan oleh kontak yang Anda kenal, atur salah satu di bawah ini. Saat ini Anda menggunakan %1$s untuk menemukan dan dapat ditemukan oleh kontak yang Anda kenal. - Ganti server identitas + Ubah server identitas Atur server identitas Putuskan server identitas Server identitas @@ -1794,9 +1794,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Terima kasih, saran Anda telah dikirim Terima kasih, masukan Anda telah dikirim Anda dapat menghubungi saya jika Anda memiliki pertanyaan lanjutan - Anda menggunakan versi beta space. Masukan Anda akan membantu menginformasikan versi berikutnya. Platform dan nama pengguna Anda akan dicatat untuk membantu kami menggunakan masukan Anda sebanyak yang kami bisa. + Anda menggunakan space versi beta. Masukan Anda akan membantu menginformasikan versi berikutnya. Platform dan nama pengguna Anda akan dicatat untuk membantu kami menggunakan masukan Anda sebanyak yang kami bisa. Masukan - Masukan spaces + Masukan tentang space Saran gagal dikirim (%s) Jelaskan saran Anda di sini Masukkan saran Anda di bawah. @@ -2215,7 +2215,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Rageshake Mode pengembang akan mengaktifkan fitur tersembunyi dan mungkin juga membuat aplikasinya kurang stabil. Untuk pengembang saja! Mode pengembang - Pengaturan lanjutan + Pengaturan Lanjutan Lihat semua sesi saya Sinkronisasi Awal… Deskripsi terlalu pendek @@ -2366,7 +2366,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. \nAnda tidak memiliki izin untuk menambahkan ruangan. Space ini belum ada ruangan Mohon kontak admin homeserver Anda untuk informasi lanjut - Sepertinya homeserver Anda belum mendukung Spaces + Sepertinya homeserver Anda belum mendukung fitur space Merasa eksperimental\? \nAnda dapat menambahkan space yang sudah ada ke space. Semua ruangan yang Anda berada akan ditampilkan juga di Beranda. @@ -2402,7 +2402,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. %d orang yang Anda tahu telah bergabung - Selamat datang ke %1$s, %2$s. + Selamat datang di %1$s, %2$s. Anda tidak berada di ruangan apapun saat ini. Di bawah adalah ruangan yang disarankan, tetapi Anda bisa mencari lebih banyak dengan tombol hijau di bawah kanan. Penjelajahan (%s) Selesaikan penyiapan @@ -2452,7 +2452,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Pastikan orang yang tepat memiliki akses ke %s. Anda dapat mengubahnya nanti. Dengan siapa Anda bekerja\? Untuk bergabung ke space yang sudah ada, Anda membutuhkan undangan ke space itu. - Anda bisa mengubahnya nanti + Anda dapat mengubahnya nanti Tipe space apa yang Anda ingin buat\? Space adalah cara baru untuk mengelompokkan ruangan dan pengguna Space privat Anda @@ -2470,9 +2470,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Mengirim Meningkatkan ruangan ke versi baru Tinggalkan ruangan dengan ID yang diberikan (atau ruangan saat ini jika null) - Bergabung ke Space dengan ID yang diberikan - Tambah ke Space yang dicantumkan - Buat Space + Bergabung ke space dengan ID yang diberikan + Tambah ke space yang dicantumkan + Buat sebuah Space Konten peristiwa Peristiwa keadaan dikirim! Peristiwa dikirim! From 5cb55cb0d304df53f4a1b6eda4867e6b479a9366 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 18 Oct 2021 14:32:45 +0000 Subject: [PATCH 157/172] Translated using Weblate (Italian) Currently translated at 99.8% (2669 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 04d01e8f95..8d323c56e5 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -3055,4 +3055,7 @@ Imposta il nome della stanza Smetti di ignorare un utente, mostrando i suoi messaggi successivi Ignora un utente, non mostrandoti i suoi messaggi + Non disponibile + Offline + Online \ No newline at end of file From bda95fcc5ef9bfb6b68ba0650c1f382944d6b150 Mon Sep 17 00:00:00 2001 From: Viacheslav Raskulin Date: Mon, 18 Oct 2021 15:11:42 +0000 Subject: [PATCH 158/172] Translated using Weblate (Russian) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index bfc82d9a95..3a2491850d 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -349,7 +349,7 @@ Комнаты Фильтр названия комнаты - Фильтр избраного + Фильтр избранного Фильтр людей Фильтр названия комнаты @@ -425,7 +425,7 @@ Введен некорректный номер телефона Этот адрес электронной почты уже используется. Отсутствует email - Отстутсвует номер телефона + Отсутствует номер телефона Отсутствует номер телефона или email Токен не действителен Пароли не совпадают @@ -468,7 +468,7 @@ Чтение списка вступивших - Откравить как + Отправить как Оригинал Крупный Средний @@ -769,7 +769,7 @@ Все у кого есть ссылка на комнату, кроме гостей Все у кого есть ссылка на комнату, включая гостей - Забаненые пользователи + Забаненные пользователи Дополнительно Внутренний ID комнаты @@ -807,7 +807,7 @@ Curve25519 идентификационный ключ Необходим отпечаток Ed25519-ключа Алгоритм - ID сесии + ID сессии Ошибка дешифровки Информация о сессии отправителя Публичное имя @@ -1284,7 +1284,7 @@ Показывать события аккаунта Включает изменения аватара и отображаемого имени. Необходимо минимизировать влияние на фоновое соединение для надёжности уведомлений. -\nНа следующем экране вам будет предложено разрешить Райоту всегда работать в фоновом режиме, пожалуйста, примите. +\nНа следующем экране вам будет предложено разрешить ${app_name} всегда работать в фоновом режиме, пожалуйста, примите. Использовать системную камеру вместо камеры Element. Показать информацию %1$s: @@ -1494,7 +1494,7 @@ \nЕсли вы не открывали новую сессию - проигнорируйте этот запрос. Подтвердить Поделиться - Запрос поделится ключем + Запрос поделится ключом Игнорировать Ошибка отклика сервера Дополнить параметры сервера @@ -1505,7 +1505,7 @@ Начать проверку Входящий запрос о проверке Проверьте эту сессию, чтобы отметить её как доверенную. Доверие сессиям партнеров позволяет быть уверенным в надёжности сквозного шифрования сообщений. - Потдверждение этой сессии пометит её доверенной для вас и вашего партнёра. + Подтверждение этой сессии пометит её доверенной для вас и вашего партнёра. Подтвердите эту сессию, убедившись, что следующие смайлики появляются на экране партнера Подтвердите эту сессию, убедившись, что следующие цифры появляются на экране партнера Вы получили входящий запрос на подтверждение. @@ -2028,7 +2028,7 @@ \n \nВы можете отменить это действие в любое время в общих настройках. Перестать игнорировать пользователя - Если вы перестаните игнорировать этого пользователя, то все его сообщения вновь будут отображаться. + Если вы перестанете игнорировать этого пользователя, то все его сообщения вновь будут отображаться. Отменить приглашение Вы уверены, что хотите отменить приглашение для этого пользователя\? Выгнать пользователя @@ -2124,7 +2124,7 @@ Вы отменили %s принято Вы приняли - Подтверждениие отправлено + Подтверждение отправлено Запрос на подтверждение Подтвердите эту сессию Подтверждение вручную @@ -2175,7 +2175,7 @@ Сравните уникальные эмодзи, убедившись, что они появились в том же порядке. Сравните код с тем, который отображается на экране другого пользователя. Сообщения с этим пользователем полностью зашифрованы и не могут быть прочитаны третьими лицами. - Ваша новая сесия теперь подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут считать её надежной. + Ваша новая сессия теперь подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут считать её надежной. Кросс-подпись Кросс-подпись включена \nПриватные ключи хранятся на устройстве. @@ -2220,7 +2220,7 @@ Настроить безопасное резервное копирование Безопасное резервное копирование Эта сессия является доверенной для безопасного обмена сообщениями, потому что вы проверили её: - Подтвердите эту сессию, чтобы пометить её доверенной и предоставить ей доступ к зашифрованным сообщениям. Если вы не входили в эту сессю, ваша учетная запись может быть скомпрометирована: + Подтвердите эту сессию, чтобы пометить её доверенной и предоставить ей доступ к зашифрованным сообщениям. Если вы не входили в эту сессию, ваша учетная запись может быть скомпрометирована: Другие пользователи могут не доверять ему Завершите настройку безопасности Верифицировать @@ -2810,7 +2810,7 @@ Настройки комнаты Покинуть текущую конференцию и перейти к другой\? Версия комнаты - Показать все команты в списке комнат, в том числе с чувствительным содержанием. + Показать все комнаты в списке комнат, в том числе с чувствительным содержанием. Показать комнаты с деликатным содержанием Список комнат Новое значение @@ -2852,7 +2852,7 @@ Управление комнатами и пространствами Отметить как не рекомендуется Отметить как рекомендуется - Предложенно + Предложено Сделайте это пространство публичным Управление комнатами Ищете кого-то не в %s\? @@ -2924,7 +2924,7 @@ Пространства - это новый способ группировки комнат и людей Ваше приватное пространство Ваше публичное пространство - Добавить простанство + Добавить пространство Приватное пространство Публичное пространство Обновляет комнату до новой версии From 803d1458921e9623678fa0e18b3b50dd5c9edc34 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 19 Oct 2021 07:29:48 +0000 Subject: [PATCH 159/172] Translated using Weblate (Swedish) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index f8ac8eba58..5ff62fa1eb 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -747,7 +747,7 @@ Byt nätverk Alla gemenskaper Allmänt - Inställningar + Alternativ Namn eller ID (#example:matrix.org) Du kommer inte att verifiera %1$s (%2$s) om du avbryter nu. Börja igen i hens användarprofil. Meddelanden i det här rummet är totalsträckskrypterade. Lär dig mer och verifiera användare i deras profiler. @@ -2778,8 +2778,8 @@ Känner du dig äventyrlig\? \nDu kan lägga till existerande utrymmen till ett utrymme. Lägg till rum - Du är den enda administratören av detta utrymme. Att lämna det betyder att ingen har kontroll över det. - Du kommer inte att kunna gå med igen om du inte är inbjuden igen. + Du är den enda administratören av detta utrymme. Om du lämnar det kommer ingen att ha kontroll över det. + Du kommer inte att kunna gå med igen om du inte bjuds in igen. Du är den enda personen här. Om du lämnar så kommer ingen kunna gå med i framtiden, inklusive du. Bjud in till %s Den här funktionen är i beta @@ -2821,13 +2821,13 @@ Tryck på din inspelning för att stoppa eller lyssna %1$d lämnade Håll in för att spela in, släpp för att skicka - Ta bort inspelningen + Radera inspelning Spelar in röstmeddelande Pausa röstmeddelande Spela röstmeddelande Röstmeddelandelås Svep för att avbryta - Börja röstmeddelande + Spela in röstmeddelande Tyvärr så inträffade ett fel vid försök att gå med i: %s Uppgradera till rekommenderad rumsversion Det är rummet kör rumsversion %s, vilken den hör hemservern har markerat som instabil. @@ -2986,7 +2986,7 @@ Vilka är dina lagkamrater\? Lägg till till det angivna utrymmet Sluta spela in - Lägg till ett mellanslag till alla utrymmen du hanterar. + Lägg till ett utrymme till alla utrymmen du hanterar. Lägg till befintliga utrymmen Lägg till befintliga rum Välj saker att lämna @@ -2995,9 +2995,9 @@ Du kommer att lämna alla rum och utrymmen i %s. Lämna alla rum och utrymmen Är du säker på att du vill lämna %s\? - Discovery (%s) + Upptäckt (%s) Slutför konfigurationen - Se till att rätt personer har tillgång till %s företag. Du kan bjuda in mer senare. + Se till att rätt personer har tillgång till %s företag. Du kan bjuda in fler senare. Bjud in med e-post, hitta kontakter och mer… Slutför inställning av upptäckbarhet. Du använder för närvarande ingen identitetsserver. För att bjuda in teammedlemmar och kunna upptäckas av dem, konfigurera en nedan. From 497d053c5dddf9bb161d11f160cfa467fd84d6a8 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 18 Oct 2021 02:13:47 +0000 Subject: [PATCH 160/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2672 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index f8ca44ab7a..66120d9429 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2946,4 +2946,7 @@ 設定聊天室名稱 停止忽略使用者,繼續顯示他們的訊息 忽略使用者,為您隱藏他們的訊息 + 不可用 + 離線 + 線上 \ No newline at end of file From 16a8cf6d5198e21c74d5c1a8bf9e06f9f404cca4 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 18 Oct 2021 14:35:15 +0000 Subject: [PATCH 161/172] Translated using Weblate (Italian) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/it-IT/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40103030.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40103030.txt b/fastlane/metadata/android/it-IT/changelogs/40103030.txt new file mode 100644 index 0000000000..e5c12d2b5e --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: rese visibili le informative dei server d'identità nelle impostazioni. Rimosso temporaneamente il supporto per Android Auto. +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/fastlane/metadata/android/it-IT/full_description.txt index dd7716ffbf..f0e4c04477 100644 --- a/fastlane/metadata/android/it-IT/full_description.txt +++ b/fastlane/metadata/android/it-IT/full_description.txt @@ -37,3 +37,6 @@ Messaggi, chiamate audio e video, condivisione file e schermo, un vasto numero d Riprendi da dove ti eri fermato Resta in contatto ovunque tu sia con la cronologia dei messaggi sincronizzata tra tutti i tuoi dispositivi e in rete su https://app.element.io + +Open source +Element Android è un progetto open source, ospitato su GitHub. Segnala errori e/o contribuisci al suo sviluppo su https://github.com/vector-im/element-android From 1d831b3a1b7464bced0c8b8d70cac32190be005f Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 18 Oct 2021 02:11:58 +0000 Subject: [PATCH 162/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/zh-TW/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40103030.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40103030.txt b/fastlane/metadata/android/zh-TW/changelogs/40103030.txt new file mode 100644 index 0000000000..7531d1d4a2 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40103030.txt @@ -0,0 +1,2 @@ +此版本中的主要變動:讓身份伺服器政策在設定中可見。暫時移除 Android Auto 支援。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/zh-TW/full_description.txt b/fastlane/metadata/android/zh-TW/full_description.txt index 90c0eb4c4c..eda511c4af 100644 --- a/fastlane/metadata/android/zh-TW/full_description.txt +++ b/fastlane/metadata/android/zh-TW/full_description.txt @@ -37,3 +37,6 @@ Element 透過不同的方式讓您掌控一切: 從上次離開的地方開始 無論您身在何處,都可以透過在您所有裝置與網頁 https://app.element.io 間完全同步的訊息歷史保持聯絡 + +開放原始碼 +Android 版的 Element 是開放原始碼專案,託管於 GitHub 上。請在 https://github.com/vector-im/element-android 上回報臭蟲及/或貢獻其開發 From c056dc27ca1f5835835bb61cc09006fb280ef4ce Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 18 Oct 2021 05:20:25 +0000 Subject: [PATCH 163/172] Translated using Weblate (Czech) Currently translated at 100.0% (34 of 34 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40103030.txt | 2 ++ fastlane/metadata/android/cs-CZ/full_description.txt | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40103030.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40103030.txt b/fastlane/metadata/android/cs-CZ/changelogs/40103030.txt new file mode 100644 index 0000000000..8faffd36ed --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40103030.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Umožňuje v nastavení zviditelnit zásady serveru identit. Dočasně odstraňuje podporu pro Android Auto. +Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/cs-CZ/full_description.txt b/fastlane/metadata/android/cs-CZ/full_description.txt index 6732b33fe3..a74c58b413 100644 --- a/fastlane/metadata/android/cs-CZ/full_description.txt +++ b/fastlane/metadata/android/cs-CZ/full_description.txt @@ -37,3 +37,6 @@ Zprávy, hlasové a videohovory, sdílení souborů, sdílení obrazovky a celá Navažte tam, kde jste skončili Zůstaňte v kontaktu, ať jste kdekoli, díky plně synchronizované historii zpráv ve všech zařízeních a na webu https://app.element.io + +Open source +Element Android je projekt s otevřeným zdrojovým kódem, který je hostován na GitHubu. Nahlaste prosím chyby a přispějte k jeho vývoji na adrese https://github.com/vector-im/element-android From 097694f6ef995189e5b15c95e033005baa945128 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 17:31:51 +0200 Subject: [PATCH 164/172] Make MegolmBackupAuthData.signatures optional for robustness --- changelog.d/4162.bugfix | 1 + .../internal/crypto/keysbackup/DefaultKeysBackupService.kt | 6 +++--- .../crypto/keysbackup/model/MegolmBackupAuthData.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 changelog.d/4162.bugfix diff --git a/changelog.d/4162.bugfix b/changelog.d/4162.bugfix new file mode 100644 index 0000000000..833ed2f1b8 --- /dev/null +++ b/changelog.d/4162.bugfix @@ -0,0 +1 @@ +Make MegolmBackupAuthData.signatures optional for robustness \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index fe2de6aa9c..b20168eaa3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -410,7 +410,7 @@ internal class DefaultKeysBackupService @Inject constructor( val keysBackupVersionTrust = KeysBackupVersionTrust() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isEmpty()) { + if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") return keysBackupVersionTrust } @@ -478,7 +478,7 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures[userId].orEmpty().toMutableMap() + val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap() if (trust) { // Add current device signature @@ -497,7 +497,7 @@ internal class DefaultKeysBackupService @Inject constructor( // Create an updated version of KeysVersionResult val newMegolmBackupAuthData = authData.copy() - val newSignatures = newMegolmBackupAuthData.signatures.toMutableMap() + val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() newSignatures[userId] = myUserSignatures val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt index 54b92546e9..17c895762c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt @@ -51,7 +51,7 @@ data class MegolmBackupAuthData( * userId -> (deviceSignKeyId -> signature) */ @Json(name = "signatures") - val signatures: Map> + val signatures: Map>? = null ) { fun toJsonDict(): JsonDict { From 97464969ea49ba8c78a15664af1f86cefb35b5cb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 19:04:32 +0200 Subject: [PATCH 165/172] Small formatting --- dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index be6ce3f04f..a487b36c93 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -126,11 +126,11 @@ ext.libs = [ 'jjwtImpl' : "io.jsonwebtoken:jjwt-impl:$jjwt", 'jjwtOrgjson' : "io.jsonwebtoken:jjwt-orgjson:$jjwt" ], - vanniktech: [ + vanniktech : [ 'emojiMaterial' : "com.vanniktech:emoji-material:$vanniktechEmoji", 'emojiGoogle' : "com.vanniktech:emoji-google:$vanniktechEmoji" ], - apache:[ + apache : [ 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" ], tests : [ From 85983562fa968387fe1db30c4f51ee59b30b4ee0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 19 Oct 2021 19:06:27 +0200 Subject: [PATCH 166/172] No need to add explicit dependencies on stdlib, this is added by the gradle plugin since 1.4 https://kotlinlang.org/docs/whatsnew14.html#dependency-on-the-standard-library-added-by-default --- attachment-viewer/build.gradle | 1 - dependencies.gradle | 2 -- matrix-sdk-android-flow/build.gradle | 1 - matrix-sdk-android/build.gradle | 2 -- multipicker/build.gradle | 1 - vector/build.gradle | 1 - 6 files changed, 8 deletions(-) diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 064f497dc7..02fbfc794c 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -53,7 +53,6 @@ dependencies { implementation libs.rx.rxKotlin implementation libs.rx.rxAndroid - implementation libs.jetbrains.kotlinStdlib implementation libs.androidx.core implementation libs.androidx.appCompat implementation libs.androidx.recyclerview diff --git a/dependencies.gradle b/dependencies.gradle index a487b36c93..7fa42a666f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -37,8 +37,6 @@ ext.libs = [ 'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin" ], jetbrains : [ - 'kotlinStdlibJdk7' : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin", - 'kotlinStdlib' : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin", 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" diff --git a/matrix-sdk-android-flow/build.gradle b/matrix-sdk-android-flow/build.gradle index fd2e2e0824..ea43ce20c8 100644 --- a/matrix-sdk-android-flow/build.gradle +++ b/matrix-sdk-android-flow/build.gradle @@ -35,7 +35,6 @@ dependencies { implementation project(":matrix-sdk-android") implementation libs.androidx.appCompat - implementation libs.jetbrains.kotlinStdlibJdk7 implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid implementation libs.androidx.lifecycleLivedata diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 43ca243ec5..b547ae037a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -100,8 +100,6 @@ static def gitRevisionDate() { } dependencies { - - implementation libs.jetbrains.kotlinStdlibJdk7 implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid diff --git a/multipicker/build.gradle b/multipicker/build.gradle index 437499df7b..bb98a2f852 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -38,7 +38,6 @@ android { } dependencies { - implementation libs.jetbrains.kotlinStdlibJdk7 implementation libs.androidx.appCompat implementation libs.androidx.fragmentKtx implementation libs.androidx.exifinterface diff --git a/vector/build.gradle b/vector/build.gradle index d06779d61c..31a4f1639c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -340,7 +340,6 @@ dependencies { implementation project(":library:ui-styles") implementation 'androidx.multidex:multidex:2.0.1' - implementation libs.jetbrains.kotlinStdlibJdk7 implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid From d764bb659f58e30f6adce59e1d7816f1e10f68f5 Mon Sep 17 00:00:00 2001 From: DUCKCHI Date: Tue, 19 Oct 2021 20:13:01 +0000 Subject: [PATCH 167/172] Translated using Weblate (Korean) Currently translated at 48.3% (1292 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ko/ --- vector/src/main/res/values-ko/strings.xml | 347 ++++------------------ 1 file changed, 54 insertions(+), 293 deletions(-) diff --git a/vector/src/main/res/values-ko/strings.xml b/vector/src/main/res/values-ko/strings.xml index dbd48a6c67..3adf75e722 100644 --- a/vector/src/main/res/values-ko/strings.xml +++ b/vector/src/main/res/values-ko/strings.xml @@ -1,10 +1,9 @@ - + %1$s: %2$s %s님의 초대 %1$s님이 사진을 보냈습니다. %1$s님이 스티커를 보냈습니다. - %1$s님이 %2$s님을 초대했습니다 %1$s님이 당신을 초대했습니다 %1$s님이 참가했습니다 @@ -32,11 +31,9 @@ 알 수 없음 (%s). %1$s님이 종단간 암호화를 켰습니다 (%2$s) %s님이 방을 업그레이드했습니다. - %1$s님이 VoIP 회의를 요청했습니다 VoIP 회의가 시작했습니다 VoIP 회의가 끝났습니다 - (아바타도 변경됨) %1$s님이 방 이름을 삭제했습니다 %1$s님이 방 주제를 삭제했습니다 @@ -47,34 +44,23 @@ %1$s님이 프로필 %2$s을(를) 업데이트했습니다 %1$s님이 %2$s님에게 방 초대를 보냈습니다 %1$s님이 %2$s의 초대를 수락했습니다 - ** 암호를 복호화할 수 없음: %s ** 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. - 검열할 수 없습니다 메시지를 보낼 수 없습니다 - 사진 업로드에 실패했습니다 - 네트워크 오류 Matrix 오류 - 현재 빈 방에 다시 들어갈 수 없습니다. - 이메일 주소 전화번호 - %s에서 초대함 방 초대 - %1$s님과 %2$s님 - %1$s님 외 %2$d명 - 빈 방 - 초기 동기화: \n계정 가져오는 중… 초기 동기화: @@ -91,15 +77,12 @@ \n커뮤니티 가져오는 중 초기 동기화: \n계정 데이터 가져오는 중 - 메시지 보내는 중… 전송 대기 열 지우기 - %1$s님이 %2$s님에게 방에 참가하라고 보낸 초대를 취소했습니다 밝은 테마 어두운 테마 검정 테마 - 동기화 중… 메시지 @@ -107,11 +90,8 @@ 기록 버그 신고 스티커 보내기 - 제 3자 라이선스 - 불러오는 중… - 취소 저장 @@ -132,14 +112,13 @@ 영상 전화를 걸 수 없습니다, 나중에 다시 시도해주세요 권한이 없어졌기 때문에, 일부 기능이 빠졌을 수 있습니다… - 권한이 없어졌기 때문에, 이 행동을 할 수 없습니다. + 현재 권한이 없기 때문에 이 작업을 수행할 수 없습니다. 전화를 걸 수 없습니다 기기 정보 무시하고 보내기 또는 초대 오프라인 - 나가기 활동 로그아웃 @@ -152,22 +131,17 @@ 닫기 클립보드에 복사되었습니다 비활성화 - 확인 경고 오류 - 즐겨찾기 사람 커뮤니티 - 방 이름 필터 시스템 알림 - 결과 없음 - 로그 보내기 충돌 로그 보내기 @@ -184,21 +158,17 @@ 홈서버 URL ID 서버 URL 검색 - 새 대화 시작하기 음성 통화 시작하기 영상 통화 시작하기 - 정말로 %s님과 새 대화를 시작하시겠습니까\? 정말 음성통화를 시작하시겠어요? 정말 영상통화를 시작하시겠어요? - 파일 보내기 스티커 보내기 사진이나 영상 찍기 사진 찍기 영상 찍기 - 로그인 계정 만들기 제출하기 @@ -207,17 +177,14 @@ 비밀번호 새 비밀번호 사용자 이름 - 서비스 초기화 중 소리로 알림 소리 없이 알림 - 구성원 정보 커뮤니티 정보 키 백업 키 백업하기 기기 확인 - 키 백업이 끝나지 않았습니다, 기다려주세요… 지금 로그아웃하면 암호화된 메세지가 사라집니다 키 백업하기 @@ -232,19 +199,15 @@ %d명의 사용자 - 초대 커뮤니티 최근에 애플리케이션이 충돌한 것 같습니다. 충돌 보고서를 열까요\? 읽음 - 사용자 이름 음성 보내기 - 스티커팩이 하나도 없습니다. \n \n뭐라도 추가할까요\? - 로그인 화면으로 돌아가기 이메일 주소 이메일 주소 (선택) @@ -265,14 +228,12 @@ 비밀번호가 초기화되었습니다. \n \n모든 기기에서 로그아웃되고 알림도 가지 않을 거에요. 다시 알림을 받으려면, 각 기기에 다시 로그인하세요. - 이벤트 청취하기 키 백업이 진행 중입니다. 지금 로그아웃하면 암호화된 메시지에 접근할 수 없습니다. 암호화된 메시지에 대한 접근을 읽지 않도록 모든 장치에서 보안 키 백업이 활성화되어 있어야 합니다. 암호화된 메시지를 원하지 않습니다 키 백업 중… 로그아웃하기 전에 키를 백업하지 않으면 암호화된 메시지에 접근할 수 없습니다. - 말하기 지우기 @@ -286,13 +247,11 @@ 수락하기 중단 무시 - 세계 검색 즐겨찾기 필터 사람 필터 방 이름 필터 커뮤니티 이름 필터 - 초대 중요하지 않음 대화 @@ -304,18 +263,14 @@ 방 없음 이용할 수 있는 공공 방이 없음 그룹 없음 - 문제를 진단하기 위해, 이 클라이언트의 로그는 버그 보고서와 함께 전송됩니다. 이 버그 보고서에는 로그와 스크린샷이 포함되면 공개적으로 표시되지 않습니다. 위의 텍스트만 보내려면 다음을 선택 해제하세요: 좌절감에 휴대 전화를 흔들고 있는 것 같네요. 버그 보고서 화면을 열어보겠습니까\? 분노의 흔들기로 버그 신고하기 - 진행 (%s%%) - 여기로 보내기 방 들어가기 계속… 죄송합니다, 이 작업을 완료하기 위한 외부 애플리케이션이 없습니다. - 통합 인증으로 로그인 재 설정 이메일 보내기 사용자 이름은 문자, 숫자, 점, 하이픈 및 밑줄만 포함할 수 있습니다 @@ -337,7 +292,6 @@ 이메일 %s(으)로 전송했습니다. 이메일에 포함된 링크를 들어갔다면, 아래를 클릭해주세요. 이메일 주소를 확인할 수 없습니다: 이메일에 있는 링크를 클릭했는 지 확인하세요 이 홈서버의 규칙을 숙지한 후 수락하세요: - URL은 http[s]://로 시작해야 합니다 로그인할 수 없음: 네트워크 오류 로그인할 수 없음 @@ -354,44 +308,32 @@ 너무 많은 요청을 보냈습니다 이 사용자 이름은 이미 사용 중입니다 이메일 링크를 아직 클릭하지 않았습니다 - 다른 기기에서 온 다시 요청된 암호 키. - 키 요청을 보냈습니다. - 요청 보냄 다른 기기에서 ${app_name}을 설치해서 메시지를 암호화하고 이 기기로 키를 보내도록 합니다. - 읽은 기록 읽기 - 그룹 목록 - %d명의 구성원 변경 사항 - 이렇게 보내기 원본 크게 중간 작게 - 다운로드를 취소하겠습니까\? 업로드를 취소하겠습니까\? %d초 %1$d분 %2$d초 - 어제 오늘 - 방 이름 방 주제 - 전화 수신 전화에 ${app_name} 기본 벨소리를 사용합니다 수신 전화 벨소리 전화에 사용할 벨소리를 선택하세요: - 전화 전화 연결됨 전화 연결 중… @@ -402,15 +344,12 @@ 수신 음성 통화 전화 진행 중… 영상 통화 진행 중… - 상대방이 전화를 받지 못했습니다. 미디어 연결 실패 카메라를 초기화할 수 없습니다 다른 곳에서 전화 응답 - 사진이나 영상 찍기 영상을 촬영할 수 없음 - 정보 첨부 파일을 보내고 저장하려면 ${app_name}은 영상과 사진 보관함에 접근하는 권한이 필요합니다. \n @@ -430,32 +369,26 @@ "${app_name}은 당신의 연락처를 확인하여 이메일과 전화번호를 기반으로 다른 Matrix 사용자를 찾을 수 있습니다. \n \n이런 이유로 연락처를 공유하는 것을 허용하겠습니까\?" - 죄송합니다. 권한이 없어서, 작업이 수행되지 않았습니다 - 저장됨 다운로드 폴더에 저장하겠습니까\? 아니오 계속 - 제거 참가 미리보기 받지 않기 - 구성원 목록 헤더 열기 동기화 중… 읽지 않은 첫 부분으로 이동하기. - %s님이 이 방알 초대했습니다 이 초대장은 %s님이 보냈습니다, 이 계정과는 관련이 없습니다. \n다른 계정으로 로그인하거나, 이 이메일을 계정에 추가할 수 있습니다. %s 방으로 접근하려고 했습니다. 토론에 참여하기 위해 참가하는 것입니까\? 이 방의 미리보기입니다. 방 상호작용이 비활성화됩니다. - 새 대화 구성원 추가 @@ -465,7 +398,6 @@ %d명의 구성원 1명의 구성원 - %d초 @@ -478,23 +410,19 @@ %d일 - 방 떠나기 방을 떠나겠습니까\? 이 대화에서 %s을(를) 삭제하겠습니까\? 만들기 - 온라인 오프라인 휴식 %1$s 상태 %2$s 전 %1$s 상태였음 - 관리자 도구 전화 다이렉트 대화 기기 - 초대 이 방 떠나기 이 방에서 삭제하기 @@ -511,24 +439,19 @@ 기기 목록 보이기 사용자를 자신과 동일한 권한 등급으로 승격시키는 것은 취소할 수 없습니다. \n확신합니까\? - 이 사용자를 이 대화에서 출입 금지하겠습니까\? 이유 - %s님을 이 대화에 초대하겠습니까\? "%1$s님, " %1$s님과 %2$s님 %1$s %2$s님 - ID로 초대 로컬 연락처 (%d) 사용자 목록 (%s) Matrix 사용자만 - ID로 사용자 초대 이메일 주소나 Matrix ID를 입력해주세요 이메일 혹은 Matrix ID - 검색 %s님이 입력 중… %1$s님과 %2$s님이 입력 중… @@ -549,7 +472,6 @@ %d개의 새 메시지 - 신뢰함 신뢰하지 않음 로그아웃 @@ -561,7 +483,6 @@ 인증서가 휴대 전화가 신뢰하는 인증서에서 변경되었습니다. 이것은 매우 비정상적입니다. 새 인증서에 수락하지 않는 것을 권합니다. 이전 신뢰하던 인증서에서 신뢰하지 않는 인증서로 변경되었습니다. 서버가 인증서를 새로 갱신했을 수 있습니다. 예상되는 핑거프린트는 서버 관리자에게 문의하세요. 서버 관리자가 위의 핑거프린트와 일치하는 것을 게시하는 경우에만 인증서를 수락할 수 있습니다. - 방 세부 사항 사람 파일 @@ -572,14 +493,12 @@ 잘못된 ID입니다. 이메일 주소나 \'@localpart:domain\'와 같은 Matrix ID이어야 합니다 초대받음 참가함 - 이 내용을 신고하는 이유 이 사용자의 모든 메시지를 숨기겠습니까\? \n \n이 동작은 앱을 다시 시작하며 일정 시간이 걸릴 수 있습니다. 업로드 취소 다운로드 취소 - 검색 방 구성원 필터 결과 없음 @@ -587,7 +506,6 @@ 메시지 사람 파일 - 참가 목록 즐겨찾기 @@ -599,7 +517,6 @@ 방 참가하기 방 참가하기 방 ID나 방 별칭을 입력 - 목록 찾기 %d개의 방 @@ -608,7 +525,6 @@ %2$s 검색 결과로 %1$s개의 방을 찾음 목록 검색 중… - 모든 메시지 (소리) 모든 메시지 언급만 @@ -619,7 +535,6 @@ 대화 떠나기 잊기 홈 화면에 단축 아이콘 추가 - 메시지 설정 버전 @@ -628,7 +543,6 @@ 제 3자 고지 저작권 개인 정보 정책 - 프로필 사진 표시 이름 이메일 @@ -637,11 +551,8 @@ 전화번호 추가 애플리케이션 정보 시스템 설정에서 애플리케이션 정보를 표시하세요. - - 고급 알림 설정 이벤트 별 알림 중요도 - 알림 개인 정보 알림 문제 해결 문제 해결 진단 @@ -650,37 +561,31 @@ 기본 진단은 괜찮습니다. 여전히 알림을 받지 못하고 있다면, 버그를 신고해서 우리가 조사할 수 있도록 도와주세요. 1개 이상의 테스트가 실패했습니다, 제안된 수정을 시도하세요. 1개 이상의 테스트가 실패했습니다, 버그를 신고해서 우리가 조사할 수 있도록 도와주세요. - 시스템 설정. 알림이 시스템 설정에서 켜집니다. 알림이 시스템 설정에서 꺼집니다. \n시스템 설정을 확인해주세요. 설정 열기 - 계정 설정. 알림이 당신의 계정에서 켜집니다. 알림이 당신의 계정에서 꺼집니다. \n계정 설정을 확인해주세요. 켜기 - 기기 설정. 알림이 이 기기에서 켜집니다. 알림이 이 기기에서 허용되지 않습니다. \n${app_name} 설정을 확인해주세요. 켜기 - 맞춤 설정. 일부 메시지 유형은 조용하게 설정되어 있습니다 (소리가 없는 알림을 생성합니다). 일부 알림이 맞춤 설정에서 꺼집니다. 맞춤 규칙을 불러오는 데 실패했습니다, 다시 시도해주세요. 설정 확인 - Play 서비스 확인 Google Play 서비스 APK는 최신 버전입니다. ${app_name}은 Google Play 서비스를 사용해 푸시 메시지를 보내지만 올바르게 설정되지 않은 모양입니다: \n%1$s Play 서비스 고치기 - Firebase 토큰 FCM 토큰이 성공적으로 검색되었습니다: \n%1$s @@ -693,27 +598,22 @@ [%1$s] \n이 오류는 ${app_name}의 통제 밖에 있습니다. 휴대 전화에 Google 계정이 없습니다. 계정 관리자를 열어 Google 계정을 추가하세요. 계정 추가 - 토큰 등록 FCM 토큰이 성공적으로 홈서버에 등록되었습니다. FCM 토큰을 홈서버에 등록 실패: \n%1$s - 알림 서비스 알림 서비스가 실행 중입니다. 알림 서비스가 실행 중이 아닙니다. \n애플리케이션을 다시 실행해보세요. 서비스 시작 - 알림 서비스 자동 다시 시작 서비스가 종료되고 자동으로 다시 시작되었습니다. 서비스 다시 시작에 실패함 - 부팅 시 시작 기기가 다시 시작되면 서비스가 시작됩니다. 기기가 다시 시작될 때 서비스가 시작되지 않습니다, 다시 시작한 후 ${app_name}을 한 번이라도 열지 않으면 알림을 받을 수 없습니다. 부팅 시 시작 활성화 - 백그라운드 제한 사항 확인 ${app_name}에 대한 백그라운드 제한 사항을 비활성화합니다. 이 테스트는 모바일 데이터를 사용해야 합니다 (WIFI 없음). \n%1$s @@ -721,12 +621,10 @@ \n앱이 백그라운드에서 작업하는 동안 앱이 시도하는 작업은 적극적으로 제한되며, 이는 알림에 영향을 줄 수 있습니다. \n%1$s 제한 사항 비활성화 - 배터리 최적화 ${app_name}은 배터리 최적화의 영향을 받지 않습니다. 사용자가 기기 화면을 끈 상태로 일정 시간 동안 연결되지 않은 상태로 두면, 기기는 Doze 모드에 들어갑니다. 이렇게 하면 앱이 네트워크에 접근하지 못하고 작업, 동기화 및 표준 경보가 지연됩니다. 최적화 무시하기 - 보통 감소된 개인 정보 보호 앱을 백그라운드에서 실행하려면 권한이 필요합니다 @@ -736,7 +634,6 @@ • 알림의 메시지 내용은 Matrix 홈서버에서 안전하게 직접 배치됩니다 • 알림은 메타데이터와 메시지 데이터를 갖습니다 • 알림은 메시지 내용을 보여주지 않습니다 - 알림 소리 이 계정에서 알림 켜기 이 기기에서 알림 켜기 @@ -745,8 +642,6 @@ 전화 알림 설정 조용한 알림 설정 LED 색상, 진동, 소리를 고르세요… - - 내 표시 이름이 들어있는 메시지 내 사용자 이름이 들어있는 메시지 1:1 대화 메시지 @@ -754,13 +649,11 @@ 방에 초대받았을 때 전화 초대 봇에게 받은 메시지 - 백그라운드 동기화 부팅 시 시작 백그라운드 동기화 켜기 동기화 요청 시간 초과 각 동기화 간 딜레이 - 버전 olm 버전 이용 약관 @@ -770,10 +663,9 @@ 미디어 유지 캐시 지우기 미디어 캐시 지우기 - 사용자 설정 알림 - 무시한 사용자 + 차단한 사용자 기타 고급 암호화 @@ -804,29 +696,23 @@ 보내기 전 미디어 미리보기 엔터 키로 메시지 보내기 가상 키보드의 엔터 버튼으로 줄 바꿈을 하는 대신 메시지를 보냅니다 - 계정 비활성화 내 계정 비활성화 - 알림 개인 정보 ${app_name}은 백그라운드에서 실행되어 알림을 안전하고 은밀하게 관리할 수 있습니다. 이것은 배터리 사용량에 영향을 줄 수 있습니다. 권한 부여 다른 설정을 선택하세요 - 백그라운드 연결 ${app_name}은 신뢰가 있는 알림을 위해 낮은 영향의 백그라운드 연결을 유지해야 합니다. \n다른 화면에서 ${app_name}이 항상 백그라운드에서 실행하도록 허용하는 메시지가 표시됩니다, 수락해주세요. 권한 부여 - 정보 분석 정보 분석 데이터 보내기 ${app_name}은 애플리케이션을 개선할 수 있도록 익명의 분석을 수집합니다. 분석을 활성화해서 ${app_name}이 개선할 수 있도록 도와주세요. 예, 저도 돕고 싶습니다! - 데이터 절약 모드 데이터 절약 모드는 특정 필터를 적용하여 현재 상태 업데이트와 입력 중 알림을 걸러냅니다. - 기기 정보 ID 공개 이름 @@ -838,16 +724,13 @@ 인증 비밀번호: 제출하기 - 이것으로 로그인 홈서버 ID 서버 통합 관리자 - 사용자 인터페이스 언어 언어를 선택하세요 - 확인 보류 중 이메일을 인증해서 거기에 있는 링크를 클릭하세요. 모두 끝나면, 계속하기를 클릭하세요. 이메일 주소를 확인할 수 없습니다. 이메일을 인증해서 거기에 있는 링크를 클릭하세요. 모두 끝나면, 계속하기를 클릭하세요. @@ -855,7 +738,6 @@ 이 이메일 주소를 찾을 수 없습니다. 이 전화번호는 이미 사용 중입니다. 이메일 주소를 확인하는 중 오류가 발생했습니다. - 비밀번호 비밀번호 변경 현재 비밀번호 @@ -869,13 +751,9 @@ \n \n이 동작은 앱을 다시 시작하고 일정 시간이 걸릴 수 있습니다. 비밀번호가 맞지 않음 - 이 알림 대상을 제거하겠습니까\? - %1$s %2$s님을 제거하겠습니까\? - 나라를 선택하세요 - 나라 나라를 선택해주세요 전화번호 @@ -887,32 +765,26 @@ 코드 전화번호를 확인하는 중 오류가 발생했습니다. 추가 정보: %s - 미디어 기본 압축 선택 기본 미디어 소스 선택 셔터 소리 재생하기 - 재능 현재는 어떤 커뮤니티의 구성원이 아닙니다. - 3일 1주 1달 영원히 - 방 사진 방 이름 주제 방 태그 이것으로 태그됨: - 즐겨찾기 중요하지 않음 없음 - 접근성 및 가시성 이 방을 방 목록에 놓기 알림 @@ -920,19 +792,15 @@ 방 기록 읽기 권한 누가 기록을 읽을 수 있나요\? 누가 이 방에 접근할 수 있나요\? - 누구나 (이 설정을 선택한 시점부터) 구성원만 (초대받은 시점부터) 구성원만 (참가한 시점부터) 구성원만 - 이 방에 링크를 타고 오려면 주소가 있어야 합니다. 초대받은 사람들만 방의 링크를 아는 누구나, 손님 제외 방의 링크를 아는 누구나, 손님 포함 - 출입 금지한 사용자 - 고급 이 방의 내부 ID 주소 @@ -943,38 +811,28 @@ 암호화를 켜기 위해 로그아웃을 해야 합니다. 확인된 기기로만 암호화 이 기기로는 방에서 확인되지 않은 기기로 암호화된 메시지를 절대 보내지 않기. - 이 방은 로컬 주소가 없습니다 새 주소 (예: #foo:matrix.org) - 이 방은 어떤 커뮤니티에도 재능을 표시하지 않습니다 새 커뮤니니 ID (예: +foo:matrix.org) 올바르지 않은 커뮤니티 ID \'%s\'은(는) 올바르지 않은 커뮤니티 ID입니다 - - 올바르지 않은 별칭 형식 \'%s\'은(는) 별칭에 올바르지 않은 형식입니다 이 방에 지정된 메인 주소가 없습니다. 메인 주소 경고 - 메인 주소로 설정 메인 주소로 설정 해제 방 ID 복사 방 주소 복사 - 이 방에서 암호화가 켜졌습니다. 이 방에서 암호화가 꺼졌습니다. 암호화 활성화 \n(경고: 다시 비활성화할 수 없음!) - 목록 테마 - %s님이 이 방의 특정 지점을 불러오려 했으나 찾을 수 없었습니다. - 종단간 암호화 정보 - 이벤트 정보 사용자 ID Curve25519 ID 키 @@ -982,7 +840,6 @@ 알고리즘 세션 ID 암호 복호화 오류 - 발신자 기기 정보 공개 이름 공개 이름 @@ -990,7 +847,6 @@ 기기 키 확인 Ed25519 핑거프린트 - 종단간 암호화 방 키 내보내기 방 키 내보내기 로컬 파일로 키 내보내기 @@ -999,51 +855,41 @@ 종단간 암호화 방 키가 \'%s\'에 저장되었습니다. \n \n경고: 애플리케이션을 삭제하면 이 파일도 삭제됩니다. - 암호화된 메시지 복구 키 백업 관리 - 종단간 암호화 방 키 가져오기 방 키 가져오기 로컬 파일에서 키 가져오기 가져오기 확인된 기기로만 암호화 이 기기에서 확인되지 않은 기기로 절대 암호화된 메시지를 보내지 않습니다. - 확인되지 않음 확인됨 블랙리스트 대상 - 알 수 없는 기기 알 수 없는 ip 없음 - 확인 확인하지 않음 블랙리스트 블랙리스트 제외 - 기기 확인 이 기기가 신뢰할 수 있는 지 확인하려면, 다른 방법을 사용하여 소유자에게 연락하세요 (예: 현실에서 혹은 전화로) 그리고 이 기기의 사용자 설정에서 표시된 키가 아래에 있는 키와 맞는지 물어보세요: 그것이 맞다면, 아래의 확인 버튼을 누르세요. 맞지 않다면, 다른 사람이 이 기기를 가로채고 있는 것이고 블랙리스트에 올려야 합니다. 앞으로 이 확인 절차는 더 정교해질 것입니다. 키가 맞다는 것은 확인합니다 - 알 수 없는 기기가 있는 방 이 방에는 확인되지 않은 알 수 없는 기기가 있습니다. \n즉, 사용자에 속해 있다고 주장하는 기기라는 보장이 없습니다. \n저희는 계속하기 전에 각 기기에 확인 절차를 하기를 권합니다, 하지만 원한다면 확인하지 않고 바로 메시지를 보낼 수 있습니다. \n \n알 수 없는 기기: - 방 목록 선택 서버를 이용할 수 없거나 과부하 상태입니다 공개 서버를 표시할 홈서버를 입력하세요 홈서버 URL %s 서버의 모든 방 모든 기본 %s 방 - 여기에 입력… - %d개의 읽지 않은 알림 메시지 @@ -1053,14 +899,12 @@ %d개의 방 - %1$s: %2$d개의 메시지 %d개의 알림 - %2$s에서 %1$s님 새 이벤트 @@ -1068,9 +912,7 @@ 새 초대 ** 보내기 실패 - 방을 열어주세요 - 기록 검색 - 글씨 크기 매우 작게 작게 @@ -1079,7 +921,6 @@ 더 크게 매우 크게 가장 크게 - 이 방에서 위젯을 다루려면 권한이 있어야 합니다 위젯 생성 실패 Jitsi로 회의 전화 만들기 @@ -1087,9 +928,7 @@ %d개의 활성 위젯 - 죄송합니다, Jitsi로 회의 전화는 오래된 기기에서 지원하지 않습니다 (안드로이드 OS가 6.0 이하인 기기) - 위젯을 만들 수 없습니다. 요청을 보낼 수 없습니다. 권한 등급은 양의 정수이어야 합니다. @@ -1107,7 +946,6 @@ 키보드 엔터 키로 메시지 보내기 음성 메시지 보내기 이 설정은 메시지를 기록하기 위한 제 3자 애플리케이션이 필요합니다. - 새 기기 \'%s\'을(를) 추가했습니다, 여기에는 암호화 키가 필요합니다. 새 기기에는 암호화 키가 필요합니다. \n기기 이름: %1$s @@ -1118,7 +956,6 @@ \n기기 이름: %1$s \n마지막으로 본 순간: %2$s \n다른 기기에서 로그인하지 않았다면, 이 요청을 무시하세요. - 확인 시작 확인 확인하지 않고 공유 @@ -1126,10 +963,8 @@ 키 공유 요청 요청 무시하기 무시 - 경고! 회의 전화는 개발 중이며 신뢰할 수 없을 수 있습니다. - 명령어 오류 인식할 수 없는 명령어: %s \"%s\" 명령어는 더 많은 매개 변수가 필요하거나, 일부 매개 변수가 옳지 않습니다. @@ -1146,56 +981,44 @@ 표시 별명 바꾸기 마크다운 On/Off Matrix 앱 관리 문제를 해결하려면 - 마크다운이 켜졌습니다. 마크다운이 꺼졌습니다. - 조용히 소리 - 암호화된 메시지 - 만들기 커뮤니티 만들기 커뮤니티 이름 예시 커뮤니티 ID 예시 - 사람 사용자 없음 - 참가함 초대받음 구성원 그룹 필터 방 그룹 필터 - %d명의 구성원 - %d개의 방 커뮤니티 관리자가 이 커뮤니티에 대한 자세한 설명을 제공하지 않았습니다. - %2$s님에 의해 %1$s 방에서 추방당했습니다 %2$s님에 의해 %1$s 방에서 출입 금지당했습니다 이유: %1$s 다시 참가하기 방 잊어버리기 - 아바타 메모 아바타 공지 아바타 - %1$s 홈서버를 계속 사용하려면 이용 약관을 검토하고 승인해야 합니다. 지금 검토하기 - 계정 비활성화 이것으로 계정은 영구적으로 사용할 수 없게 됩니다. 로그인할 수 없고 누구도 같은 사용자 ID로 다시 등록할 수 없게 됩니다. 이 계정으로 참가한 모든 방에서 떠나게 되고, ID 서버의 계정 세부 사항도 삭제됩니다. 이 행동은 돌이킬 수 없습니다. \n @@ -1205,58 +1028,44 @@ 내 계정을 비활성화하면 내가 보낸 모든 메시지는 잊어주세요 (경고: 이것은 미래 사용자가 불완전한 대화를 읽게 됩니다) 계속하려면, 비밀번호를 입력하세요: 계정 비활성화 - 사용자 이름을 입력하세요. 비밀번호를 입력하세요. 이 방은 교체되었으며 더 이상 활동하지 않습니다 대화는 여기서 계속됩니다 이 방은 다른 대화의 연장선입니다 오래된 메시지를 보려면 여기를 클릭 - 리소스 한도 초과됨 관리자 연락 - 서비스 관리자에게 연락 - 이 홈서버가 리소스 한도를 초과해서 일부 사용자는 로그인할 수 없습니다. 이 홈서버가 리소스 한도를 초과했습니다. - 이 홈서버가 월 간 활성 사용자 한도를 초과해서 일부 사용자는 로그인할 수 없습니다. 이 홈서버가 월 간 활성 사용자 한도를 초과했습니다. - 한도를 높이려면 %s하세요. 이 서비스 사용을 계속하려면 %s하세요. - 방 구성원 불러오기 지연 첫 화면에서 방 멤버만 불러옴으로써 퍼포먼스를 향상시킵니다. 아직 홈서버가 방 구성원 불러오기 지연을 지원하지 않습니다. 나중에 시도하세요. - 죄송합니다, 오류가 발생했습니다 - 펼치기 접기 - 정보 영역 보이기 항상 메시지와 오류일 시 오류만 - %1$s: %1$s: %2$s +%d %d+ 올바른 Google Play 서비스 APK를 찾을 수 없습니다. 알림이 제대로 작동하지 않을 수 있습니다. - 암호 만들기 암호 확인 암호 입력 암호가 맞지 않음 암호를 입력하세요 암호가 너무 약합니다 - ${app_name}으로 복구 키를 생성하려면 암호를 지워주세요. 이용할 수 있는 Matrix 세션이 없음 - 암호화된 메시지를 잃지 마세요 암호화된 방의 메시지는 종단간 암호화로 보호됩니다. 자신과 수신자만이 메시지를 읽을 수 있는 키를 갖습니다. \n @@ -1264,7 +1073,6 @@ 키 백업 시작 (고급) 수동으로 키 내보내기 - 암호로 백업을 보호하세요. 홈서버에 암호화된 키의 사본을 저장합니다. 보안을 유지하기 위해 암호로 백업을 보호하세요. \n @@ -1286,12 +1094,10 @@ 복구 키가 \'%s\'(으)로 저장되었습니다. \n \n경고: 이 파일은 애플리케이션이 삭제되면 함께 삭제됩니다. - 백업이 이미 홈서버에 존재합니다 이미 키 백업을 다른 기기에 설정한 모양입니다. 만들고 있는 것으로 바꾸겠습니까\? 바꾸기 멈추기 - 사본을 만들어 주세요 복구 키 공유… 암호를 사용해 복구 키를 생성하는 것은 시간이 걸릴 수 있습니다. @@ -1299,25 +1105,18 @@ 예기치 않은 오류 백업 시작함 이제 암호화 키가 백그라운드에서 홈서버로 백업됩니다. 처음 백업에는 몇 분이 걸릴 수 있습니다. - - 확신합니까\? 로그아웃을 하거나 이 기기를 잃어버리면 메시지에 접근할 수 없게 될지도 모릅니다. - 백업 버전 가져오는 중… 복구 암호를 사용해서 암호화된 메시지 기록을 풀기 복구 키를 사용 복구 암호가 기억나지 않는다면, %s할 수 있습니다. - 복구 키를 사용해서 암호화된 메시지 기록을 풀기 복구 키 입력 - 메시지 복구 - 복구 키를 잃어버렸나요\? 설정에서 새로운 키를 만들 수 있습니다. 이 암호로 백업을 복호화할 수 없습니다: 올바른 복구 암호를 입력해서 확인해주세요. 네트워크 오류: 인터넷 연결 상태를 확인하고 다시 시도해주세요. - 백업 복구: 복구 키 계산 중… 키 다운로드 중… @@ -1325,7 +1124,6 @@ 기록 풀기 복구 키를 입력하세요 이 복구 키로 백업을 복호화할 수 없습니다: 올바른 복구 키를 입력해서 확인해주세요. - 백업이 복구되었습니다 %s ! %d개의 키와 함께 백업을 복구했습니다. @@ -1333,18 +1131,13 @@ %d개의 새 키가 이 기기에 추가되었습니다. - 최신 복구 키 버전을 가져오는 데 실패했습니다 (%s). 세션 암호화가 활성화되지 않았습니다 - - 백업에서 복구 백업 삭제 - 이 기기를 위한 키 백업이 올바르게 설정되었습니다. 이 기기에서 키 백업이 활성화되지 않았습니다. 이 기기에서 키가 아직 백업되지 않았습니다. - 백업이 ID %s의 알 수 없는 기기의 서명이 있습니다. 백업이 이 기기의 올바른 서명이 있습니다. 백업이 확인된 기기 %s의 올바른 서명이 있습니다. @@ -1352,15 +1145,12 @@ 백업이 확인된 기기 %s의 올바르지 않은 서명이 있습니다 백업이 확인되지 않은 기기 %s의 올바르지 않은 서명이 있습니다 백업에 대한 신뢰 정보를 주지 못했습니다 (%s). - 이 기기에서 키 백업을 사용하려면, 지금 암호나 복구 키를 복구하세요. 백업 삭제 중… 백업 삭제에 실패함 (%s) - 백업 상태 확인하기 백업 삭제 서버에서 백업한 암호화 키를 삭제하겠습니까\? 더 이상 복구 키를 사용해 암호화된 메시지 기록을 읽을 수 없습니다. - 새 키 백업 새 보안 메시지 키 백업이 삭제되었습니다. \n @@ -1368,65 +1158,50 @@ 접니다 암호화된 메시지를 잃지 마세요 키 백업 시작 - 암호화된 메시지를 잃지 마세요 키 백업하기 - 새 암호화된 메시지 키 키 백업에서 관리 - 키 백업 중… - 모든 키가 백업됨 %d개의 키를 백업 중… - 버전 알고리즘 서명 - 잘못된 홈서버 검색 응답 자동 완성 서버 설정 ${app_name}이 userId 도메인 \"%1$s\"에 대한 맞춤 서버 설정을 감지했습니다: \n%2$s 설정 사용 - 올바르지 않거나 만료된 자격 때문에 로그아웃되었습니다. - 짧은 문장과 비교하여 확인하세요. 보안을 최대화하려면, 상대방과 직접 대면하거나 신뢰할 수 있는 다른 대화 수단을 사용하는 것이 좋습니다. 확인 시작 수신 확인 요청 이 기기를 확인하여 신뢰할 수 있는 것으로 표시하세요. 종단간 암호화 메시지를 사용하는 경우 상대방의 기기를 신뢰하면 안심할 수 있습니다. 이 기기를 확인하여 신뢰할 수 있는 것으로 표시하세요, 그리고 당신의 기기도 상대방에게 신뢰할 수 있는 것으로 표시하세요. - 다음 이모지가 상대방의 화면에 나타나는 것을 확인하는 것으로 이 기기를 인증합니다 다음 숫자가 상대방의 화면에 나타나는 것을 확인하는 것으로 이 기기를 인증합니다 - 수신 확인 요청을 받았습니다. 요청 보기 상대방이 확인하기를 기다리는 중… - 확인되었습니다! 성공적으로 이 기기를 확인했습니다. 이 사용자 간의 보안 메시지는 종단간 암호화 되며 제 3자가 읽을 수 없습니다. 알겠습니다 - 아무것도 안 나타나나요\? 일부 클라이언트는 아직 대화 식 확인을 지원하지 않습니다. 옛날 확인 방식을 사용하세요. 옛날 확인 방식 사용하기. - 키 확인 요청 취소됨 상대방이 확인을 취소했습니다. \n%s 확인이 취소되었습니다. \n이유: %s - 상호작용 기기 확인 확인 요청 %s님이 당신의 기기를 확인하고 싶습니다 - 사용자가 확인을 취소했습니다 확인 과정 시간이 초과되었습니다 기기는 이 처리에 대해 모릅니다 @@ -1438,16 +1213,12 @@ 키 맞지 않음 사용자 맞지 않음 알 수 없는 오류 - - 편집 답장 - 다시 시도 앱을 시작하면 방에 참가하기. 초대장을 보냈습니다 %1$s 님이 초대했습니다 - 모두 읽었습니다! 더 이상 읽지 않은 메시지가 없습니다 홈에 온 것을 환영합니다! @@ -1456,19 +1227,15 @@ 다이렉트 메시지 대화가 여기에 표시됩니다 방이 여기 표시됩니다 - 리액션 동의 좋아요 리액션 추가 리액션 보기 리액션 - 사용자가 감춘 이벤트 방 관리자가 감춘 이벤트 %1$s님이 %2$s에 마지막으로 편집함 - - 잘못된 이벤트, 표시할 수 없음 새 방 만들기 네트워크 없음. 인터넷 연결 상태를 확인하세요. @@ -1476,13 +1243,10 @@ 네트워크 변경 기다려주세요… 모든 커뮤니티 - 이 방은 미리 볼 수 없습니다 세계가 읽을 수 있는 방의 미리보기는 아직 ${app_name}X에서 지원하지 않습니다 - 다이렉트 메시지 - 새 방 만들기 방 이름 @@ -1490,18 +1254,13 @@ 누구나 이 방에 참가할 수 있습니다 방 목록 이 방을 방 목록에 게시 - 신뢰 정보를 얻는 과정에서 오류가 발생했습니다 키 백업 데이터를 얻는 과정에서 오류가 발생했습니다 - 파일 \"%1$s\"에서 종단간 암호화 키 가져옴. - Matrix SDK 버전 다른 제 3자 고지 이 방을 이미 봤습니다! - 빠른 리액션 - 기본 환경 설정 보안 & 개인 @@ -1509,91 +1268,65 @@ 푸시 규칙 푸시 규칙이 정의되지 않음 등록된 푸시 게이트웨이가 없음 - app_id: push_key: app_display_name: device_name: Url: Format: - 음성 & 영상 도움 & 정보 - - 등록 토큰 - 제안하기 아래에 당신의 제안을 적어주세요. 여기에 당신의 제안을 설명 감사합니다, 제안을 성공적으로 보냈습니다 제안을 보내는 데 실패함 (%s) - 타임라인에서 숨겨진 이벤트 보이기 - 다이렉트 메시지 - 기다리세요… 썸네일 암호화 중… 썸네일 보내는 중 (%1$s / %2$s) 파일 암호화 중… 파일 보내는 중 (%1$s / %2$s) - 파일 %1$s 다운로드하는 중… 파일 %1$s을(를) 다운로드했습니다! - (편집됨) - - 메시지 편집 수정 사항이 없음 - 대화 필터… 원하는 것을 찾을 수 없나요\? 새 방 만들기 새 다이렉트 메시지 보내기 방 목록 보기 - 이름 혹은 ID (#예시:matrix.org) - 타임라인에서 스와이프로 답장 기능 켜기 - 클립보드에 링크 복사 - Matrix ID로 추가 방 만드는 중… 결과가 없습니다, Matrix ID로 추가를 사용하여 서버를 검색하세요. 결과를 얻기 위해 검색 시작 사용자 이름 또는 ID로 필터… - 방에 참가하는 중… - 편집 기록 보기 - 검토 끊기 - 계속 하려면 이 서비스 약관에 동의해야 합니다. - 서비스 약관 조건 검토 다른 사람이 검색할 수 있음 봇, 브릿지, 위젯과 스티커 팩을 사용하세요 - 읽은 시간 - 없음 취소 연결 해제 설정된 ID 서버가 없습니다. - 잘놋 설정된 서버로 인해 전화에 실패함 전화가 정상적으로 작동하도록 TURN 서버를 설정하려면 홈서버 (%1$s)의 관리자에게 연락해주세요. \n \n아니면 %2$s에서 공개 서버를 사용할 수 있습니다, 하지만 신뢰할 수 없고 서버와 IP 주소를 공유하게 됩니다. 설정에서 이것을 관리할 수도 있습니다. %s 사용하기 다시 묻지 않기 - 계정 복구 용 이메일을 설정합니다, 그리고 이후 알고 있는 사람들이 당신을 찾을 수 있는지 여부를 선택할 수 있습니다. 전화를 설정합니다, 그리고 이후 알고 있는 사람들이 당신을 찾을 수 있는지 여부를 선택할 수 있습니다. 계정 복구 용 이메일을 설정합니다. 이메일이나 전화로 이후 알고 있는 사람들이 당신을 찾을 수 있는지 여부를 선택하는데 사용됩니다. @@ -1612,8 +1345,6 @@ 백그라운드 동기화 없음 앱이 백그라운드에 있을 때 수신 메시지의 알림을 받지 않습니다. 설정을 업데이트하는데 실패했습니다. - - 원하는 동기화 간격 %s \n동기화는 자원 (배터리)이나 기기의 상태 (수면)에 따라 지연됩니다. @@ -1623,9 +1354,7 @@ 기기의 공개 이름은 대화하는 사람들에게 보여집니다 ID 서버를 사용하고 있지 않습니다 설정된 ID 서버가 없습니다, 비밀번호를 초기화하려면 ID 서버가 필요합니다. - 다른 홈서버로 연결을 시도합니다. 로그아웃하겠습니까\? - ID 서버 ID 서버 연결 해제 ID 서버 설정 @@ -1639,27 +1368,19 @@ 전화번호로 찾을 수 있음 %s(으)로 확인 이메일을 보냈습니다, 이메일을 확인하고 확인 링크를 클릭하세요 보류 중 - 새 ID 서버를 입력 ID 서버에 연결할 수 없음 ID 서버 URL을 입력해주세요 ID 서버가 서비스 약관이 없습니다 선택한 ID 서버가 서비스 약관이 없습니다. 서비스의 소유자를 신뢰하는 경우에만 계속하세요 %s(으)로 문자 메시지를 보냈습니다. 문자에 있는 확인 코드를 입력해주세요. - 현재 이메일 주소나 전화번호를 ID 서버 %1$s와 공유하고 있습니다. 공유하기를 중지하려면 %2$s(으)로 다시 연결해야 합니다. ID 서버 (%s)의 서비스 약관에 동의하면 다른 사용자가 당신을 이메일 주소나 전화번호로 찾을 수 있게 됩니다. - 상세 로그 켜기. 상세 로그는 분노의 흔들기를 보낼 때 더 많은 로그를 제공해서 개발자에게 도움을 줍니다. 이 설정을 켜도 애플리케이션은 메시지 내용이나 다른 개인 정보를 기록하지 않습니다. - - 홈서버의 이용 약관에 동의한 후 다시 시도해주세요. - 서버의 응답 시간이 지연되고 있습니다. 연결 상태가 좋지 않거나 서버에서 오류가 발생했을지도 모릅니다. 나중에 다시 시도해주세요. - 첨부 파일 보내기 - 내비게이션 서랍 열기 방 만들기 메뉴 열기 방 만들기 메뉴 닫기… @@ -1669,16 +1390,13 @@ 비밀번호 보이기 비밀번호 감추기 맨 아래로 건너뛰기 - %1$s님, %2$s님 그리고 %3$s님이 읽음 %1$s님 그리고 %2$s님이 읽음 %s님이 읽음 %d명이 읽음 - 파일 \'%1$s\' (%2$s)이(가) 업로드하기에 너무 큽니다. 제한은 %3$s입니다. - 첨부 파일을 검색하는 중 오류가 발생했습니다. 파일 연락처 @@ -1687,7 +1405,6 @@ 갤러리 스티커 공유 데이터를 처리할 수 없음 - 스팸 문자입니다 부적절한 문자입니다 맞춤 신고 @@ -1695,7 +1412,6 @@ 이 내용을 신고하는 이유 신고 사용자 출입 금지 - 내용 신고됨 이 내용을 신고했습니다. \n @@ -1708,18 +1424,63 @@ 이 내용을 부적절한 문자로 신고했습니다. \n \n이 사용자의 내용을 더 이상 보고 싶지 않다면, 사용자를 차단하거나 메시지를 감출 수 있습니다 - ${app_name}은 종단간 키를 디스크에 저장하려면 권한이 필요합니다. \n \n키를 수동으로 내보내려면 다음 팝업에서 접근을 허용해주세요. - 현재 네트워크 연결이 없습니다 - 비밀번호가 정확하지 않습니다 라이엇 모바일에서 불가능한 것입니다 인증이 요구됩니다 - - 통합 통합 수락 - + 설명 + 주제 + 주제 추가 + 주제 + 방 주제 (선택) + 주제 변경 + 이 방의 메시지는 종단간 암호화가 적용되었습니다. + 이 방의 메시지는 종단간 암호화가 적용되었습니다. 더보기 & 프로필을 통해 검증. + 이 방의 메시지는 종단간 암호화가 적용됩니다. +\n +\n메시지는 암호화되어 보호되며, 메시지를 복호화할 수 있는 고유 키는 사용자와 수신자만 가지고 있습니다. + 이 방의 메시지는 종단간 암호화가 적용되지 않습니다. + 더보기 + 보안 + 이 계정에 연결된 전화번호가 없습니다 + 전화번호 + 이 계정에 연결된 이메일 주소가 없습니다 + 이메일 주소 + Matrix 계정에 연결된 이메일과 전화번호를 관리합니다 + 이메일과 전화번호 + 메시지 입력란에 이모지 키보드 버튼을 추가합니다. + 이모지 키보드 표시 + 삭제된 메시지를 대화에 표시합니다 + 삭제된 메시지 표시 + 알림과 함께 이메일을 받으려면 Matrix 계정에 이메일을 연결해주세요 + 이메일 알림 + 기타 + SSL 에러. + 새로운 비밀번호 설정하기… + 내 QR코드 공유하기 + QR 코드가 스캔되지 않았습니다! + QR 코드 스캔 + 아니오 + + QR 코드로 추가 + 친구 초대 + 회의는 Jitsi의 보안 및 권한 정책을 사용합니다. 회의가 진행되는 동안 방에 있는 사람들이 참여 버튼을 통해 참가할 수 있습니다. + 전화 회의 시작 + 화상회의 시작 + 회의가 이미 진행 중입니다! + 전화를 걸 수 있는 권한이 없습니다 + 이 방에서 전화를 걸 수 있는 권한이 없습니다 + 전화 회의를 시작할 수 있는 권한이 없습니다 + 이 방에서 전화 회의를 시작할 수 있는 권한이 없습니다 + 권한 없음 + 이 작업을 수행하려면 시스템 설정에서 카메라 권한을 허용해주세요. + 이 작업을 수행할 수 있는 일부 권한이 없습니다. 시스템 설정에서 권한을 허용해주세요. + 일시 정지 + 재생 + 음성 메시지를 보내려면 마이크 권한을 허용해주세요. + \ No newline at end of file From c7fa40fd463ff0c59e95940c5c5128e0725dc733 Mon Sep 17 00:00:00 2001 From: Kiel Date: Tue, 19 Oct 2021 19:45:13 +0000 Subject: [PATCH 168/172] Translated using Weblate (English (United Kingdom)) Currently translated at 0.7% (21 of 2672 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/en_GB/ --- vector/src/main/res/values-en-rGB/strings.xml | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-en-rGB/strings.xml b/vector/src/main/res/values-en-rGB/strings.xml index a6b3daec93..76003bbc69 100644 --- a/vector/src/main/res/values-en-rGB/strings.xml +++ b/vector/src/main/res/values-en-rGB/strings.xml @@ -1,2 +1,24 @@ - \ No newline at end of file + + Extend & customise your experience + Filter favourites + Remove from favourites + Add to favourites + FAVOURITES + Synchronising Self Signing key + Synchronising User key + Synchronising Master key + Initialise CrossSigning + Your email domain is not authorised to register on this server + Unrecognised command: %s + Optimised for real time + Optimised for battery + Background synchronisation + Ignore Optimisation + ${app_name} is not affected by Battery Optimisation. + Battery Optimisation + De-prioritise + Cannot initialise the camera + Unauthorised, missing valid authentication credentials + Initialising service + \ No newline at end of file From 628ccdc328410a6ec6b021fc3642c77e2a47a1e8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Oct 2021 09:16:56 +0200 Subject: [PATCH 169/172] Add English - GB to the list of languages --- vector/src/main/res/values-en-rGB/strings_no_weblate.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 vector/src/main/res/values-en-rGB/strings_no_weblate.xml diff --git a/vector/src/main/res/values-en-rGB/strings_no_weblate.xml b/vector/src/main/res/values-en-rGB/strings_no_weblate.xml new file mode 100644 index 0000000000..a57e7ca631 --- /dev/null +++ b/vector/src/main/res/values-en-rGB/strings_no_weblate.xml @@ -0,0 +1,8 @@ + + + + en + GB + Latn + + \ No newline at end of file From 367795ee24aba444d8ad23c5ccf36fc8d6b3d757 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Oct 2021 09:29:15 +0200 Subject: [PATCH 170/172] Fix crash reported by the PlayStore, for release 1.3.4 I did not find a way to reproduce, but this change should add some safety --- .../app/features/home/room/detail/RoomDetailFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 473a993395..7461b89001 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -641,9 +641,11 @@ class RoomDetailFragment @Inject constructor( } } .setOnEmojiPopupDismissListener { - views.composerLayout.views.composerEmojiButton.apply { - contentDescription = getString(R.string.a11y_open_emoji_picker) - setImageResource(R.drawable.ic_insert_emoji) + if (isAdded) { + views.composerLayout.views.composerEmojiButton.apply { + contentDescription = getString(R.string.a11y_open_emoji_picker) + setImageResource(R.drawable.ic_insert_emoji) + } } } .build(views.composerLayout.views.composerEditText) From e536e1c78510212be7515b2fd0249163dc166ed4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Oct 2021 10:42:12 +0200 Subject: [PATCH 171/172] Run towncrier --- CHANGES.md | 36 ++++++++++++++++++++++++++++++++++++ changelog.d/2909.feature | 1 - changelog.d/3437.bugfix | 1 - changelog.d/3608.bugfix | 1 - changelog.d/3774.bugfix | 1 - changelog.d/3890.misc | 1 - changelog.d/4006.bugfix | 1 - changelog.d/4090.feature | 1 - changelog.d/4106.bugfix | 2 -- changelog.d/4162.bugfix | 1 - changelog.d/4167.bugfix | 1 - changelog.d/4193.bugfix | 1 - changelog.d/4201.bugfix | 1 - changelog.d/4216.misc | 1 - changelog.d/4221.bugfix | 1 - changelog.d/4226.misc | 1 - changelog.d/4234.bugfix | 1 - changelog.d/4247.bugfix | 1 - changelog.d/4250.misc | 1 - changelog.d/4261.bugfix | 1 - changelog.d/4264.misc | 1 - changelog.d/465.misc | 1 - changelog.d/908.bugfix | 1 - 23 files changed, 36 insertions(+), 23 deletions(-) delete mode 100644 changelog.d/2909.feature delete mode 100644 changelog.d/3437.bugfix delete mode 100644 changelog.d/3608.bugfix delete mode 100644 changelog.d/3774.bugfix delete mode 100644 changelog.d/3890.misc delete mode 100644 changelog.d/4006.bugfix delete mode 100644 changelog.d/4090.feature delete mode 100644 changelog.d/4106.bugfix delete mode 100644 changelog.d/4162.bugfix delete mode 100644 changelog.d/4167.bugfix delete mode 100644 changelog.d/4193.bugfix delete mode 100644 changelog.d/4201.bugfix delete mode 100644 changelog.d/4216.misc delete mode 100644 changelog.d/4221.bugfix delete mode 100644 changelog.d/4226.misc delete mode 100644 changelog.d/4234.bugfix delete mode 100644 changelog.d/4247.bugfix delete mode 100644 changelog.d/4250.misc delete mode 100644 changelog.d/4261.bugfix delete mode 100644 changelog.d/4264.misc delete mode 100644 changelog.d/465.misc delete mode 100644 changelog.d/908.bugfix diff --git a/CHANGES.md b/CHANGES.md index c545a70671..8de71eae62 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,39 @@ +Changes in Element v1.3.4 (2021-10-20) +====================================== + +Features ✨ +---------- + - Implement /part command, with or without parameter ([#2909](https://github.com/vector-im/element-android/issues/2909)) + - Handle Presence support, for Direct Message room ([#4090](https://github.com/vector-im/element-android/issues/4090)) + +Bugfixes 🐛 +---------- + - Issue #908 Adding trailing space " " or ": " if the user started a sentence by mentioning someone, ([#908](https://github.com/vector-im/element-android/issues/908)) + - Fixes reappearing notifications when dismissing notifications from slow homeservers or delayed /sync responses ([#3437](https://github.com/vector-im/element-android/issues/3437)) + - Catching event decryption crash and logging when attempting to markOlmSessionForUnwedging fails ([#3608](https://github.com/vector-im/element-android/issues/3608)) + - Fixing notification sounds being triggered for every message, now they only trigger for the first, consistent with the vibrations ([#3774](https://github.com/vector-im/element-android/issues/3774)) + - Voice Message not sendable if recorded while flight mode was on ([#4006](https://github.com/vector-im/element-android/issues/4006)) + - Fixes push notification emails list not refreshing the first time seeing the notifications page. + Also improves the error handling in the email notification toggling by using synchronous flows instead of the WorkManager ([#4106](https://github.com/vector-im/element-android/issues/4106)) + - Make MegolmBackupAuthData.signatures optional for robustness ([#4162](https://github.com/vector-im/element-android/issues/4162)) + - Fixing push notifications starting the looping background sync when the push notification causes the application to be created. ([#4167](https://github.com/vector-im/element-android/issues/4167)) + - Fix random crash when user logs out just after the log in. ([#4193](https://github.com/vector-im/element-android/issues/4193)) + - Make the font size selection dialog scrollable ([#4201](https://github.com/vector-im/element-android/issues/4201)) + - Fix conversation notification for sent messages ([#4221](https://github.com/vector-im/element-android/issues/4221)) + - Fixes the developer sync options being displayed in the home menu when developer mode is disabled ([#4234](https://github.com/vector-im/element-android/issues/4234)) + - Restore support for Android Auto as sent messages are no longer read aloud ([#4247](https://github.com/vector-im/element-android/issues/4247)) + - Fix crash on slash commands Exceptions ([#4261](https://github.com/vector-im/element-android/issues/4261)) + +Other changes +------------- + - Scrub user sensitive data like gps location from images when sending on original quality ([#465](https://github.com/vector-im/element-android/issues/465)) + - Migrate to MvRx2 (Mavericks) ([#3890](https://github.com/vector-im/element-android/issues/3890)) + - Implement a new github action workflow to generate two PRs for emoji and sas string sync ([#4216](https://github.com/vector-im/element-android/issues/4216)) + - Improve wording around rageshakes in the defect issue template. ([#4226](https://github.com/vector-im/element-android/issues/4226)) + - Add automation to move incoming issues and X-Needs-Info into the right places on the issue triage board. ([#4250](https://github.com/vector-im/element-android/issues/4250)) + - Uppon sharing image compression fails, return the original image ([#4264](https://github.com/vector-im/element-android/issues/4264)) + + Changes in Element v1.3.3 (2021-10-11) ====================================== diff --git a/changelog.d/2909.feature b/changelog.d/2909.feature deleted file mode 100644 index 4d72734192..0000000000 --- a/changelog.d/2909.feature +++ /dev/null @@ -1 +0,0 @@ -Implement /part command, with or without parameter \ No newline at end of file diff --git a/changelog.d/3437.bugfix b/changelog.d/3437.bugfix deleted file mode 100644 index 11e493e1ba..0000000000 --- a/changelog.d/3437.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes reappearing notifications when dismissing notifications from slow homeservers or delayed /sync responses \ No newline at end of file diff --git a/changelog.d/3608.bugfix b/changelog.d/3608.bugfix deleted file mode 100644 index 5f219ab319..0000000000 --- a/changelog.d/3608.bugfix +++ /dev/null @@ -1 +0,0 @@ -Catching event decryption crash and logging when attempting to markOlmSessionForUnwedging fails \ No newline at end of file diff --git a/changelog.d/3774.bugfix b/changelog.d/3774.bugfix deleted file mode 100644 index 9145771bae..0000000000 --- a/changelog.d/3774.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixing notification sounds being triggered for every message, now they only trigger for the first, consistent with the vibrations \ No newline at end of file diff --git a/changelog.d/3890.misc b/changelog.d/3890.misc deleted file mode 100644 index 3bace80fa7..0000000000 --- a/changelog.d/3890.misc +++ /dev/null @@ -1 +0,0 @@ -Migrate to MvRx2 (Mavericks) \ No newline at end of file diff --git a/changelog.d/4006.bugfix b/changelog.d/4006.bugfix deleted file mode 100644 index 61ac98ccb8..0000000000 --- a/changelog.d/4006.bugfix +++ /dev/null @@ -1 +0,0 @@ -Voice Message not sendable if recorded while flight mode was on \ No newline at end of file diff --git a/changelog.d/4090.feature b/changelog.d/4090.feature deleted file mode 100644 index be8aa317a6..0000000000 --- a/changelog.d/4090.feature +++ /dev/null @@ -1 +0,0 @@ -Handle Presence support, for Direct Message room \ No newline at end of file diff --git a/changelog.d/4106.bugfix b/changelog.d/4106.bugfix deleted file mode 100644 index 6ca030f8c4..0000000000 --- a/changelog.d/4106.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Fixes push notification emails list not refreshing the first time seeing the notifications page. -Also improves the error handling in the email notification toggling by using synchronous flows instead of the WorkManager \ No newline at end of file diff --git a/changelog.d/4162.bugfix b/changelog.d/4162.bugfix deleted file mode 100644 index 833ed2f1b8..0000000000 --- a/changelog.d/4162.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make MegolmBackupAuthData.signatures optional for robustness \ No newline at end of file diff --git a/changelog.d/4167.bugfix b/changelog.d/4167.bugfix deleted file mode 100644 index 8df264d8f6..0000000000 --- a/changelog.d/4167.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixing push notifications starting the looping background sync when the push notification causes the application to be created. diff --git a/changelog.d/4193.bugfix b/changelog.d/4193.bugfix deleted file mode 100644 index a395e70cee..0000000000 --- a/changelog.d/4193.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix random crash when user logs out just after the log in. \ No newline at end of file diff --git a/changelog.d/4201.bugfix b/changelog.d/4201.bugfix deleted file mode 100644 index fa8e506015..0000000000 --- a/changelog.d/4201.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make the font size selection dialog scrollable diff --git a/changelog.d/4216.misc b/changelog.d/4216.misc deleted file mode 100644 index acf5f1dbcb..0000000000 --- a/changelog.d/4216.misc +++ /dev/null @@ -1 +0,0 @@ -Implement a new github action workflow to generate two PRs for emoji and sas string sync diff --git a/changelog.d/4221.bugfix b/changelog.d/4221.bugfix deleted file mode 100644 index 76c66898b1..0000000000 --- a/changelog.d/4221.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix conversation notification for sent messages diff --git a/changelog.d/4226.misc b/changelog.d/4226.misc deleted file mode 100644 index ac7a294f35..0000000000 --- a/changelog.d/4226.misc +++ /dev/null @@ -1 +0,0 @@ -Improve wording around rageshakes in the defect issue template. diff --git a/changelog.d/4234.bugfix b/changelog.d/4234.bugfix deleted file mode 100644 index e7b46a6186..0000000000 --- a/changelog.d/4234.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixes the developer sync options being displayed in the home menu when developer mode is disabled \ No newline at end of file diff --git a/changelog.d/4247.bugfix b/changelog.d/4247.bugfix deleted file mode 100644 index da8ca300b4..0000000000 --- a/changelog.d/4247.bugfix +++ /dev/null @@ -1 +0,0 @@ -Restore support for Android Auto as sent messages are no longer read aloud diff --git a/changelog.d/4250.misc b/changelog.d/4250.misc deleted file mode 100644 index c4d2fcbd6d..0000000000 --- a/changelog.d/4250.misc +++ /dev/null @@ -1 +0,0 @@ -Add automation to move incoming issues and X-Needs-Info into the right places on the issue triage board. diff --git a/changelog.d/4261.bugfix b/changelog.d/4261.bugfix deleted file mode 100644 index 4283335131..0000000000 --- a/changelog.d/4261.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash on slash commands Exceptions \ No newline at end of file diff --git a/changelog.d/4264.misc b/changelog.d/4264.misc deleted file mode 100644 index 601abbf792..0000000000 --- a/changelog.d/4264.misc +++ /dev/null @@ -1 +0,0 @@ -Uppon sharing image compression fails, return the original image diff --git a/changelog.d/465.misc b/changelog.d/465.misc deleted file mode 100644 index 709d1f1957..0000000000 --- a/changelog.d/465.misc +++ /dev/null @@ -1 +0,0 @@ -Scrub user sensitive data like gps location from images when sending on original quality diff --git a/changelog.d/908.bugfix b/changelog.d/908.bugfix deleted file mode 100644 index f43b03e892..0000000000 --- a/changelog.d/908.bugfix +++ /dev/null @@ -1 +0,0 @@ -Issue #908 Adding trailing space " " or ": " if the user started a sentence by mentioning someone, From d5f2a6179d8ed66ec5f23f48086ab6018723e21b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 20 Oct 2021 10:44:20 +0200 Subject: [PATCH 172/172] Fastlane change --- fastlane/metadata/android/en-US/changelogs/40103040.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40103040.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40103040.txt b/fastlane/metadata/android/en-US/changelogs/40103040.txt new file mode 100644 index 0000000000..06b32c8dff --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40103040.txt @@ -0,0 +1,2 @@ +Main changes in this version: Add Presence support, for Direct Message room (note: presence is disabled on matrix.org. Add again Android Auto support. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.4 \ No newline at end of file