From 8c32796d5cc5dcbde40b1b4b70da5bffc3b7a304 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Jan 2020 16:06:58 +0100 Subject: [PATCH 01/28] Ensure builds are reproducible (#842) --- CHANGES.md | 2 +- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 657f553c36..8d2ee92d7d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ Translations πŸ—£: - Build 🧱: - - + - Ensure builds are reproducible (#842) Changes in RiotX 0.13.0 (2020-01-17) =================================================== diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7a1348a54c..e510d11efb 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -74,7 +74,7 @@ android { } static def gitRevision() { - def cmd = "git rev-parse --short HEAD" + def cmd = "git rev-parse --short=8 HEAD" return cmd.execute().text.trim() } diff --git a/vector/build.gradle b/vector/build.gradle index 14ec9f2c21..a7a0d4afe8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -45,7 +45,7 @@ def getVersionCode() { } static def gitRevision() { - def cmd = "git rev-parse --short HEAD" + def cmd = "git rev-parse --short=8 HEAD" return cmd.execute().text.trim() } From 825427b7b08501a2f9f0d436111f15d5b19871d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 17 Jan 2020 16:50:18 +0100 Subject: [PATCH 02/28] Add a first content into the AUTHORS file --- AUTHORS.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index e69de29bb2..d1898a3cf6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -0,0 +1,35 @@ +A full developer contributors list can be found [here](https://github.com/vector-im/riotX-android/graphs/contributors). + +# Core team: + +Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves. + +## Benoit: Android team leader + +[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org) +- Android team leader and project leader, Android developer, GitHub community manager. +- Specialist of the account creation, and many other fun features. +- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager. +- Release manager on the Play Store + +## FranΓ§ois: Software architect + +[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org) +- Software architect, Android developer +- First developer on the project. +- Work mainly on the global architecture of the project. +- Specialist of the timeline, and lots of other features. + +## Valere: Product manager, Android developer + +[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org) +- Product manager, Android developer +- Specialist on the crypto implementation. + +# Other contributors + +First of all, we thank all contributors who use RiotX and report problems on this GitHub project or via the integrated rageshake function. + +We do not forget all translators, for their work of translating RiotX into many languages. They are also the authors of RiotX. + +Feel free to add your name below, when you contribute to the project! From eee6969b02e22f3a5b67e5157eb1749c9392d369 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Jan 2020 15:10:16 +0100 Subject: [PATCH 03/28] Room settings, and enable encryption in unencrypted rooms (#212) --- CHANGES.md | 2 +- .../im/vector/riotx/core/di/FragmentModule.kt | 34 +++++-- .../riotx/core/platform/VectorBaseFragment.kt | 20 +++- .../roomprofile/RoomProfileActivity.kt | 3 +- .../settings/RoomSettingsAction.kt | 26 ++++++ .../settings/RoomSettingsController.kt | 74 +++++++++++++++ .../settings/RoomSettingsFragment.kt | 84 +++++++++++++++++ .../settings/RoomSettingsViewModel.kt | 93 +++++++++++++++++++ .../settings/RoomSettingsViewState.kt | 32 +++++++ .../devices/VectorSettingsDevicesFragment.kt | 8 -- .../VectorSettingsIgnoredUsersFragment.kt | 8 -- .../res/layout/fragment_room_member_list.xml | 13 ++- .../res/layout/merge_overlay_waiting_view.xml | 2 + vector/src/main/res/values/strings_riotX.xml | 2 + 14 files changed, 371 insertions(+), 30 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt diff --git a/CHANGES.md b/CHANGES.md index 657f553c36..91145eece4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.14.0 (2020-XX-XX) =================================================== Features ✨: - - + - Enable encryption in unencrypted rooms, from the room settings (#212) Improvements πŸ™Œ: - diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index b99268d6d4..4fdd2f1f73 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -22,32 +22,49 @@ import androidx.fragment.app.FragmentFactory import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap +import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment +import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment import im.vector.riotx.features.crypto.verification.SASVerificationShortCodeFragment import im.vector.riotx.features.crypto.verification.SASVerificationStartFragment import im.vector.riotx.features.crypto.verification.SASVerificationVerifiedFragment +import im.vector.riotx.features.grouplist.GroupListFragment import im.vector.riotx.features.home.HomeDetailFragment import im.vector.riotx.features.home.HomeDrawerFragment import im.vector.riotx.features.home.LoadingFragment -import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment -import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment -import im.vector.riotx.features.grouplist.GroupListFragment import im.vector.riotx.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.riotx.features.home.room.detail.RoomDetailFragment import im.vector.riotx.features.home.room.list.RoomListFragment -import im.vector.riotx.features.login.* +import im.vector.riotx.features.login.LoginCaptchaFragment +import im.vector.riotx.features.login.LoginFragment +import im.vector.riotx.features.login.LoginGenericTextInputFormFragment +import im.vector.riotx.features.login.LoginResetPasswordFragment +import im.vector.riotx.features.login.LoginResetPasswordMailConfirmationFragment +import im.vector.riotx.features.login.LoginResetPasswordSuccessFragment +import im.vector.riotx.features.login.LoginServerSelectionFragment +import im.vector.riotx.features.login.LoginServerUrlFormFragment +import im.vector.riotx.features.login.LoginSignUpSignInSelectionFragment +import im.vector.riotx.features.login.LoginSplashFragment +import im.vector.riotx.features.login.LoginWaitForEmailFragment +import im.vector.riotx.features.login.LoginWebFragment import im.vector.riotx.features.login.terms.LoginTermsFragment -import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment import im.vector.riotx.features.reactions.EmojiChooserFragment import im.vector.riotx.features.reactions.EmojiSearchResultFragment import im.vector.riotx.features.roomdirectory.PublicRoomsFragment import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment +import im.vector.riotx.features.roommemberprofile.RoomMemberProfileFragment import im.vector.riotx.features.roomprofile.RoomProfileFragment import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment -import im.vector.riotx.features.settings.* +import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment +import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment +import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment +import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment +import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment +import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment +import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment @@ -272,6 +289,11 @@ interface FragmentModule { @FragmentKey(RoomMemberListFragment::class) fun bindRoomMemberListFragment(fragment: RoomMemberListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(RoomSettingsFragment::class) + fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment + @Binds @IntoMap @FragmentKey(RoomMemberProfileFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index 91c166a96c..65b78a4eaa 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -22,10 +22,15 @@ import android.app.ProgressDialog import android.content.Context import android.os.Bundle import android.os.Parcelable -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.View +import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.annotation.LayoutRes import androidx.annotation.MainThread +import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProviders @@ -35,6 +40,7 @@ import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.MvRx import com.bumptech.glide.util.Util.assertMainThread import com.google.android.material.snackbar.Snackbar +import im.vector.riotx.R import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.ScreenComponent @@ -233,4 +239,16 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { inflater.inflate(menuRes, menu) } } + + /* ========================================================================================== + * Common Dialogs + * ========================================================================================== */ + + protected fun displayErrorDialog(throwable: Throwable) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.dialog_title_error) + .setMessage(errorFormatter.toHumanReadable(throwable)) + .setPositiveButton(R.string.ok, null) + .show() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt index 160d2341ed..1a9b268b90 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileActivity.kt @@ -26,6 +26,7 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.roomprofile.members.RoomMemberListFragment +import im.vector.riotx.features.roomprofile.settings.RoomSettingsFragment class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { @@ -69,7 +70,7 @@ class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable { } private fun openRoomSettings() { - notImplemented("Open room settings") + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) } private fun openRoomMembers() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt new file mode 100644 index 0000000000..3c1b10cf8e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsAction.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.settings + +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class RoomSettingsAction : VectorViewModelAction { + data class SetRoomName(val newName: String) : RoomSettingsAction() + data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() + data class SetRoomAvatar(val newAvatarUrl: String) : RoomSettingsAction() + object EnableEncryption : RoomSettingsAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt new file mode 100644 index 0000000000..b9a20ef915 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.settings + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.profiles.buildProfileAction +import im.vector.riotx.core.epoxy.profiles.buildProfileSection +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import javax.inject.Inject + +// TODO Add other feature here (waiting for design) +class RoomSettingsController @Inject constructor( + private val stringProvider: StringProvider, + colorProvider: ColorProvider +) : TypedEpoxyController() { + + interface Callback { + fun onEnableEncryptionClicked() + } + + private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) + + var callback: Callback? = null + + init { + setData(null) + } + + override fun buildModels(data: RoomSettingsViewState?) { + val roomSummary = data?.roomSummary?.invoke() ?: return + + buildProfileSection( + stringProvider.getString(R.string.settings) + ) + + // Only enable encryption for the moment + if (roomSummary.isEncrypted) { + buildProfileAction( + id = "encryption", + title = stringProvider.getString(R.string.room_settings_addresses_e2e_enabled), + dividerColor = dividerColor, + divider = false, + editable = false, + action = {} + ) + } else { + buildProfileAction( + id = "encryption", + title = stringProvider.getString(R.string.room_settings_enable_encryption), + subtitle = stringProvider.getString(R.string.room_settings_enable_encryption_warning), + dividerColor = dividerColor, + divider = false, + editable = true, + action = { callback?.onEnableEncryptionClicked() } + ) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt new file mode 100644 index 0000000000..e7a221d40d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.settings + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.roomprofile.RoomProfileArgs +import kotlinx.android.synthetic.main.fragment_room_member_list.* +import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import javax.inject.Inject + +class RoomSettingsFragment @Inject constructor( + val viewModelFactory: RoomSettingsViewModel.Factory, + private val controller: RoomSettingsController, + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment(), RoomSettingsController.Callback { + + private val viewModel: RoomSettingsViewModel by fragmentViewModel() + private val roomProfileArgs: RoomProfileArgs by args() + + override fun getLayoutResId() = R.layout.fragment_room_member_list + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + controller.callback = this + setupToolbar(roomMemberListToolbar) + recyclerView.configureWith(controller, hasFixedSize = true) + waiting_view_status_text.setText(R.string.please_wait) + waiting_view_status_text.isVisible = true + + viewModel.requestErrorLiveData.observeEvent(this) { + displayErrorDialog(it) + } + } + + override fun onDestroyView() { + recyclerView.cleanup() + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { viewState -> + controller.setData(viewState) + renderRoomSummary(viewState) + } + + override fun onEnableEncryptionClicked() { + viewModel.handle(RoomSettingsAction.EnableEncryption) + } + + private fun renderRoomSummary(state: RoomSettingsViewState) { + waiting_view.isVisible = state.currentRequest is Loading + + state.roomSummary()?.let { + roomMemberListToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt new file mode 100644 index 0000000000..fad8504e01 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.settings + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.rx.rx +import im.vector.matrix.rx.unwrap +import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.platform.VectorViewModel + +class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomSettingsViewState): RoomSettingsViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomSettingsViewState): RoomSettingsViewModel? { + val fragment: RoomSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewModelFactory.create(state) + } + } + + private val room = session.getRoom(initialState.roomId)!! + + init { + observeRoomSummary() + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .unwrap() + .execute { async -> + copy(roomSummary = async) + } + } + + override fun handle(action: RoomSettingsAction) { + when (action) { + is RoomSettingsAction.EnableEncryption -> handleEnableEncryption() + } + } + + private fun handleEnableEncryption() { + setState { + copy(currentRequest = Loading()) + } + + room.enableEncryption(MXCRYPTO_ALGORITHM_MEGOLM, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + setState { + copy(currentRequest = Uninitialized) + } + + _requestErrorLiveData.postLiveEvent(failure) + } + + override fun onSuccess(data: Unit) { + setState { + copy(currentRequest = Uninitialized) + } + } + }) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt new file mode 100644 index 0000000000..b3be2e308e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.settings + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotx.features.roomprofile.RoomProfileArgs + +data class RoomSettingsViewState( + val roomId: String, + val roomSummary: Async = Uninitialized, + val currentRequest: Async = Uninitialized +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index 465b3ba0fb..8de7c7a322 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -83,14 +83,6 @@ class VectorSettingsDevicesFragment @Inject constructor( (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_devices_list) } - private fun displayErrorDialog(throwable: Throwable) { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(throwable)) - .setPositiveButton(R.string.ok, null) - .show() - } - override fun onDeviceClicked(deviceInfo: DeviceInfo) { devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo)) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 6435f43d87..d7069ce9f9 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -77,14 +77,6 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( .show() } - private fun displayErrorDialog(throwable: Throwable) { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(throwable)) - .setPositiveButton(R.string.ok, null) - .show() - } - // ============================================================================================================== // ignored users list management // ============================================================================================================== diff --git a/vector/src/main/res/layout/fragment_room_member_list.xml b/vector/src/main/res/layout/fragment_room_member_list.xml index 5fe4fc3770..06b4293e78 100644 --- a/vector/src/main/res/layout/fragment_room_member_list.xml +++ b/vector/src/main/res/layout/fragment_room_member_list.xml @@ -4,14 +4,15 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/rootConstraintLayout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="?riotx_header_panel_background"> @@ -42,9 +43,9 @@ android:maxLines="1" android:textColor="?vctr_toolbar_primary_text_color" android:textSize="18sp" - app:layout_constraintStart_toEndOf="@+id/roomMemberListToolbarAvatarImageView" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/roomMemberListToolbarAvatarImageView" app:layout_constraintTop_toTopOf="parent" tools:text="@sample/matrix.json/data/roomName" /> @@ -57,10 +58,12 @@ android:layout_width="0dp" android:layout_height="0dp" android:overScrollMode="always" - app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar" tools:listitem="@layout/item_autocomplete_matrix_item" /> + + diff --git a/vector/src/main/res/layout/merge_overlay_waiting_view.xml b/vector/src/main/res/layout/merge_overlay_waiting_view.xml index b7e7bf41c7..3ee572cd71 100644 --- a/vector/src/main/res/layout/merge_overlay_waiting_view.xml +++ b/vector/src/main/res/layout/merge_overlay_waiting_view.xml @@ -10,6 +10,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?vctr_waiting_background_color" + android:clickable="true" + android:focusable="true" android:visibility="gone" tools:visibility="visible"> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7fff839e7e..453235e0ce 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -40,5 +40,7 @@ Message editor + Enable end-to-end encryption + Warning: once encryption is enabled in a room, it cannot be disabled From 56b140fcb44b9247d83f1d64163a565366ff69de Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Jan 2020 15:23:40 +0100 Subject: [PATCH 04/28] Room settings: rename stuff for genericity --- .../roomprofile/members/RoomMemberListFragment.kt | 10 +++++----- .../roomprofile/settings/RoomSettingsFragment.kt | 10 +++++----- ..._list.xml => fragment_room_setting_generic.xml} | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) rename vector/src/main/res/layout/{fragment_room_member_list.xml => fragment_room_setting_generic.xml} (84%) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index 4919fa39e3..e6e54d6771 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -29,7 +29,7 @@ import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs -import kotlinx.android.synthetic.main.fragment_room_member_list.* +import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import javax.inject.Inject class RoomMemberListFragment @Inject constructor( @@ -41,12 +41,12 @@ class RoomMemberListFragment @Inject constructor( private val viewModel: RoomMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() - override fun getLayoutResId() = R.layout.fragment_room_member_list + override fun getLayoutResId() = R.layout.fragment_room_setting_generic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomMemberListController.callback = this - setupToolbar(roomMemberListToolbar) + setupToolbar(roomSettingsToolbar) recyclerView.configureWith(roomMemberListController, hasFixedSize = true) } @@ -66,8 +66,8 @@ class RoomMemberListFragment @Inject constructor( private fun renderRoomSummary(state: RoomMemberListViewState) { state.roomSummary()?.let { - roomMemberListToolbarTitleView.text = it.displayName - avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView) + roomSettingsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index e7a221d40d..d75821b7da 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -31,7 +31,7 @@ import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs -import kotlinx.android.synthetic.main.fragment_room_member_list.* +import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -44,12 +44,12 @@ class RoomSettingsFragment @Inject constructor( private val viewModel: RoomSettingsViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() - override fun getLayoutResId() = R.layout.fragment_room_member_list + override fun getLayoutResId() = R.layout.fragment_room_setting_generic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) controller.callback = this - setupToolbar(roomMemberListToolbar) + setupToolbar(roomSettingsToolbar) recyclerView.configureWith(controller, hasFixedSize = true) waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true @@ -77,8 +77,8 @@ class RoomSettingsFragment @Inject constructor( waiting_view.isVisible = state.currentRequest is Loading state.roomSummary()?.let { - roomMemberListToolbarTitleView.text = it.displayName - avatarRenderer.render(it.toMatrixItem(), roomMemberListToolbarAvatarImageView) + roomSettingsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) } } } diff --git a/vector/src/main/res/layout/fragment_room_member_list.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml similarity index 84% rename from vector/src/main/res/layout/fragment_room_member_list.xml rename to vector/src/main/res/layout/fragment_room_setting_generic.xml index 06b4293e78..aa86ee342b 100644 --- a/vector/src/main/res/layout/fragment_room_member_list.xml +++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml @@ -8,7 +8,7 @@ android:background="?riotx_header_panel_background"> @@ -61,8 +61,8 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/roomMemberListToolbar" - tools:listitem="@layout/item_autocomplete_matrix_item" /> + app:layout_constraintTop_toBottomOf="@+id/roomSettingsToolbar" + tools:listitem="@layout/item_profile_action" /> From 28db05e50931a595a052540c0cf9fcb7b2382075 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Jan 2020 15:39:14 +0100 Subject: [PATCH 05/28] Cleanup and copy wording from Riot-Web --- .../riotx/core/epoxy/profiles/ProfileActionItem.kt | 5 ++++- .../riotx/core/epoxy/profiles/ProfileItemExtensions.kt | 5 +++-- .../roomprofile/settings/RoomSettingsController.kt | 4 +--- .../roomprofile/settings/RoomSettingsFragment.kt | 10 +++++++++- vector/src/main/res/values/strings_riotX.xml | 6 +++++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt index 0a7dcb0de7..404570568d 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileActionItem.kt @@ -43,11 +43,14 @@ abstract class ProfileActionItem : VectorEpoxyModel() @EpoxyAttribute var destructive: Boolean = false @EpoxyAttribute - lateinit var listener: View.OnClickListener + var listener: View.OnClickListener? = null override fun bind(holder: Holder) { super.bind(holder) holder.view.setOnClickListener(listener) + if (listener == null) { + holder.view.isClickable = false + } holder.editable.isVisible = editable holder.title.text = title val tintColor = if (destructive) { diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt index 32060f577d..ab68bdc4ce 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemExtensions.kt @@ -19,6 +19,7 @@ package im.vector.riotx.core.epoxy.profiles import androidx.annotation.DrawableRes import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.core.epoxy.ClickListener import im.vector.riotx.core.epoxy.dividerItem fun EpoxyController.buildProfileSection(title: String) { @@ -37,7 +38,7 @@ fun EpoxyController.buildProfileAction( @DrawableRes icon: Int = 0, destructive: Boolean = false, divider: Boolean = true, - action: () -> Unit + action: ClickListener? = null ) { profileActionItem { iconRes(icon) @@ -47,7 +48,7 @@ fun EpoxyController.buildProfileAction( destructive(destructive) title(title) listener { _ -> - action() + action?.invoke() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt index b9a20ef915..fdd127e68b 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsController.kt @@ -49,15 +49,13 @@ class RoomSettingsController @Inject constructor( stringProvider.getString(R.string.settings) ) - // Only enable encryption for the moment if (roomSummary.isEncrypted) { buildProfileAction( id = "encryption", title = stringProvider.getString(R.string.room_settings_addresses_e2e_enabled), dividerColor = dividerColor, divider = false, - editable = false, - action = {} + editable = false ) } else { buildProfileAction( diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index d75821b7da..498292bd52 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.roomprofile.settings import android.os.Bundle import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.Loading import com.airbnb.mvrx.args @@ -70,7 +71,14 @@ class RoomSettingsFragment @Inject constructor( } override fun onEnableEncryptionClicked() { - viewModel.handle(RoomSettingsAction.EnableEncryption) + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.room_settings_enable_encryption_dialog_title) + .setMessage(R.string.room_settings_enable_encryption_dialog_content) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ -> + viewModel.handle(RoomSettingsAction.EnableEncryption) + } + .show() } private fun renderRoomSummary(state: RoomSettingsViewState) { diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 453235e0ce..4ca908128e 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -41,6 +41,10 @@ Message editor Enable end-to-end encryption - Warning: once encryption is enabled in a room, it cannot be disabled + Once enabled, encryption cannot be disabled. + + Enable encryption? + Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. + Enable encryption From f5e6b4b857364cbd1f12e2932b391cfdf881b0d4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Jan 2020 15:58:12 +0100 Subject: [PATCH 06/28] F-Droid: fix the "-dev" issue in version name (#815) --- .idea/dictionaries/bmarty.xml | 2 ++ CHANGES.md | 2 +- vector/build.gradle | 25 +++++++++++++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index 10c12796c0..680a1d57cf 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -9,6 +9,8 @@ decryptor emoji emojis + fdroid + gplay hmac ktlint linkified diff --git a/CHANGES.md b/CHANGES.md index 657f553c36..6c9d9cef00 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ Translations πŸ—£: - Build 🧱: - - + - F-Droid: fix the "-dev" issue in version name (#815) Changes in RiotX 0.13.0 (2020-01-17) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 14ec9f2c21..bbf45dda53 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -27,7 +27,7 @@ static def generateVersionCodeFromTimestamp() { // It's unix timestamp, minus timestamp of October 3rd 2018 (first commit date) divided by 100: It's incremented by one every 100 seconds. // plus 20_000_000 for compatibility reason with the previous way the Version Code was computed // Note that the result will be multiplied by 10 when adding the digit for the arch - return ((getGitTimestamp() - 1_538_524_800 ) / 100).toInteger() + 20_000_000 + return ((getGitTimestamp() - 1_538_524_800) / 100).toInteger() + 20_000_000 } def generateVersionCodeFromVersionName() { @@ -66,7 +66,8 @@ static def gitBranchName() { } } -static def getVersionSuffix() { +// For Google Play build, build on any other branch than master will have a "-dev" suffix +static def getGplayVersionSuffix() { if (gitBranchName() == "master") { return "" } else { @@ -74,6 +75,20 @@ static def getVersionSuffix() { } } +static def gitTag() { + def cmd = "git describe --exact-match --tags" + return cmd.execute().text.trim() +} + +// For F-Droid build, build on a not tagged commit will have a "-dev" suffix +static def getFdroidVersionSuffix() { + if (gitTag() == "") { + return "-dev" + } else { + return "" + } +} + project.android.buildTypes.all { buildType -> buildType.javaCompileOptions.annotationProcessorOptions.arguments = [ @@ -102,8 +117,6 @@ android { // Other branches (master, features, etc.) will have version code based on application version. versionCode project.getVersionCode() - versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}" - buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" resValue "string", "git_revision", "\"${gitRevision()}\"" @@ -190,6 +203,8 @@ android { gplay { dimension "store" + versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" + resValue "bool", "isGplay", "true" buildConfigField "boolean", "ALLOW_FCM_USE", "true" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\"" @@ -199,6 +214,8 @@ android { fdroid { dimension "store" + versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}" + resValue "bool", "isGplay", "false" buildConfigField "boolean", "ALLOW_FCM_USE", "false" buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\"" From d672313649cc1019b2ab06f752a97629567b9c78 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 20 Jan 2020 17:41:29 +0100 Subject: [PATCH 07/28] Room settings: use boolean instead of Async --- .../features/roomprofile/settings/RoomSettingsFragment.kt | 3 +-- .../features/roomprofile/settings/RoomSettingsViewModel.kt | 6 +++--- .../features/roomprofile/settings/RoomSettingsViewState.kt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index 498292bd52..b707a7e6e4 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -20,7 +20,6 @@ import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -82,7 +81,7 @@ class RoomSettingsFragment @Inject constructor( } private fun renderRoomSummary(state: RoomSettingsViewState) { - waiting_view.isVisible = state.currentRequest is Loading + waiting_view.isVisible = state.isLoading state.roomSummary()?.let { roomSettingsToolbarTitleView.text = it.displayName diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index fad8504e01..27386c141c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -71,13 +71,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: private fun handleEnableEncryption() { setState { - copy(currentRequest = Loading()) + copy(isLoading = true) } room.enableEncryption(MXCRYPTO_ALGORITHM_MEGOLM, object : MatrixCallback { override fun onFailure(failure: Throwable) { setState { - copy(currentRequest = Uninitialized) + copy(isLoading = false) } _requestErrorLiveData.postLiveEvent(failure) @@ -85,7 +85,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: override fun onSuccess(data: Unit) { setState { - copy(currentRequest = Uninitialized) + copy(isLoading = false) } } }) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt index b3be2e308e..d68ed6853e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewState.kt @@ -25,7 +25,7 @@ import im.vector.riotx.features.roomprofile.RoomProfileArgs data class RoomSettingsViewState( val roomId: String, val roomSummary: Async = Uninitialized, - val currentRequest: Async = Uninitialized + val isLoading: Boolean = false ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) From be371f9279a1e204d5813ad2eda0783ebd00be29 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 12:45:38 +0100 Subject: [PATCH 08/28] Introduce ViewEvents in ViewModel and code harmonization --- .../riotx/core/platform/VectorBaseFragment.kt | 12 +++++- .../riotx/core/platform/VectorViewEvents.kt | 27 ++++++++++++ .../riotx/core/platform/VectorViewModel.kt | 23 +++++----- .../CreateDirectRoomDirectoryUsersFragment.kt | 6 ++- .../createdirect/CreateDirectRoomViewModel.kt | 3 +- .../settings/KeysBackupSettingsViewModel.kt | 3 +- .../features/grouplist/GroupListViewModel.kt | 3 +- .../features/home/HomeDetailViewModel.kt | 3 +- .../room/breadcrumbs/BreadcrumbsViewModel.kt | 3 +- .../home/room/detail/RoomDetailViewEvents.kt | 4 +- .../home/room/detail/RoomDetailViewModel.kt | 15 +++---- .../action/MessageActionsViewModel.kt | 3 +- .../edithistory/ViewEditHistoryViewModel.kt | 15 +++++-- .../reactions/ViewReactionsViewModel.kt | 10 ++++- .../home/room/list/RoomListFragment.kt | 14 ++++++- .../home/room/list/RoomListViewEvents.kt | 6 ++- .../home/room/list/RoomListViewModel.kt | 7 +--- .../actions/RoomListQuickActionsViewModel.kt | 3 +- .../features/login/AbstractLoginFragment.kt | 12 ++---- .../riotx/features/login/LoginActivity.kt | 2 +- .../riotx/features/login/LoginViewEvents.kt | 7 +++- .../riotx/features/login/LoginViewModel.kt | 17 +++----- .../reactions/EmojiSearchResultViewModel.kt | 3 +- .../roomdirectory/PublicRoomsFragment.kt | 1 + .../roomdirectory/RoomDirectoryViewModel.kt | 3 +- .../createroom/CreateRoomViewModel.kt | 10 ++++- .../picker/RoomDirectoryPickerViewModel.kt | 9 +++- .../roompreview/RoomPreviewViewModel.kt | 3 +- .../RoomMemberProfileFragment.kt | 14 +++++-- .../RoomMemberProfileViewEvents.kt | 9 ++-- .../RoomMemberProfileViewModel.kt | 9 +--- .../roomprofile/RoomProfileFragment.kt | 8 ++-- .../roomprofile/RoomProfileViewEvents.kt | 10 +++-- .../roomprofile/RoomProfileViewModel.kt | 7 +--- .../members/RoomMemberListViewModel.kt | 3 +- .../settings/RoomSettingsFragment.kt | 15 +++++-- .../settings/RoomSettingsViewEvents.kt | 28 +++++++++++++ .../settings/RoomSettingsViewModel.kt | 7 +--- .../settings/devices/DevicesAction.kt | 28 +++++++++++++ .../settings/devices/DevicesViewEvents.kt | 28 +++++++++++++ .../settings/devices/DevicesViewModel.kt | 28 ++++++------- .../devices/VectorSettingsDevicesFragment.kt | 42 ++++++++++++------- .../ignored/IgnoredUsersViewEvents.kt | 28 +++++++++++++ .../settings/ignored/IgnoredUsersViewModel.kt | 15 +++++-- .../VectorSettingsIgnoredUsersFragment.kt | 22 ++++++---- .../settings/push/PushGatewaysViewModel.kt | 10 ++++- .../settings/push/PushRulesViewModel.kt | 3 +- .../features/share/IncomingShareViewModel.kt | 3 +- .../signout/soft/SoftLogoutActivity.kt | 2 +- .../signout/soft/SoftLogoutViewEvents.kt | 7 +++- .../signout/soft/SoftLogoutViewModel.kt | 17 ++++---- 51 files changed, 411 insertions(+), 159 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/platform/VectorViewEvents.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewEvents.kt diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index 65b78a4eaa..7b9916b9ad 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -126,6 +126,14 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { mUnBinder = ButterKnife.bind(this, view) } + open fun showLoading(message: CharSequence?) { + showLoadingDialog(message) + } + + open fun showFailure(throwable: Throwable) { + displayErrorDialog(throwable) + } + @CallSuper override fun onDestroyView() { super.onDestroyView() @@ -188,10 +196,10 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { } } - protected fun showLoadingDialog(message: CharSequence, cancelable: Boolean = false) { + protected fun showLoadingDialog(message: CharSequence? = null, cancelable: Boolean = false) { progress = ProgressDialog(requireContext()).apply { setCancelable(cancelable) - setMessage(message) + setMessage(message ?: getString(R.string.please_wait)) setProgressStyle(ProgressDialog.STYLE_SPINNER) show() } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewEvents.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewEvents.kt new file mode 100644 index 0000000000..b5c191f81a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewEvents.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.core.platform + +/** + * Interface for View Events + */ +interface VectorViewEvents + +/** + * To use when no view events is associated to the ViewModel + */ +object EmptyViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt index 74b18be3c2..bda4426c45 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt @@ -16,20 +16,23 @@ package im.vector.riotx.core.platform -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.* -import im.vector.riotx.core.utils.LiveEvent +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.Success +import im.vector.riotx.core.utils.DataSource +import im.vector.riotx.core.utils.PublishDataSource import io.reactivex.Observable import io.reactivex.Single -abstract class VectorViewModel(initialState: S) +abstract class VectorViewModel(initialState: S) : BaseMvRxViewModel(initialState, false) { - // Generic handling of any request error - protected val _requestErrorLiveData = MutableLiveData>() - val requestErrorLiveData: LiveData> - get() = _requestErrorLiveData + // Used to post transient events to the View + protected val _viewEvents = PublishDataSource() + val viewEvents: DataSource = _viewEvents /** * This method does the same thing as the execute function, but it doesn't subscribe to the stream @@ -53,5 +56,5 @@ abstract class VectorViewModel(initial .doOnNext { setState { stateReducer(it) } } } - abstract fun handle(action: A) + abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt index 77cee5fd6f..ecfe054767 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomDirectoryUsersFragment.kt @@ -23,7 +23,11 @@ import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.textChanges import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R -import im.vector.riotx.core.extensions.* +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.hideKeyboard +import im.vector.riotx.core.extensions.setupAsSearch +import im.vector.riotx.core.extensions.showKeyboard import im.vector.riotx.core.platform.VectorBaseFragment import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.* import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 9023312530..fd56aacee4 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.LiveEvent import io.reactivex.Single @@ -51,7 +52,7 @@ data class SelectUserAction( class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt index 4acb318033..bed18ae99d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsViewModel.kt @@ -24,11 +24,12 @@ import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState, session: Session -) : VectorViewModel(initialState), +) : VectorViewModel(initialState), KeysBackupStateListener { @AssistedInject.Factory diff --git a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt index 816f721040..c31a30b432 100644 --- a/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/grouplist/GroupListViewModel.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.rx.rx import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.utils.LiveEvent @@ -45,7 +46,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private val selectedGroupStore: SelectedGroupDataSource, private val session: Session, private val stringProvider: StringProvider -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt index d6d8f06e60..6b322a2b48 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailViewModel.kt @@ -24,6 +24,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.grouplist.SelectedGroupDataSource @@ -40,7 +41,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val selectedGroupStore: SelectedGroupDataSource, private val homeRoomListStore: HomeRoomListDataSource, private val stringProvider: StringProvider) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt index 83e9e0fb3f..f8a1e302af 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsViewModel.kt @@ -24,12 +24,13 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.schedulers.Schedulers class BreadcrumbsViewModel @AssistedInject constructor(@Assisted initialState: BreadcrumbsViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index a1ad480584..ce312d0d7f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -16,9 +16,11 @@ package im.vector.riotx.features.home.room.detail +import im.vector.riotx.core.platform.VectorViewEvents + /** * Transient events for RoomDetail */ -sealed class RoomDetailViewEvents { +sealed class RoomDetailViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : RoomDetailViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index db938c14e5..36cbdcaa75 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -20,7 +20,12 @@ import android.net.Uri import androidx.annotation.IdRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.ViewModelContext import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.PublishRelay import com.squareup.inject.assisted.Assisted @@ -55,9 +60,7 @@ import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.UserPreferencesProvider -import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.LiveEvent -import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand @@ -81,7 +84,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private val stringProvider: StringProvider, private val typingHelper: TypingHelper, private val session: Session -) : VectorViewModel(initialState), Timeline.Listener { +) : VectorViewModel(initialState), Timeline.Listener { private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId @@ -104,9 +107,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro var timeline = room.createTimeline(eventId, timelineSettings) private set - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - // Can be used for several actions, for a one shot result private val _requestLiveData = MutableLiveData>>() val requestLiveData: LiveData>> @@ -290,6 +290,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } + // TODO Cleanup this and use ViewEvents private val _nonBlockingPopAlert = MutableLiveData>>>() val nonBlockingPopAlert: LiveData>>> get() = _nonBlockingPopAlert diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 3f0e8b041f..29068b07df 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -35,6 +35,7 @@ import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.extensions.canReact +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter @@ -89,7 +90,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private val noticeEventFormatter: NoticeEventFormatter, private val stringProvider: StringProvider, private val vectorPreferences: VectorPreferences -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { private val eventId = initialState.eventId private val informationData = initialState.informationData diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index 64d8950420..3c98d24ccf 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -15,7 +15,15 @@ */ package im.vector.riotx.features.home.room.detail.timeline.edithistory -import com.airbnb.mvrx.* +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.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -28,10 +36,11 @@ import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import timber.log.Timber -import java.util.* +import java.util.UUID data class ViewEditHistoryViewState( val eventId: String, @@ -47,7 +56,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted initialState: ViewEditHistoryViewState, val session: Session, val dateFormatter: VectorDateFormatter -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { private val roomId = initialState.roomId private val eventId = initialState.eventId diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 761e80dd59..05cdbc0fd8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -16,7 +16,12 @@ package im.vector.riotx.features.home.room.detail.timeline.reactions -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session @@ -25,6 +30,7 @@ import im.vector.matrix.rx.RxRoom import im.vector.matrix.rx.unwrap import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import io.reactivex.Observable @@ -54,7 +60,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted initialState: DisplayReactionsViewState, private val session: Session, private val dateFormatter: VectorDateFormatter -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { private val roomId = initialState.roomId private val eventId = initialState.eventId diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 4d9f5fb847..6dde2a2d28 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -27,7 +27,11 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.OnModelBuildFinishedListener -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -102,9 +106,11 @@ class RoomListFragment @Inject constructor( .observe() .observeOn(AndroidSchedulers.mainThread()) .subscribe { + dismissLoadingDialog() when (it) { + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) - is RoomListViewEvents.Failure -> showErrorInSnackbar(it.throwable) } } .disposeOnDestroyView() @@ -117,6 +123,10 @@ class RoomListFragment @Inject constructor( .disposeOnDestroyView() } + override fun showFailure(throwable: Throwable) { + showErrorInSnackbar(throwable) + } + override fun onDestroyView() { roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewEvents.kt index 1181236da2..2e147293ec 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewEvents.kt @@ -17,10 +17,14 @@ package im.vector.riotx.features.home.room.list +import im.vector.riotx.core.platform.VectorViewEvents + /** * Transient events for RoomList */ -sealed class RoomListViewEvents { +sealed class RoomListViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : RoomListViewEvents() data class Failure(val throwable: Throwable) : RoomListViewEvents() + data class SelectRoom(val roomId: String) : RoomListViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index a9ea831723..f2f563cf26 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.DataSource -import im.vector.riotx.core.utils.PublishDataSource import io.reactivex.schedulers.Schedulers import timber.log.Timber import javax.inject.Inject @@ -34,7 +33,7 @@ import javax.inject.Inject class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private val session: Session, private val roomSummariesSource: DataSource>) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { interface Factory { fun create(initialState: RoomListViewState): RoomListViewModel @@ -52,9 +51,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private val displayMode = initialState.displayMode private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - init { observeRoomSummaries() } @@ -197,6 +193,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { + _viewEvents.post(RoomListViewEvents.Loading(null)) session.getRoom(action.roomId)?.leave(null, object : MatrixCallback { override fun onFailure(failure: Throwable) { _viewEvents.post(RoomListViewEvents.Failure(failure)) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt index 1c4d414f18..40f773b253 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt @@ -24,11 +24,12 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initialState: RoomListQuickActionsState, session: Session -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index d7e37f762b..b243ea771f 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -70,14 +70,14 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { when (loginViewEvents) { - is LoginViewEvents.Error -> showError(loginViewEvents.throwable) - else -> + is LoginViewEvents.Failure -> showFailure(loginViewEvents.throwable) + else -> // This is handled by the Activity Unit } } - private fun showError(throwable: Throwable) { + override fun showFailure(throwable: Throwable) { when (throwable) { is Failure.ServerError -> { if (throwable.error.code == MatrixError.M_FORBIDDEN @@ -96,11 +96,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { } open fun onError(throwable: Throwable) { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.dialog_title_error) - .setMessage(errorFormatter.toHumanReadable(throwable)) - .setPositiveButton(R.string.ok, null) - .show() + super.showFailure(throwable) } override fun onBackPressed(toolbarButton: Boolean): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt index d879212c3d..908a9c6370 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginActivity.kt @@ -209,7 +209,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable { .setMessage(R.string.login_error_outdated_homeserver_content) .setPositiveButton(R.string.ok, null) .show() - is LoginViewEvents.Error -> + is LoginViewEvents.Failure -> // This is handled by the Fragments Unit } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt index 4c089174f4..25747df3d4 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewEvents.kt @@ -18,12 +18,15 @@ package im.vector.riotx.features.login import im.vector.matrix.android.api.auth.registration.FlowResult +import im.vector.riotx.core.platform.VectorViewEvents /** * Transient events for Login */ -sealed class LoginViewEvents { +sealed class LoginViewEvents: VectorViewEvents { + data class Loading(val message: CharSequence? = null) : LoginViewEvents() + data class Failure(val throwable: Throwable) : LoginViewEvents() + data class RegistrationFlowResult(val flowResult: FlowResult, val isRegistrationStarted: Boolean) : LoginViewEvents() - data class Error(val throwable: Throwable) : LoginViewEvents() object OutdatedHomeserver : LoginViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 8eb4652da5..b38b1d3ee2 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -41,8 +41,6 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.DataSource -import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.signout.soft.SoftLogoutActivity @@ -59,7 +57,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private val pushRuleTriggerListener: PushRuleTriggerListener, private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory, private val sessionListener: SessionListener) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -95,9 +93,6 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private var currentTask: Cancelable? = null - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - override fun handle(action: LoginAction) { when (action) { is LoginAction.UpdateServerType -> handleUpdateServerType(action) @@ -179,7 +174,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi override fun onFailure(failure: Throwable) { if (failure !is CancellationException) { - _viewEvents.post(LoginViewEvents.Error(failure)) + _viewEvents.post(LoginViewEvents.Failure(failure)) } setState { copy( @@ -201,7 +196,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } override fun onFailure(failure: Throwable) { - _viewEvents.post(LoginViewEvents.Error(failure)) + _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( asyncRegistration = Uninitialized @@ -223,7 +218,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi } override fun onFailure(failure: Throwable) { - _viewEvents.post(LoginViewEvents.Error(failure)) + _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( asyncRegistration = Uninitialized @@ -526,7 +521,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi if (homeServerConnectionConfig == null) { // This is invalid - _viewEvents.post(LoginViewEvents.Error(Throwable("Unable to create a HomeServerConnectionConfig"))) + _viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig"))) } else { currentTask?.cancel() currentTask = null @@ -540,7 +535,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback { override fun onFailure(failure: Throwable) { - _viewEvents.post(LoginViewEvents.Error(failure)) + _viewEvents.post(LoginViewEvents.Failure(failure)) setState { copy( asyncHomeServerLoginFlowRequest = Uninitialized diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt index 8aa03d9b22..9856693d22 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.reactions.data.EmojiDataSource import im.vector.riotx.features.reactions.data.EmojiItem @@ -33,7 +34,7 @@ data class EmojiSearchResultViewState( class EmojiSearchResultViewModel @AssistedInject constructor( @Assisted initialState: EmojiSearchResultViewState, private val dataSource: EmojiDataSource) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index 1e625cff75..580844ddfa 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -75,6 +75,7 @@ class PublicRoomsFragment @Inject constructor( sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom) } + // TODO remove this, replace by ViewEvents viewModel.joinRoomErrorLiveData.observeEvent(this) { throwable -> Snackbar.make(publicRoomsCoordinator, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) .show() diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index c4a91a520a..bab07795e9 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.postLiveEvent +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.LiveEvent import timber.log.Timber @@ -41,7 +42,7 @@ private const val PUBLIC_ROOMS_LIMIT = 20 class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: PublicRoomsViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index ff4aa332f4..457574e736 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -17,7 +17,12 @@ package im.vector.riotx.features.roomdirectory.createroom import androidx.fragment.app.FragmentActivity -import com.airbnb.mvrx.* +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -25,12 +30,13 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState, private val session: Session -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt index b7bdaea495..54f93b5404 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/picker/RoomDirectoryPickerViewModel.kt @@ -16,17 +16,22 @@ package im.vector.riotx.features.roomdirectory.picker -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel class RoomDirectoryPickerViewModel @AssistedInject constructor(@Assisted initialState: RoomDirectoryPickerViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index 3de5cb4334..b4aae0d116 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -26,13 +26,14 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.rx.rx +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.JoinState import timber.log.Timber class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: RoomPreviewViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index 23db6b53a8..f118aad8ed 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -20,7 +20,12 @@ package im.vector.riotx.features.roommemberprofile import android.os.Bundle import android.os.Parcelable import android.view.View -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.animations.AppBarStateChangeListener @@ -31,6 +36,7 @@ import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.* @@ -75,11 +81,13 @@ class RoomMemberProfileFragment @Inject constructor( matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) viewModel.viewEvents .observe() + .observeOn(AndroidSchedulers.mainThread()) .subscribe { dismissLoadingDialog() when (it) { - is RoomMemberProfileViewEvents.Loading -> showLoadingDialog(it.message) - is RoomMemberProfileViewEvents.Failure -> showErrorInSnackbar(it.throwable) + is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) + is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) + is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit } } .disposeOnDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt index 093e54989b..4647c124a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewEvents.kt @@ -16,11 +16,14 @@ package im.vector.riotx.features.roommemberprofile +import im.vector.riotx.core.platform.VectorViewEvents + /** * Transient events for RoomMemberProfile */ -sealed class RoomMemberProfileViewEvents { - data class Loading(val message: CharSequence) : RoomMemberProfileViewEvents() - object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() +sealed class RoomMemberProfileViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : RoomMemberProfileViewEvents() data class Failure(val throwable: Throwable) : RoomMemberProfileViewEvents() + + object OnIgnoreActionSuccess : RoomMemberProfileViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt index 88f394e865..a2488b1ed4 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -44,8 +44,6 @@ import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.DataSource -import im.vector.riotx.core.utils.PublishDataSource import io.reactivex.Observable import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers @@ -55,7 +53,7 @@ import kotlinx.coroutines.withContext class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private val initialState: RoomMemberProfileViewState, private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -71,9 +69,6 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - private val room = if (initialState.roomId != null) { session.getRoom(initialState.roomId) } else { @@ -183,7 +178,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v private fun handleIgnoreAction() = withState { state -> val isIgnored = state.isIgnored() ?: return@withState - _viewEvents.post(RoomMemberProfileViewEvents.Loading(stringProvider.getString(R.string.please_wait))) + _viewEvents.post(RoomMemberProfileViewEvents.Loading()) val ignoreActionCallback = object : MatrixCallback { override fun onSuccess(data: Unit) { _viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index da161efd6a..b6e597f232 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -38,6 +38,7 @@ import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.view_stub_room_profile_header.* @@ -79,12 +80,13 @@ class RoomProfileFragment @Inject constructor( matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) roomProfileViewModel.viewEvents .observe() + .observeOn(AndroidSchedulers.mainThread()) .subscribe { dismissLoadingDialog() when (it) { - is RoomProfileViewEvents.Loading -> showLoadingDialog(it.message) - RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() - is RoomProfileViewEvents.Failure -> showError(it.throwable) + is RoomProfileViewEvents.Loading -> showLoading(it.message) + is RoomProfileViewEvents.Failure -> showFailure(it.throwable) + is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() } } .disposeOnDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt index 3834247b52..50b3c136e5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewEvents.kt @@ -15,11 +15,15 @@ */ package im.vector.riotx.features.roomprofile + +import im.vector.riotx.core.platform.VectorViewEvents + /** * Transient events for RoomProfile */ -sealed class RoomProfileViewEvents { - data class Loading(val message: CharSequence): RoomProfileViewEvents() - object OnLeaveRoomSuccess: RoomProfileViewEvents() +sealed class RoomProfileViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : RoomProfileViewEvents() data class Failure(val throwable: Throwable) : RoomProfileViewEvents() + + object OnLeaveRoomSuccess : RoomProfileViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index 8134c93b4f..6c66ac67b2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -29,13 +29,11 @@ import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.utils.DataSource -import im.vector.riotx.core.utils.PublishDataSource class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState, private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -51,9 +49,6 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R } } - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - private val room = session.getRoom(initialState.roomId)!! init { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index 42ef8f285d..8472d4a2a5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -34,13 +34,14 @@ import im.vector.matrix.android.api.session.room.powerlevels.PowerLevelsHelper import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.Observable import io.reactivex.functions.BiFunction class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index b707a7e6e4..3c316a498e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -27,10 +27,10 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith -import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -54,9 +54,16 @@ class RoomSettingsFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true - viewModel.requestErrorLiveData.observeEvent(this) { - displayErrorDialog(it) - } + viewModel.viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + dismissLoadingDialog() + when (it) { + is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) + } + } + .disposeOnDestroyView() } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt new file mode 100644 index 0000000000..999258398b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.riotx.features.roomprofile.settings + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for room settings screen + */ +sealed class RoomSettingsViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : RoomSettingsViewEvents() + data class Failure(val throwable: Throwable) : RoomSettingsViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt index 27386c141c..ee6413b6a4 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,9 +17,7 @@ package im.vector.riotx.features.roomprofile.settings import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -28,12 +26,11 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap -import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -80,7 +77,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: copy(isLoading = false) } - _requestErrorLiveData.postLiveEvent(failure) + _viewEvents.post(RoomSettingsViewEvents.Failure(failure)) } override fun onSuccess(data: Unit) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt new file mode 100644 index 0000000000..550703039f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesAction.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.devices + +import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class DevicesAction : VectorViewModelAction { + object Retry : DevicesAction() + data class Delete(val deviceInfo: DeviceInfo) : DevicesAction() + data class Password(val password: String) : DevicesAction() + data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction() + data class ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt new file mode 100644 index 0000000000..b42738439a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewEvents.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.riotx.features.settings.devices + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for Ignored users screen + */ +sealed class DevicesViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : DevicesViewEvents() + data class Failure(val throwable: Throwable) : DevicesViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index b2b015a3f0..0324036347 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -18,7 +18,15 @@ package im.vector.riotx.features.settings.devices import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.* +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.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -29,7 +37,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.utils.LiveEvent import timber.log.Timber @@ -37,20 +44,13 @@ data class DevicesViewState( val myDeviceId: String = "", val devices: Async> = Uninitialized, val currentExpandedDeviceId: String? = null, + // TODO Replace by isLoading boolean val request: Async = Uninitialized ) : MvRxState -sealed class DevicesAction : VectorViewModelAction { - object Retry : DevicesAction() - data class Delete(val deviceInfo: DeviceInfo) : DevicesAction() - data class Password(val password: String) : DevicesAction() - data class Rename(val deviceInfo: DeviceInfo, val newName: String) : DevicesAction() - data class ToggleDevice(val deviceInfo: DeviceInfo) : DevicesAction() -} - class DevicesViewModel @AssistedInject constructor(@Assisted initialState: DevicesViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -153,7 +153,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic ) } - _requestErrorLiveData.postLiveEvent(failure) + _viewEvents.post(DevicesViewEvents.Failure(failure)) } }) } @@ -207,7 +207,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic ) } - _requestErrorLiveData.postLiveEvent(failure) + _viewEvents.post(DevicesViewEvents.Failure(failure)) } } @@ -261,7 +261,7 @@ class DevicesViewModel @AssistedInject constructor(@Assisted initialState: Devic ) } - _requestErrorLiveData.postLiveEvent(failure) + _viewEvents.post(DevicesViewEvents.Failure(failure)) } }) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index 8de7c7a322..f4b8b54143 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -35,6 +35,7 @@ import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.toast +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -52,7 +53,7 @@ class VectorSettingsDevicesFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_generic_recycler - private val devicesViewModel: DevicesViewModel by fragmentViewModel() + private val viewModel: DevicesViewModel by fragmentViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -61,16 +62,29 @@ class VectorSettingsDevicesFragment @Inject constructor( waiting_view_status_text.isVisible = true devicesController.callback = this recyclerView.configureWith(devicesController, showDivider = true) - devicesViewModel.requestErrorLiveData.observeEvent(this) { - displayErrorDialog(it) - // Password is maybe not good, for safety measure, reset it here - mAccountPassword = "" - } - devicesViewModel.requestPasswordLiveData.observeEvent(this) { + viewModel.viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + dismissLoadingDialog() + when (it) { + is DevicesViewEvents.Loading -> showLoading(it.message) + is DevicesViewEvents.Failure -> showFailure(it.throwable) + } + } + .disposeOnDestroyView() + viewModel.requestPasswordLiveData.observeEvent(this) { maybeShowDeleteDeviceWithPasswordDialog() } } + override fun showFailure(throwable: Throwable) { + super.showFailure(throwable) + + // Password is maybe not good, for safety measure, reset it here + mAccountPassword = "" + } + override fun onDestroyView() { devicesController.callback = null recyclerView.cleanup() @@ -84,11 +98,11 @@ class VectorSettingsDevicesFragment @Inject constructor( } override fun onDeviceClicked(deviceInfo: DeviceInfo) { - devicesViewModel.handle(DevicesAction.ToggleDevice(deviceInfo)) + viewModel.handle(DevicesAction.ToggleDevice(deviceInfo)) } override fun onDeleteDevice(deviceInfo: DeviceInfo) { - devicesViewModel.handle(DevicesAction.Delete(deviceInfo)) + viewModel.handle(DevicesAction.Delete(deviceInfo)) } override fun onRenameDevice(deviceInfo: DeviceInfo) { @@ -96,7 +110,7 @@ class VectorSettingsDevicesFragment @Inject constructor( } override fun retry() { - devicesViewModel.handle(DevicesAction.Retry) + viewModel.handle(DevicesAction.Retry) } /** @@ -117,7 +131,7 @@ class VectorSettingsDevicesFragment @Inject constructor( .setPositiveButton(R.string.ok) { _, _ -> val newName = input.text.toString() - devicesViewModel.handle(DevicesAction.Rename(deviceInfo, newName)) + viewModel.handle(DevicesAction.Rename(deviceInfo, newName)) } .setNegativeButton(R.string.cancel, null) .show() @@ -128,7 +142,7 @@ class VectorSettingsDevicesFragment @Inject constructor( */ private fun maybeShowDeleteDeviceWithPasswordDialog() { if (mAccountPassword.isNotEmpty()) { - devicesViewModel.handle(DevicesAction.Password(mAccountPassword)) + viewModel.handle(DevicesAction.Password(mAccountPassword)) } else { val inflater = requireActivity().layoutInflater val layout = inflater.inflate(R.layout.dialog_device_delete, null) @@ -144,7 +158,7 @@ class VectorSettingsDevicesFragment @Inject constructor( return@OnClickListener } mAccountPassword = passwordEditText.text.toString() - devicesViewModel.handle(DevicesAction.Password(mAccountPassword)) + viewModel.handle(DevicesAction.Password(mAccountPassword)) }) .setNegativeButton(R.string.cancel, null) .setOnKeyListener(DialogInterface.OnKeyListener { dialog, keyCode, event -> @@ -158,7 +172,7 @@ class VectorSettingsDevicesFragment @Inject constructor( } } - override fun invalidate() = withState(devicesViewModel) { state -> + override fun invalidate() = withState(viewModel) { state -> devicesController.update(state) handleRequestStatus(state.request) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewEvents.kt new file mode 100644 index 0000000000..3b98159d1a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewEvents.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.riotx.features.settings.ignored + +import im.vector.riotx.core.platform.VectorViewEvents + +/** + * Transient events for Ignored users screen + */ +sealed class IgnoredUsersViewEvents : VectorViewEvents { + data class Loading(val message: CharSequence? = null) : IgnoredUsersViewEvents() + data class Failure(val throwable: Throwable) : IgnoredUsersViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt index e77660746c..e9c02618b0 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,14 +16,21 @@ package im.vector.riotx.features.settings.ignored -import com.airbnb.mvrx.* +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.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx -import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModelAction @@ -38,7 +45,7 @@ sealed class IgnoredUsersAction : VectorViewModelAction { class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: IgnoredUsersViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -89,7 +96,7 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: ) } - _requestErrorLiveData.postLiveEvent(failure) + _viewEvents.post(IgnoredUsersViewEvents.Failure(failure)) } override fun onSuccess(data: Unit) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index d7069ce9f9..66cad6f247 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -27,9 +27,9 @@ import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith -import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -41,7 +41,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_generic_recycler - private val ignoredUsersViewModel: IgnoredUsersViewModel by fragmentViewModel() + private val viewModel: IgnoredUsersViewModel by fragmentViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -50,9 +50,17 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( waiting_view_status_text.isVisible = true ignoredUsersController.callback = this recyclerView.configureWith(ignoredUsersController) - ignoredUsersViewModel.requestErrorLiveData.observeEvent(this) { - displayErrorDialog(it) - } + viewModel.viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + dismissLoadingDialog() + when (it) { + is IgnoredUsersViewEvents.Loading -> showLoading(it.message) + is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) + } + } + .disposeOnDestroyView() } override fun onDestroyView() { @@ -71,7 +79,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( AlertDialog.Builder(requireActivity()) .setMessage(getString(R.string.settings_unignore_user, userId)) .setPositiveButton(R.string.yes) { _, _ -> - ignoredUsersViewModel.handle(IgnoredUsersAction.UnIgnore(userId)) + viewModel.handle(IgnoredUsersAction.UnIgnore(userId)) } .setNegativeButton(R.string.no, null) .show() @@ -81,7 +89,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( // ignored users list management // ============================================================================================================== - override fun invalidate() = withState(ignoredUsersViewModel) { state -> + override fun invalidate() = withState(viewModel) { state -> ignoredUsersController.update(state) handleUnIgnoreRequestStatus(state.unIgnoreRequest) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt index db4586dff5..672e8e7874 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt @@ -16,13 +16,19 @@ package im.vector.riotx.features.settings.push -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.rx.RxSession import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel data class PushGatewayViewState( @@ -31,7 +37,7 @@ data class PushGatewayViewState( class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: PushGatewayViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesViewModel.kt index e376b7ed6f..3c71238b33 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushRulesViewModel.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel data class PushRulesViewState( @@ -28,7 +29,7 @@ data class PushRulesViewState( ) : MvRxState class PushRulesViewModel(initialState: PushRulesViewState) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { companion object : MvRxViewModelFactory { diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt index bfe08a5c52..37b429d3d7 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.rx.rx import im.vector.riotx.ActiveSessionDataSource import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers @@ -39,7 +40,7 @@ data class IncomingShareState(private val dummy: Boolean = false) : MvRxState class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState, private val sessionObservableStore: ActiveSessionDataSource, private val shareRoomListObservableStore: ShareRoomListDataSource) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutActivity.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutActivity.kt index 8d61fb00b5..96dbbaf102 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutActivity.kt @@ -73,7 +73,7 @@ class SoftLogoutActivity : LoginActivity() { private fun handleSoftLogoutViewEvents(softLogoutViewEvents: SoftLogoutViewEvents) { when (softLogoutViewEvents) { - is SoftLogoutViewEvents.Error -> + is SoftLogoutViewEvents.Failure -> showError(errorFormatter.toHumanReadable(softLogoutViewEvents.throwable)) is SoftLogoutViewEvents.ErrorNotSameUser -> { // Pop the backstack diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewEvents.kt index 1e48fb2a25..314d6b0747 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewEvents.kt @@ -17,11 +17,14 @@ package im.vector.riotx.features.signout.soft +import im.vector.riotx.core.platform.VectorViewEvents + /** * Transient events for SoftLogout */ -sealed class SoftLogoutViewEvents { +sealed class SoftLogoutViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : SoftLogoutViewEvents() + data class ErrorNotSameUser(val currentUserId: String, val newUserId: String) : SoftLogoutViewEvents() - data class Error(val throwable: Throwable) : SoftLogoutViewEvents() object ClearData : SoftLogoutViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewModel.kt index baf208636b..5ee7b6e8a3 100644 --- a/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/signout/soft/SoftLogoutViewModel.kt @@ -16,7 +16,13 @@ package im.vector.riotx.features.signout.soft -import com.airbnb.mvrx.* +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback @@ -28,8 +34,6 @@ import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.hasUnsavedKeys import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.utils.DataSource -import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.features.login.LoginMode import timber.log.Timber @@ -41,7 +45,7 @@ class SoftLogoutViewModel @AssistedInject constructor( private val session: Session, private val activeSessionHolder: ActiveSessionHolder, private val authenticationService: AuthenticationService -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -71,9 +75,6 @@ class SoftLogoutViewModel @AssistedInject constructor( private var currentTask: Cancelable? = null - private val _viewEvents = PublishDataSource() - val viewEvents: DataSource = _viewEvents - init { // Get the supported login flow getSupportedLoginFlow() @@ -192,7 +193,7 @@ class SoftLogoutViewModel @AssistedInject constructor( currentTask = session.updateCredentials(action.credentials, object : MatrixCallback { override fun onFailure(failure: Throwable) { - _viewEvents.post(SoftLogoutViewEvents.Error(failure)) + _viewEvents.post(SoftLogoutViewEvents.Failure(failure)) setState { copy( asyncLoginAction = Uninitialized From e81c804ed649a09b23483ba5bd3b59c4f2ea3643 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 12:51:01 +0100 Subject: [PATCH 09/28] Ensure when will be exhaustive --- .../riotx/core/extensions/Exhaustive.kt | 20 +++++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 3 ++- .../home/room/list/RoomListFragment.kt | 3 ++- .../RoomMemberProfileFragment.kt | 3 ++- .../roomprofile/RoomProfileFragment.kt | 3 ++- .../settings/RoomSettingsFragment.kt | 3 ++- .../settings/RoomSettingsViewEvents.kt | 1 - .../devices/VectorSettingsDevicesFragment.kt | 3 ++- .../VectorSettingsIgnoredUsersFragment.kt | 3 ++- 9 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/extensions/Exhaustive.kt diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Exhaustive.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Exhaustive.kt new file mode 100644 index 0000000000..ac6e14392e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Exhaustive.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.core.extensions + +// Trick to ensure that when block is exhaustive +val T.exhaustive: T get() = this diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 27983aa487..69b3314d64 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -85,6 +85,7 @@ import im.vector.riotx.R import im.vector.riotx.core.dialogs.withColoredButton import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.setTextOrHide @@ -312,7 +313,7 @@ class RoomDetailFragment @Inject constructor( .subscribe { when (it) { is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) - } + }.exhaustive } .disposeOnDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 6dde2a2d28..8d55b2e710 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -39,6 +39,7 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt import im.vector.riotx.R import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment @@ -111,7 +112,7 @@ class RoomListFragment @Inject constructor( is RoomListViewEvents.Loading -> showLoading(it.message) is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) - } + }.exhaustive } .disposeOnDestroyView() diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index f118aad8ed..8be57fa774 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -32,6 +32,7 @@ import im.vector.riotx.core.animations.AppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment @@ -88,7 +89,7 @@ class RoomMemberProfileFragment @Inject constructor( is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit - } + }.exhaustive } .disposeOnDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index b6e597f232..4e43f03e2e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -31,6 +31,7 @@ import im.vector.riotx.core.animations.AppBarStateChangeListener import im.vector.riotx.core.animations.MatrixItemAppBarStateChangeListener import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer @@ -87,7 +88,7 @@ class RoomProfileFragment @Inject constructor( is RoomProfileViewEvents.Loading -> showLoading(it.message) is RoomProfileViewEvents.Failure -> showFailure(it.throwable) is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() - } + }.exhaustive } .disposeOnDestroyView() roomListQuickActionsSharedActionViewModel diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index 3c316a498e..dbc9893308 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs @@ -61,7 +62,7 @@ class RoomSettingsFragment @Inject constructor( dismissLoadingDialog() when (it) { is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) - } + }.exhaustive } .disposeOnDestroyView() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt index 999258398b..4856a935a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsViewEvents.kt @@ -23,6 +23,5 @@ import im.vector.riotx.core.platform.VectorViewEvents * Transient events for room settings screen */ sealed class RoomSettingsViewEvents : VectorViewEvents { - data class Loading(val message: CharSequence? = null) : RoomSettingsViewEvents() data class Failure(val throwable: Throwable) : RoomSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index f4b8b54143..49c423f0ac 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment @@ -70,7 +71,7 @@ class VectorSettingsDevicesFragment @Inject constructor( when (it) { is DevicesViewEvents.Loading -> showLoading(it.message) is DevicesViewEvents.Failure -> showFailure(it.throwable) - } + }.exhaustive } .disposeOnDestroyView() viewModel.requestPasswordLiveData.observeEvent(this) { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 66cad6f247..5d12600b5c 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -27,6 +27,7 @@ import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import io.reactivex.android.schedulers.AndroidSchedulers @@ -58,7 +59,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) - } + }.exhaustive } .disposeOnDestroyView() } From da3e547d82282fa907584146f77f3cd48d66b1a2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 15:00:20 +0100 Subject: [PATCH 10/28] Create facility extension to observe ViewEvents --- .../riotx/core/platform/VectorBaseFragment.kt | 16 +++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 14 +++++-------- .../home/room/list/RoomListFragment.kt | 20 +++++++------------ .../features/login/AbstractLoginFragment.kt | 14 +++++-------- .../RoomMemberProfileFragment.kt | 20 +++++++------------ .../roomprofile/RoomProfileFragment.kt | 20 +++++++------------ .../settings/RoomSettingsFragment.kt | 16 +++++---------- .../devices/VectorSettingsDevicesFragment.kt | 18 ++++++----------- .../VectorSettingsIgnoredUsersFragment.kt | 18 ++++++----------- 9 files changed, 64 insertions(+), 92 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index 7b9916b9ad..357fb0c5bc 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -46,6 +46,7 @@ import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.features.navigation.Navigator +import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable import timber.log.Timber @@ -234,6 +235,21 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { return this } + /* ========================================================================================== + * ViewEvents + * ========================================================================================== */ + + protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { + viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + dismissLoadingDialog() + observer(it) + } + .disposeOnDestroyView() + } + /* ========================================================================================== * MENU MANAGEMENT * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 69b3314d64..3f8dcc04ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -307,15 +307,11 @@ class RoomDetailFragment @Inject constructor( displayRoomDetailActionResult(it) } - roomDetailViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - when (it) { - is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) - }.exhaustive - } - .disposeOnDestroyView() + roomDetailViewModel.observeViewEvents { + when (it) { + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + }.exhaustive + } } override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 8d55b2e710..5a32f4b8b3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -51,7 +51,6 @@ import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsShare import im.vector.riotx.features.home.room.list.widget.FabMenuView import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.share.SharedData -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_list.* import javax.inject.Inject @@ -103,18 +102,13 @@ class RoomListFragment @Inject constructor( setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.subscribe { renderState(it) } - roomListViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) - is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) - }.exhaustive - } - .disposeOnDestroyView() + roomListViewModel.observeViewEvents { + when (it) { + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) + }.exhaustive + } createChatFabMenu.listener = this diff --git a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt index b243ea771f..83263d05a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/AbstractLoginFragment.kt @@ -27,9 +27,9 @@ import com.airbnb.mvrx.withState import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.riotx.R +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.VectorBaseFragment -import io.reactivex.android.schedulers.AndroidSchedulers import javax.net.ssl.HttpsURLConnection /** @@ -59,13 +59,9 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { loginSharedActionViewModel = activityViewModelProvider.get(LoginSharedActionViewModel::class.java) - loginViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - handleLoginViewEvents(it) - } - .disposeOnDestroyView() + loginViewModel.observeViewEvents { + handleLoginViewEvents(it) + } } private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) { @@ -74,7 +70,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { else -> // This is handled by the Activity Unit - } + }.exhaustive } override fun showFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt index 8be57fa774..3d1455042c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -37,7 +37,6 @@ import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.view_stub_room_member_profile_header.* @@ -80,18 +79,13 @@ class RoomMemberProfileFragment @Inject constructor( appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) - viewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) - is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) - is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit - }.exhaustive - } - .disposeOnDestroyView() + viewModel.observeViewEvents { + when (it) { + is RoomMemberProfileViewEvents.Loading -> showLoading(it.message) + is RoomMemberProfileViewEvents.Failure -> showFailure(it.throwable) + is RoomMemberProfileViewEvents.OnIgnoreActionSuccess -> Unit + }.exhaustive + } } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index 4e43f03e2e..4e86738271 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -39,7 +39,6 @@ import im.vector.riotx.features.home.room.list.actions.RoomListActionsArgs import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.riotx.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* import kotlinx.android.synthetic.main.view_stub_room_profile_header.* @@ -79,18 +78,13 @@ class RoomProfileFragment @Inject constructor( appBarStateChangeListener = MatrixItemAppBarStateChangeListener(headerView, listOf(matrixProfileToolbarAvatarImageView, matrixProfileToolbarTitleView)) matrixProfileAppBarLayout.addOnOffsetChangedListener(appBarStateChangeListener) - roomProfileViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is RoomProfileViewEvents.Loading -> showLoading(it.message) - is RoomProfileViewEvents.Failure -> showFailure(it.throwable) - is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() - }.exhaustive - } - .disposeOnDestroyView() + roomProfileViewModel.observeViewEvents { + when (it) { + is RoomProfileViewEvents.Loading -> showLoading(it.message) + is RoomProfileViewEvents.Failure -> showFailure(it.throwable) + is RoomProfileViewEvents.OnLeaveRoomSuccess -> onLeaveRoom() + }.exhaustive + } roomListQuickActionsSharedActionViewModel .observe() .subscribe { handleQuickActions(it) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt index dbc9893308..d2e06f30e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/settings/RoomSettingsFragment.kt @@ -31,7 +31,6 @@ import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.roomprofile.RoomProfileArgs -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -55,16 +54,11 @@ class RoomSettingsFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true - viewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive - } - .disposeOnDestroyView() + viewModel.observeViewEvents { + when (it) { + is RoomSettingsViewEvents.Failure -> showFailure(it.throwable) + }.exhaustive + } } override fun onDestroyView() { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt index 49c423f0ac..f24fb2f542 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -36,7 +36,6 @@ import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.toast -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -63,17 +62,12 @@ class VectorSettingsDevicesFragment @Inject constructor( waiting_view_status_text.isVisible = true devicesController.callback = this recyclerView.configureWith(devicesController, showDivider = true) - viewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is DevicesViewEvents.Loading -> showLoading(it.message) - is DevicesViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive - } - .disposeOnDestroyView() + viewModel.observeViewEvents { + when (it) { + is DevicesViewEvents.Loading -> showLoading(it.message) + is DevicesViewEvents.Failure -> showFailure(it.throwable) + }.exhaustive + } viewModel.requestPasswordLiveData.observeEvent(this) { maybeShowDeleteDeviceWithPasswordDialog() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 5d12600b5c..c211d14edc 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -30,7 +30,6 @@ import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment -import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_generic_recycler.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import javax.inject.Inject @@ -51,17 +50,12 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( waiting_view_status_text.isVisible = true ignoredUsersController.callback = this recyclerView.configureWith(ignoredUsersController) - viewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - dismissLoadingDialog() - when (it) { - is IgnoredUsersViewEvents.Loading -> showLoading(it.message) - is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) - }.exhaustive - } - .disposeOnDestroyView() + viewModel.observeViewEvents { + when (it) { + is IgnoredUsersViewEvents.Loading -> showLoading(it.message) + is IgnoredUsersViewEvents.Failure -> showFailure(it.throwable) + }.exhaustive + } } override fun onDestroyView() { From 3c2fa40b58ad47ccb7eef46985f07d873e35cf77 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 17:57:06 +0100 Subject: [PATCH 11/28] Sharing things to RiotX: sort list by recent room first (#771) --- .idea/codeStyles/codeStyleConfig.xml | 1 + CHANGES.md | 2 +- .../api/session/room/model/RoomSummary.kt | 7 ++- .../database/mapper/RoomSummaryMapper.kt | 5 +- .../database/model/RoomSummaryEntity.kt | 54 +++++++++---------- .../session/room/DefaultRoomService.kt | 2 +- .../sync/UserAccountDataSyncHandler.kt | 18 +++++-- .../user/accountdata/SaveBreadcrumbsTask.kt | 5 +- .../room/list/BreadcrumbsRoomComparator.kt | 44 +++++++++++++++ .../room/list/ChronologicalRoomComparator.kt | 34 +++++------- .../features/share/IncomingShareViewModel.kt | 7 ++- 11 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/list/BreadcrumbsRoomComparator.kt diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index 79ee123c2b..6e6eec1148 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 7a39b8021d..54dd5e5247 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Enable encryption in unencrypted rooms, from the room settings (#212) Improvements πŸ™Œ: - - + - Sharing things to RiotX: sort list by recent room first (#771) Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 1f7a7b144a..7ec6254613 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -45,7 +45,8 @@ data class RoomSummary( val readMarkerId: String? = null, val userDrafts: List = emptyList(), var isEncrypted: Boolean, - val typingRoomMemberIds: List = emptyList() + val typingRoomMemberIds: List = emptyList(), + val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS ) { val isVersioned: Boolean @@ -53,4 +54,8 @@ data class RoomSummary( val hasNewMessages: Boolean get() = notificationCount != 0 + + companion object { + const val NOT_IN_BREADCRUMBS = -1 + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index 72d221aafe..896f994c99 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.database.model.RoomSummaryEntity -import java.util.* +import java.util.UUID import javax.inject.Inject internal class RoomSummaryMapper @Inject constructor( @@ -74,7 +74,8 @@ internal class RoomSummaryMapper @Inject constructor( canonicalAlias = roomSummaryEntity.canonicalAlias, aliases = roomSummaryEntity.aliases.toList(), isEncrypted = roomSummaryEntity.isEncrypted, - typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList() + typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(), + breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt index d857d8810c..c56e9ba10e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/RoomSummaryEntity.kt @@ -17,35 +17,37 @@ package im.vector.matrix.android.internal.database.model import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.VersioningState import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey -internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", - var displayName: String? = "", - var avatarUrl: String? = "", - var topic: String? = "", - var latestPreviewableEvent: TimelineEventEntity? = null, - var heroes: RealmList = RealmList(), - var joinedMembersCount: Int? = 0, - var invitedMembersCount: Int? = 0, - var isDirect: Boolean = false, - var directUserId: String? = null, - var otherMemberIds: RealmList = RealmList(), - var notificationCount: Int = 0, - var highlightCount: Int = 0, - var readMarkerId: String? = null, - var hasUnreadMessages: Boolean = false, - var tags: RealmList = RealmList(), - var userDrafts: UserDraftsEntity? = null, - var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, - var canonicalAlias: String? = null, - var aliases: RealmList = RealmList(), - // this is required for querying - var flatAliases: String = "", - var isEncrypted: Boolean = false, - var typingUserIds: RealmList = RealmList() +internal open class RoomSummaryEntity( + @PrimaryKey var roomId: String = "", + var displayName: String? = "", + var avatarUrl: String? = "", + var topic: String? = "", + var latestPreviewableEvent: TimelineEventEntity? = null, + var heroes: RealmList = RealmList(), + var joinedMembersCount: Int? = 0, + var invitedMembersCount: Int? = 0, + var isDirect: Boolean = false, + var directUserId: String? = null, + var otherMemberIds: RealmList = RealmList(), + var notificationCount: Int = 0, + var highlightCount: Int = 0, + var readMarkerId: String? = null, + var hasUnreadMessages: Boolean = false, + var tags: RealmList = RealmList(), + var userDrafts: UserDraftsEntity? = null, + var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS, + var canonicalAlias: String? = null, + var aliases: RealmList = RealmList(), + // this is required for querying + var flatAliases: String = "", + var isEncrypted: Boolean = false, + var typingUserIds: RealmList = RealmList() ) : RealmObject() { private var membershipStr: String = Membership.NONE.name @@ -66,7 +68,5 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "", versioningStateStr = value.name } - companion object { - const val NOT_IN_BREADCRUMBS = -1 - } + companion object } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 0cfc5aad3c..cf4c858bbd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -130,7 +130,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona return RoomSummaryEntity.where(realm) .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) .notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) - .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS) .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 07e8664102..f76c2ff448 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -21,16 +21,26 @@ import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.api.pushrules.RuleSetKey import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMemberContent +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.* +import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity +import im.vector.matrix.android.internal.database.model.IgnoredUserEntity +import im.vector.matrix.android.internal.database.model.PushRulesEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.query.getDirectRooms import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync -import im.vector.matrix.android.internal.session.sync.model.accountdata.* +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import io.realm.Realm @@ -177,10 +187,10 @@ internal class UserAccountDataSyncHandler @Inject constructor( // Update the room summaries // Reset all the indexes... RoomSummaryEntity.where(realm) - .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS) .findAll() .forEach { - it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS + it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS } // ...and apply new indexes diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt index 008dd1d652..ecdcdb8768 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/SaveBreadcrumbsTask.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.session.user.accountdata import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields @@ -50,10 +51,10 @@ internal class DefaultSaveBreadcrumbsTask @Inject constructor( // Update the room summaries // Reset all the indexes... RoomSummaryEntity.where(realm) - .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummary.NOT_IN_BREADCRUMBS) .findAll() .forEach { - it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS + it.breadcrumbsIndex = RoomSummary.NOT_IN_BREADCRUMBS } // ...and apply new indexes diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/BreadcrumbsRoomComparator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/BreadcrumbsRoomComparator.kt new file mode 100644 index 0000000000..3b858b39fb --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/BreadcrumbsRoomComparator.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.list + +import im.vector.matrix.android.api.session.room.model.RoomSummary +import javax.inject.Inject + +class BreadcrumbsRoomComparator @Inject constructor( + private val chronologicalRoomComparator: ChronologicalRoomComparator +) : Comparator { + + override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { + val leftBreadcrumbsIndex = leftRoomSummary?.breadcrumbsIndex ?: RoomSummary.NOT_IN_BREADCRUMBS + val rightBreadcrumbsIndex = rightRoomSummary?.breadcrumbsIndex ?: RoomSummary.NOT_IN_BREADCRUMBS + + return if (leftBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) { + if (rightBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) { + chronologicalRoomComparator.compare(leftRoomSummary, rightRoomSummary) + } else { + 1 + } + } else { + if (rightBreadcrumbsIndex == RoomSummary.NOT_IN_BREADCRUMBS) { + -1 + } else { + leftBreadcrumbsIndex - rightBreadcrumbsIndex + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/ChronologicalRoomComparator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/ChronologicalRoomComparator.kt index 047a518974..618d6c901b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/ChronologicalRoomComparator.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/ChronologicalRoomComparator.kt @@ -22,26 +22,20 @@ import javax.inject.Inject class ChronologicalRoomComparator @Inject constructor() : Comparator { override fun compare(leftRoomSummary: RoomSummary?, rightRoomSummary: RoomSummary?): Int { - var rightTimestamp = 0L - var leftTimestamp = 0L - if (null != leftRoomSummary) { - leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0 - } - if (null != rightRoomSummary) { - rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0 - } - return if (rightRoomSummary?.latestPreviewableEvent?.root == null) { - -1 - } else if (leftRoomSummary?.latestPreviewableEvent?.root == null) { - 1 - } else { - val deltaTimestamp = rightTimestamp - leftTimestamp - if (deltaTimestamp > 0) { - 1 - } else if (deltaTimestamp < 0) { - -1 - } else { - 0 + return when { + rightRoomSummary?.latestPreviewableEvent?.root == null -> -1 + leftRoomSummary?.latestPreviewableEvent?.root == null -> 1 + else -> { + val rightTimestamp = rightRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0 + val leftTimestamp = leftRoomSummary.latestPreviewableEvent?.root?.originServerTs ?: 0 + + val deltaTimestamp = rightTimestamp - leftTimestamp + + when { + deltaTimestamp > 0 -> 1 + deltaTimestamp < 0 -> -1 + else -> 0 + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt index 37b429d3d7..8b791fbf1b 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareViewModel.kt @@ -28,6 +28,7 @@ import im.vector.riotx.ActiveSessionDataSource import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.home.room.list.BreadcrumbsRoomComparator import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import java.util.concurrent.TimeUnit @@ -39,7 +40,8 @@ data class IncomingShareState(private val dummy: Boolean = false) : MvRxState */ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: IncomingShareState, private val sessionObservableStore: ActiveSessionDataSource, - private val shareRoomListObservableStore: ShareRoomListDataSource) + private val shareRoomListObservableStore: ShareRoomListDataSource, + private val breadcrumbsRoomComparator: BreadcrumbsRoomComparator) : VectorViewModel(initialState) { @AssistedInject.Factory @@ -69,6 +71,9 @@ class IncomingShareViewModel @AssistedInject constructor(@Assisted initialState: ?: Observable.just(emptyList()) } .throttleLast(300, TimeUnit.MILLISECONDS) + .map { + it.sortedWith(breadcrumbsRoomComparator) + } .subscribe { shareRoomListObservableStore.post(it) } From c971f18fc0d76c1cdbf47505570e68ea679fea2a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 19:02:41 +0100 Subject: [PATCH 12/28] Add section header when displaying room list to share (#771) --- .../home/room/list/RoomListViewModel.kt | 72 +++++++----- .../home/room/list/RoomListViewState.kt | 15 ++- .../home/room/list/RoomSummaryController.kt | 107 +++++++++++------- .../features/share/IncomingShareActivity.kt | 4 +- vector/src/main/res/values/strings_riotX.xml | 3 + 5 files changed, 132 insertions(+), 69 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt index f2f563cf26..397df50c2b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewModel.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.utils.DataSource +import im.vector.riotx.features.home.RoomListDisplayMode import io.reactivex.schedulers.Schedulers import timber.log.Timber import javax.inject.Inject @@ -202,35 +203,54 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun buildRoomSummaries(rooms: List): RoomSummaries { - // Set up init size on directChats and groupRooms as they are the biggest ones - val invites = ArrayList() - val favourites = ArrayList() - val directChats = ArrayList(rooms.size) - val groupRooms = ArrayList(rooms.size) - val lowPriorities = ArrayList() - val serverNotices = ArrayList() + if (displayMode == RoomListDisplayMode.SHARE) { + val recentRooms = ArrayList(20) + val otherRooms = ArrayList(rooms.size) - rooms - .filter { roomListDisplayModeFilter.test(it) } - .forEach { room -> - val tags = room.tags.map { it.name } - when { - room.membership == Membership.INVITE -> invites.add(room) - tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room) - tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room) - tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room) - room.isDirect -> directChats.add(room) - else -> groupRooms.add(room) + rooms + .filter { roomListDisplayModeFilter.test(it) } + .forEach { room -> + when (room.breadcrumbsIndex) { + RoomSummary.NOT_IN_BREADCRUMBS -> otherRooms.add(room) + else -> recentRooms.add(room) + } } - } - return RoomSummaries().apply { - put(RoomCategory.INVITE, invites) - put(RoomCategory.FAVOURITE, favourites) - put(RoomCategory.DIRECT, directChats) - put(RoomCategory.GROUP, groupRooms) - put(RoomCategory.LOW_PRIORITY, lowPriorities) - put(RoomCategory.SERVER_NOTICE, serverNotices) + return RoomSummaries().apply { + put(RoomCategory.RECENT_ROOMS, recentRooms) + put(RoomCategory.OTHER_ROOMS, otherRooms) + } + } else { + // Set up init size on directChats and groupRooms as they are the biggest ones + val invites = ArrayList() + val favourites = ArrayList() + val directChats = ArrayList(rooms.size) + val groupRooms = ArrayList(rooms.size) + val lowPriorities = ArrayList() + val serverNotices = ArrayList() + + rooms + .filter { roomListDisplayModeFilter.test(it) } + .forEach { room -> + val tags = room.tags.map { it.name } + when { + room.membership == Membership.INVITE -> invites.add(room) + tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room) + tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room) + tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room) + room.isDirect -> directChats.add(room) + else -> groupRooms.add(room) + } + } + + return RoomSummaries().apply { + put(RoomCategory.INVITE, invites) + put(RoomCategory.FAVOURITE, favourites) + put(RoomCategory.DIRECT, directChats) + put(RoomCategory.GROUP, groupRooms) + put(RoomCategory.LOW_PRIORITY, lowPriorities) + put(RoomCategory.SERVER_NOTICE, serverNotices) + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt index b41b4b9eeb..c127fa10e2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListViewState.kt @@ -43,7 +43,10 @@ data class RoomListViewState( val isDirectRoomsExpanded: Boolean = true, val isGroupRoomsExpanded: Boolean = true, val isLowPriorityRoomsExpanded: Boolean = true, - val isServerNoticeRoomsExpanded: Boolean = true + val isServerNoticeRoomsExpanded: Boolean = true, + // For sharing + val isRecentExpanded: Boolean = true, + val isOtherExpanded: Boolean = true ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) @@ -56,6 +59,8 @@ data class RoomListViewState( RoomCategory.GROUP -> isGroupRoomsExpanded RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded + RoomCategory.RECENT_ROOMS -> isRecentExpanded + RoomCategory.OTHER_ROOMS -> isOtherExpanded } } @@ -67,6 +72,8 @@ data class RoomListViewState( RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded) RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded) RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded) + RoomCategory.RECENT_ROOMS -> copy(isRecentExpanded = !isRecentExpanded) + RoomCategory.OTHER_ROOMS -> copy(isOtherExpanded = !isOtherExpanded) } } @@ -86,7 +93,11 @@ enum class RoomCategory(@StringRes val titleRes: Int) { DIRECT(R.string.bottom_action_people_x), GROUP(R.string.bottom_action_rooms), LOW_PRIORITY(R.string.low_priority_header), - SERVER_NOTICE(R.string.system_alerts_header) + SERVER_NOTICE(R.string.system_alerts_header), + + // For Sharing + RECENT_ROOMS(R.string.room_list_sharing_header_recent_rooms), + OTHER_ROOMS(R.string.room_list_sharing_header_other_rooms) } fun RoomSummaries?.isNullOrEmpty(): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt index 6ffe37cb15..c4afd442ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryController.kt @@ -59,39 +59,9 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri override fun buildModels() { val nonNullViewState = viewState ?: return when (nonNullViewState.displayMode) { - RoomListDisplayMode.FILTERED, - RoomListDisplayMode.SHARE -> { - buildFilteredRooms(nonNullViewState) - } - else -> { - var showHelp = false - val roomSummaries = nonNullViewState.asyncFilteredRooms() - roomSummaries?.forEach { (category, summaries) -> - if (summaries.isEmpty()) { - return@forEach - } else { - val isExpanded = nonNullViewState.isCategoryExpanded(category) - buildRoomCategory(nonNullViewState, summaries, category.titleRes, nonNullViewState.isCategoryExpanded(category)) { - listener?.onToggleRoomCategory(category) - } - if (isExpanded) { - buildRoomModels(summaries, - nonNullViewState.joiningRoomsIds, - nonNullViewState.joiningErrorRoomsIds, - nonNullViewState.rejectingRoomsIds, - nonNullViewState.rejectingErrorRoomsIds) - // Never set showHelp to true for invitation - if (category != RoomCategory.INVITE) { - showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp() - } - } - } - } - - if (showHelp) { - buildLongClickHelp() - } - } + RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState) + RoomListDisplayMode.SHARE -> buildShareRooms(nonNullViewState) + else -> buildRooms(nonNullViewState) } } @@ -109,9 +79,69 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri viewState.rejectingRoomsIds, viewState.rejectingErrorRoomsIds) - when { - viewState.displayMode == RoomListDisplayMode.FILTERED -> addFilterFooter(viewState) - filteredSummaries.isEmpty() -> addEmptyFooter() + addFilterFooter(viewState) + } + + private fun buildShareRooms(viewState: RoomListViewState) { + var hasResult = false + val roomSummaries = viewState.asyncFilteredRooms() + + roomListNameFilter.filter = viewState.roomFilter + + roomSummaries?.forEach { (category, summaries) -> + val filteredSummaries = summaries + .filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) } + + if (filteredSummaries.isEmpty()) { + return@forEach + } else { + hasResult = true + val isExpanded = viewState.isCategoryExpanded(category) + buildRoomCategory(viewState, emptyList(), category.titleRes, viewState.isCategoryExpanded(category)) { + listener?.onToggleRoomCategory(category) + } + if (isExpanded) { + buildRoomModels(filteredSummaries, + emptySet(), + emptySet(), + emptySet(), + emptySet() + ) + } + } + } + if (!hasResult) { + addNoResultItem() + } + } + + private fun buildRooms(viewState: RoomListViewState) { + var showHelp = false + val roomSummaries = viewState.asyncFilteredRooms() + roomSummaries?.forEach { (category, summaries) -> + if (summaries.isEmpty()) { + return@forEach + } else { + val isExpanded = viewState.isCategoryExpanded(category) + buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) { + listener?.onToggleRoomCategory(category) + } + if (isExpanded) { + buildRoomModels(summaries, + viewState.joiningRoomsIds, + viewState.joiningErrorRoomsIds, + viewState.rejectingRoomsIds, + viewState.rejectingErrorRoomsIds) + // Never set showHelp to true for invitation + if (category != RoomCategory.INVITE) { + showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp() + } + } + } + } + + if (showHelp) { + buildLongClickHelp() } } @@ -130,7 +160,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri } } - private fun addEmptyFooter() { + private fun addNoResultItem() { noResultItem { id("no_result") text(stringProvider.getString(R.string.no_result_placeholder)) @@ -142,9 +172,6 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) { - if (summaries.isEmpty()) { - return - } // TODO should add some business logic later val unreadCount = if (summaries.isEmpty()) { 0 diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt index e48c8246d2..3669a51937 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareActivity.kt @@ -44,7 +44,9 @@ class IncomingShareActivity : @Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var incomingShareViewModelFactory: IncomingShareViewModel.Factory private lateinit var attachmentsHelper: AttachmentsHelper - private val incomingShareViewModel: IncomingShareViewModel by viewModel() + // Do not remove, even if not used, it instantiates the view model + @Suppress("unused") + private val viewModel: IncomingShareViewModel by viewModel() private val roomListFragment: RoomListFragment? get() { return supportFragmentManager.findFragmentById(R.id.shareRoomListFragmentContainer) as? RoomListFragment diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 4ca908128e..781912fbe5 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -34,6 +34,9 @@ Unignore + Recent rooms + Other rooms + Timeline From 426e291ce98888116034cf707edc3229c74e127e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 14:46:36 +0100 Subject: [PATCH 13/28] i18n for RiotX limitation messages --- .../timeline/factory/DefaultItemFactory.kt | 9 +++-- .../timeline/factory/MessageItemFactory.kt | 33 ++++++++++++++++--- vector/src/main/res/values/strings_riotX.xml | 4 +++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index d9bed98b1f..377975554a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -17,9 +17,11 @@ package im.vector.riotx.features.home.room.detail.timeline.factory import im.vector.matrix.android.api.session.room.timeline.TimelineEvent -import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.R +import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_ @@ -28,6 +30,7 @@ import javax.inject.Inject class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider, private val informationDataFactory: MessageInformationDataFactory) { fun create(text: String, @@ -49,9 +52,9 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava callback: TimelineEventController.Callback?, throwable: Throwable? = null): DefaultItem { val text = if (throwable == null) { - "${event.root.getClearType()} events are not yet handled" + stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType()) } else { - "an exception occurred when rendering the event ${event.root.eventId}" + stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId) } val informationData = informationDataFactory.create(event, null) return create(text, informationData, highlight, callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9e05cdcc18..3febf19208 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -26,7 +26,16 @@ import android.view.View import dagger.Lazy import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent +import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent +import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent +import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt @@ -40,8 +49,24 @@ import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.containsOnlyEmojis import im.vector.riotx.core.utils.isLocalFile import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.home.room.detail.timeline.helper.* -import im.vector.riotx.features.home.room.detail.timeline.item.* +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem +import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem +import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_ +import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem +import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.riotx.features.home.room.detail.timeline.tools.linkify import im.vector.riotx.features.html.CodeVisitor @@ -153,7 +178,7 @@ class MessageItemFactory @Inject constructor( informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?): DefaultItem? { - val text = "${messageContent.type} message events are not yet handled" + val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.type) return defaultItemFactory.create(text, informationData, highlight, callback) } diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 781912fbe5..89127d4e25 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -32,6 +32,10 @@ Jump to read receipt + "RiotX does not handle events of type '%1$s' (yet)" + "RiotX does not handle message of type '%1$s' (yet)" + "RiotX encountered an issue when rendering content of event with id '%1$s'" + Unignore Recent rooms From d530c64a84572d153e427b67177375daf462950b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 15:35:46 +0100 Subject: [PATCH 14/28] Render defaultItem as other item: display user avatar Also ensure bottom sheet always has a header, for user avatar and date --- .../action/MessageActionsEpoxyController.kt | 19 +++++----- .../action/MessageActionsViewModel.kt | 25 ++++++++----- .../detail/timeline/item/BaseEventItem.kt | 2 ++ .../room/detail/timeline/item/DefaultItem.kt | 8 +++-- .../item_timeline_event_base_noinfo.xml | 9 +++-- .../item_timeline_event_default_stub.xml | 35 ++++++++++++++----- .../item_timeline_event_notice_stub.xml | 8 ++--- 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index f90dbed95e..bdfdb02be1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -46,17 +46,14 @@ class MessageActionsEpoxyController @Inject constructor( override fun buildModels(state: MessageActionState) { // Message preview - val body = state.messageBody - if (body != null) { - bottomSheetMessagePreviewItem { - id("preview") - avatarRenderer(avatarRenderer) - matrixItem(state.informationData.matrixItem) - movementMethod(createLinkMovementMethod(listener)) - userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } - body(body.linkify(listener)) - time(state.time()) - } + bottomSheetMessagePreviewItem { + id("preview") + avatarRenderer(avatarRenderer) + matrixItem(state.informationData.matrixItem) + movementMethod(createLinkMovementMethod(listener)) + userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } + body(state.messageBody.linkify(listener)) + time(state.time()) } // Send state diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 29068b07df..936bc263e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -15,7 +15,12 @@ */ package im.vector.riotx.features.home.room.detail.timeline.action -import com.airbnb.mvrx.* +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import dagger.Lazy @@ -45,7 +50,8 @@ import im.vector.riotx.features.html.VectorHtmlCompressor import im.vector.riotx.features.reactions.data.EmojiDataSource import im.vector.riotx.features.settings.VectorPreferences import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale /** * Quick reactions state @@ -60,7 +66,7 @@ data class MessageActionState( val eventId: String, val informationData: MessageInformationData, val timelineEvent: Async = Uninitialized, - val messageBody: CharSequence? = null, + val messageBody: CharSequence = "", // For quick reactions val quickStates: Async> = Uninitialized, // For actions @@ -155,13 +161,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeTimelineEventState() { asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent -> - val computedMessage = computeMessageBody(timelineEvent) - val actions = actionsForEvent(timelineEvent) - setState { copy(messageBody = computedMessage, actions = actions) } + setState { + copy( + messageBody = computeMessageBody(timelineEvent), + actions = actionsForEvent(timelineEvent) + ) + } } } - private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? { + private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence { return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, EventType.STICKER -> { @@ -189,7 +198,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted noticeEventFormatter.format(timelineEvent) } else -> null - } + } ?: "" } private fun actionsForEvent(timelineEvent: TimelineEvent): List { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt index 02b7341c72..f674cfa0f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.view.View import android.view.ViewStub import android.widget.RelativeLayout +import androidx.annotation.CallSuper import androidx.annotation.IdRes import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute @@ -42,6 +43,7 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute lateinit var dimensionConverter: DimensionConverter + @CallSuper override fun bind(holder: H) { super.bind(holder) holder.leftGuideline.updateLayoutParams { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index dc52293292..3bf564577f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.view.View +import android.widget.ImageView import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -50,8 +51,10 @@ abstract class DefaultItem : BaseEventItem() { var text: CharSequence? = null override fun bind(holder: Holder) { - holder.messageView.text = text + super.bind(holder) holder.view.setOnLongClickListener(longClickListener) + avatarRenderer.render(informationData.matrixItem, holder.avatarView) + holder.messageView.text = text holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) } @@ -62,7 +65,8 @@ abstract class DefaultItem : BaseEventItem() { override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { - val messageView by bind(R.id.stateMessageView) + val avatarView by bind(R.id.itemDefaultAvatarView) + val messageView by bind(R.id.itemDefaultTextView) } companion object { diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index c1987dccb2..ebc32baa57 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -36,8 +36,9 @@ + android:layout="@layout/item_timeline_event_default_stub" + tools:layout_marginTop="80dp" + tools:visibility="visible" /> + android:layout="@layout/item_timeline_event_merged_header_stub" + tools:layout_marginTop="160dp" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/item_timeline_event_default_stub.xml b/vector/src/main/res/layout/item_timeline_event_default_stub.xml index 345bda0b7e..68c8936b32 100644 --- a/vector/src/main/res/layout/item_timeline_event_default_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_default_stub.xml @@ -1,12 +1,31 @@ - \ No newline at end of file + android:orientation="horizontal"> + + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml index 76190062b1..9aacf357f1 100644 --- a/vector/src/main/res/layout/item_timeline_event_notice_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_notice_stub.xml @@ -3,8 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:orientation="horizontal" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="horizontal"> + tools:text="@string/notice_avatar_url_changed" /> \ No newline at end of file From 632832a651340413be0940b73f0d7d10822019cd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 15:44:41 +0100 Subject: [PATCH 15/28] Nearly same code for DefaultItem and NoticeItem --- .../timeline/factory/DefaultItemFactory.kt | 16 +++++--- .../room/detail/timeline/item/DefaultItem.kt | 40 ++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 377975554a..89e21e04a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.timeline.factory +import android.view.View import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider @@ -37,14 +38,19 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?): DefaultItem { + val attributes = DefaultItem.Attributes( + avatarRenderer = avatarRenderer, + informationData = informationData, + text = text, + itemLongClickListener = View.OnLongClickListener { view -> + callback?.onEventLongClicked(informationData, null, view) ?: false + }, + readReceiptsCallback = callback + ) return DefaultItem_() .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(highlight) - .text(text) - .avatarRenderer(avatarRenderer) - .informationData(informationData) - .baseCallback(callback) - .readReceiptsCallback(callback) + .attributes(attributes) } fun create(event: TimelineEvent, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index 3bf564577f..0ccc982c4c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -30,45 +30,39 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle abstract class DefaultItem : BaseEventItem() { @EpoxyAttribute - lateinit var informationData: MessageInformationData - @EpoxyAttribute - lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute - var baseCallback: TimelineEventController.BaseCallback? = null - - private var longClickListener = View.OnLongClickListener { - return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true - } - - @EpoxyAttribute - var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null + lateinit var attributes: Attributes private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { - readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts) + attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) }) - @EpoxyAttribute - var text: CharSequence? = null - override fun bind(holder: Holder) { super.bind(holder) - holder.view.setOnLongClickListener(longClickListener) - avatarRenderer.render(informationData.matrixItem, holder.avatarView) - holder.messageView.text = text - holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) + holder.messageTextView.text = attributes.text + attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) + holder.view.setOnLongClickListener(attributes.itemLongClickListener) + holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) } override fun getEventIds(): List { - return listOf(informationData.eventId) + return listOf(attributes.informationData.eventId) } override fun getViewType() = STUB_ID class Holder : BaseHolder(STUB_ID) { - val avatarView by bind(R.id.itemDefaultAvatarView) - val messageView by bind(R.id.itemDefaultTextView) + val avatarImageView by bind(R.id.itemDefaultAvatarView) + val messageTextView by bind(R.id.itemDefaultTextView) } + data class Attributes( + val avatarRenderer: AvatarRenderer, + val informationData: MessageInformationData, + val text: CharSequence, + val itemLongClickListener: View.OnLongClickListener? = null, + val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null + ) + companion object { private const val STUB_ID = R.id.messageContentDefaultStub } From e9ea69f055ba4492c3e6e42b9afcbf0a15ef9236 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 23 Jan 2020 23:25:26 +0100 Subject: [PATCH 16/28] Add support for /rainbow and /rainbowme command (#879) --- CHANGES.md | 2 +- .../vector/riotx/features/command/Command.kt | 2 + .../riotx/features/command/CommandParser.kt | 10 +++ .../riotx/features/command/ParsedCommand.kt | 2 + .../home/room/detail/RoomDetailViewModel.kt | 21 ++++- .../composer/rainbow/RainbowGenerator.kt | 87 +++++++++++++++++++ .../room/detail/composer/rainbow/RgbColor.kt | 30 +++++++ vector/src/main/res/values/strings_riotX.xml | 3 + 8 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..b8b5dd4b36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements πŸ™Œ: - Sharing things to RiotX: sort list by recent room first (#771) Other changes: - - + - Add support for /rainbow and /rainbowme commands (#879) Bugfix πŸ›: - diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 8b72ffa4a6..6151ae0d66 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -37,6 +37,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d KICK_USER("/kick", " [reason]", R.string.command_description_kick_user), CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), MARKDOWN("/markdown", "", R.string.command_description_markdown), + RAINBOW("/rainbow", "", R.string.command_description_rainbow), + RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), SPOILER("/spoiler", "", R.string.command_description_spoiler); diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 359f2c1f13..58671df539 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -80,6 +80,16 @@ object CommandParser { ParsedCommand.SendEmote(message) } + Command.RAINBOW.command -> { + val message = textMessage.subSequence(Command.RAINBOW.command.length, textMessage.length).trim() + + ParsedCommand.SendRainbow(message) + } + Command.RAINBOW_EMOTE.command -> { + val message = textMessage.subSequence(Command.RAINBOW_EMOTE.command.length, textMessage.length).trim() + + ParsedCommand.SendRainbowEmote(message) + } Command.JOIN_ROOM.command -> { if (messageParts.size >= 2) { val roomAlias = messageParts[1] diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index dd7c0c7e86..c43b78d71c 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -34,6 +34,8 @@ sealed class ParsedCommand { // Valid commands: class SendEmote(val message: CharSequence) : ParsedCommand() + class SendRainbow(val message: CharSequence) : ParsedCommand() + class SendRainbowEmote(val message: CharSequence) : ParsedCommand() class BanUser(val userId: String, val reason: String?) : ParsedCommand() class UnbanUser(val userId: String, val reason: String?) : ParsedCommand() class SetUserPowerLevel(val userId: String, val powerLevel: Int) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 36cbdcaa75..867a382bec 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -56,6 +56,7 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider @@ -64,6 +65,7 @@ import im.vector.riotx.core.utils.LiveEvent import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand +import im.vector.riotx.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.typing.TypingHelper import im.vector.riotx.features.settings.VectorPreferences @@ -83,6 +85,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, + private val rainbowGenerator: RainbowGenerator, private val session: Session ) : VectorViewModel(initialState), Timeline.Listener { @@ -385,6 +388,20 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } + is ParsedCommand.SendRainbow -> { + slashCommandResult.message.toString().let { + room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) + } + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.SendRainbowEmote -> { + slashCommandResult.message.toString().let { + room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) + } + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } is ParsedCommand.SendSpoiler -> { room.sendFormattedTextMessage( "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", @@ -401,7 +418,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro // TODO _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) } - } + }.exhaustive } is SendMode.EDIT -> { // is original event a reply? @@ -459,7 +476,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro popDraft() } } - } + }.exhaustive } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt new file mode 100644 index 0000000000..c9defac8e8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.composer.rainbow + +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +/** + * Inspired from React-Sdk + * Ref: https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/utils/colour.js + */ +class RainbowGenerator @Inject constructor() { + + fun generate(text: String): String { + val frequency = 360f / text.length + + return text + .mapIndexed { idx, letter -> + // Do better than React-Sdk: Avoid adding font color for spaces + if (letter == ' ') { + "$letter" + } else { + val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor() + "$letter" + } + } + .joinToString(separator = "") + } + + private fun hueToRGB(h: Float, s: Float, l: Float): RgbColor { + val c = s * (1 - abs(2 * l - 1)) + val x = c * (1 - abs((h / 60) % 2 - 1)) + val m = l - c / 2 + + var r = 0f + var g = 0f + var b = 0f + + when { + h < 60f -> { + r = c + g = x + } + h < 120f -> { + r = x + g = c + } + h < 180f -> { + g = c + b = x + } + h < 240f -> { + g = x + b = c + } + h < 300f -> { + r = x + b = c + } + else -> { + r = c + b = x + } + } + + return RgbColor( + ((r + m) * 255).roundToInt(), + ((g + m) * 255).roundToInt(), + ((b + m) * 255).roundToInt() + ) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt new file mode 100644 index 0000000000..bf2e808a36 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RgbColor.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.composer.rainbow + +data class RgbColor( + val r: Int, + val g: Int, + val b: Int +) + +fun RgbColor.toDashColor(): String { + return listOf(r, g, b) + .joinToString(separator = "", prefix = "#") { + it.toString(16).padStart(2, '0') + } +} diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 781912fbe5..98064c0148 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -37,6 +37,9 @@ Recent rooms Other rooms + Sends the given message colored as a rainbow + Sends the given emote colored as a rainbow + Timeline From f3e88c75cfc3deb566db55aca8f399a1e933761d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 09:42:56 +0100 Subject: [PATCH 17/28] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9f94454cd..1848c7baba 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f6 # New Android SDK -RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough. +RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the SDK is stable enough. # Roadmap From 976a8fc568b0dcef0def649199080e18f518ff18 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 16:36:28 +0100 Subject: [PATCH 18/28] Hide the algorithm when turning on e2e (#897) --- CHANGES.md | 1 + .../src/main/res/values/strings_RiotX.xml | 3 +++ .../timeline/format/NoticeEventFormatter.kt | 20 +++++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..fb1ed4371d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements πŸ™Œ: - Sharing things to RiotX: sort list by recent room first (#771) + - Hide the algorithm when turning on e2e (#897) Other changes: - diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index d781ec5f1e..d030b857cf 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -1,4 +1,7 @@ + %1$s turned on end-to-end encryption. + %1$s turned on end-to-end encryption (unrecognised algorithm %2$s). + diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 563a970cfb..16275e6a73 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -19,9 +19,21 @@ package im.vector.riotx.features.home.room.detail.timeline.format import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.* +import im.vector.matrix.android.api.session.room.model.GuestAccess +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomAliasesContent +import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent +import im.vector.matrix.android.api.session.room.model.RoomGuestAccessContent +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibilityContent +import im.vector.matrix.android.api.session.room.model.RoomJoinRules +import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent +import im.vector.matrix.android.api.session.room.model.RoomMemberContent +import im.vector.matrix.android.api.session.room.model.RoomNameContent +import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.model.event.EncryptionEventContent import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder @@ -180,7 +192,11 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active private fun formatRoomEncryptionEvent(event: Event, senderName: String?): CharSequence? { val content = event.content.toModel() ?: return null - return sp.getString(R.string.notice_end_to_end, senderName, content.algorithm) + return if (content.algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { + sp.getString(R.string.notice_end_to_end_ok, senderName) + } else { + sp.getString(R.string.notice_end_to_end_unknown_algorithm, senderName, content.algorithm) + } } private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String { From 2eeeea33770a3725b34ea8c67eb92947864c7712 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 17:19:22 +0100 Subject: [PATCH 19/28] Encryption is enabled only for MEGOLM. --- .../internal/crypto/DefaultCryptoService.kt | 25 ++++++++++++++----- .../session/room/RoomSummaryUpdater.kt | 14 ++++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 783b7eb4ad..0cf378aed9 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -56,9 +56,15 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.* +import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask +import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask +import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask +import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask +import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask +import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.extensions.foldToCallback @@ -71,7 +77,12 @@ import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.fetchCopied -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -475,14 +486,16 @@ internal class DefaultCryptoService @Inject constructor( } /** - * Tells if a room is encrypted + * Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM * * @param roomId the room id - * @return true if the room is encrypted + * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM */ override fun isRoomEncrypted(roomId: String): Boolean { - val encryptionEvent = monarchy.fetchCopied { - EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst() + val encryptionEvent = monarchy.fetchCopied { realm -> + EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) + .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") + .findFirst() } return encryptionEvent != null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index bbb5feba15..e65a1eb73e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -23,12 +23,18 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.database.mapper.ContentMapper -import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.query.* +import im.vector.matrix.android.internal.database.query.getOrCreate +import im.vector.matrix.android.internal.database.query.isEventRead +import im.vector.matrix.android.internal.database.query.latestEvent +import im.vector.matrix.android.internal.database.query.prev +import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper @@ -92,7 +98,9 @@ internal class RoomSummaryUpdater @Inject constructor( val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() - val encryptionEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ENCRYPTION).prev() + val encryptionEvent = EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) + .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") + .prev() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events From ef0b438a89d4774d9c38454ca62bf6febcca5059 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 15:42:08 +0100 Subject: [PATCH 20/28] Give the possibility to enable encryption when creating room (#837) --- CHANGES.md | 1 + .../roomdirectory/createroom/CreateRoomAction.kt | 1 + .../createroom/CreateRoomController.kt | 16 +++++++++++++--- .../createroom/CreateRoomFragment.kt | 4 ++++ .../createroom/CreateRoomViewModel.kt | 9 +++++++++ .../createroom/CreateRoomViewState.kt | 1 + vector/src/main/res/values/strings_riotX.xml | 3 +++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 54dd5e5247..adca5d9331 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Changes in RiotX 0.14.0 (2020-XX-XX) Features ✨: - Enable encryption in unencrypted rooms, from the room settings (#212) + - Enable e2e by default when creating DM, and give the possibility to enable encryption when creating room (#837) Improvements πŸ™Œ: - Sharing things to RiotX: sort list by recent room first (#771) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt index 333834ca3c..8986db180a 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomAction.kt @@ -22,5 +22,6 @@ sealed class CreateRoomAction : VectorViewModelAction { data class SetName(val name: String) : CreateRoomAction() data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction() data class SetIsInRoomDirectory(val isInRoomDirectory: Boolean) : CreateRoomAction() + data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction() object Create : CreateRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt index 2477e6fab0..92e178c628 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt @@ -39,9 +39,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin var index = 0 override fun buildModels(viewState: CreateRoomViewState) { - val asyncCreateRoom = viewState.asyncCreateRoomRequest - - when (asyncCreateRoom) { + when (val asyncCreateRoom = viewState.asyncCreateRoomRequest) { is Success -> { // Nothing to display, the screen will be closed } @@ -101,12 +99,24 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin listener?.setIsInRoomDirectory(value) } } + formSwitchItem { + id("encryption") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_encryption_title)) + summary(stringProvider.getString(R.string.create_room_encryption_description)) + switchChecked(viewState.isEncrypted) + + listener { value -> + listener?.setIsEncrypted(value) + } + } } interface Listener { fun onNameChange(newName: String) fun setIsPublic(isPublic: Boolean) fun setIsInRoomDirectory(isInRoomDirectory: Boolean) + fun setIsEncrypted(isEncrypted: Boolean) fun retry() } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index aacc21916a..827db96783 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -85,6 +85,10 @@ class CreateRoomFragment @Inject constructor(private val createRoomController: C viewModel.handle(CreateRoomAction.SetIsInRoomDirectory(isInRoomDirectory)) } + override fun setIsEncrypted(isEncrypted: Boolean) { + viewModel.handle(CreateRoomAction.SetIsEncrypted(isEncrypted)) + } + override fun retry() { Timber.v("Retry") viewModel.handle(CreateRoomAction.Create) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index 457574e736..ee170de0e0 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity @@ -62,6 +63,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr is CreateRoomAction.SetName -> setName(action) is CreateRoomAction.SetIsPublic -> setIsPublic(action) is CreateRoomAction.SetIsInRoomDirectory -> setIsInRoomDirectory(action) + is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) is CreateRoomAction.Create -> doCreateRoom() } } @@ -72,6 +74,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) } + private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) } + private fun doCreateRoom() = withState { state -> if (state.asyncCreateRoomRequest is Loading || state.asyncCreateRoomRequest is Success) { return@withState @@ -89,6 +93,11 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr // Public room preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + + // Encryption + if (state.isEncrypted) { + enableEncryptionWithAlgorithm(MXCRYPTO_ALGORITHM_MEGOLM) + } } session.createRoom(createRoomParams, object : MatrixCallback { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt index 363d31edc6..810319d54f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -24,5 +24,6 @@ data class CreateRoomViewState( val roomName: String = "", val isPublic: Boolean = false, val isInRoomDirectory: Boolean = false, + val isEncrypted: Boolean = false, val asyncCreateRoomRequest: Async = Uninitialized ) : MvRxState diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 89127d4e25..5fc94d243d 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -3,6 +3,9 @@ + "Enable encryption" + "Once enabled, encryption cannot be disabled." + Your email domain is not authorized to register on this server Messages in this room are not end-to-end encrypted. From da9b9f486447385fc718ca4fbad46389bb8df07d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 21 Jan 2020 15:48:35 +0100 Subject: [PATCH 21/28] Make the whole cell clickable --- .../java/im/vector/riotx/features/form/FormSwitchItem.kt | 6 ++++++ vector/src/main/res/layout/item_form_switch.xml | 1 + 2 files changed, 7 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt index 2e48a8b709..1f3c7c81bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/form/FormSwitchItem.kt @@ -44,6 +44,12 @@ abstract class FormSwitchItem : VectorEpoxyModel() { var summary: String? = null override fun bind(holder: Holder) { + holder.view.setOnClickListener { + if (enabled) { + holder.switchView.toggle() + } + } + holder.titleView.text = title holder.summaryView.setTextOrHide(summary) diff --git a/vector/src/main/res/layout/item_form_switch.xml b/vector/src/main/res/layout/item_form_switch.xml index 5757c4b853..3583ac8024 100644 --- a/vector/src/main/res/layout/item_form_switch.xml +++ b/vector/src/main/res/layout/item_form_switch.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?riotx_background" + android:foreground="?attr/selectableItemBackground" android:minHeight="@dimen/item_form_min_height"> Date: Tue, 28 Jan 2020 21:56:02 +0100 Subject: [PATCH 22/28] Add TUs for RainbowGenerator (not all passing) --- .../composer/rainbow/RainbowGeneratorTest.kt | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt new file mode 100644 index 0000000000..521fb15bf2 --- /dev/null +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.composer.rainbow + +import org.junit.Assert.assertEquals +import org.junit.Test + +class RainbowGeneratorTest { + + private val rainbowGenerator = RainbowGenerator() + + @Test + fun testEmpty() { + assertEquals("", rainbowGenerator.generate("")) + } + + @Test + fun testAscii1() { + assertEquals("""a""", rainbowGenerator.generate("a")) + } + + @Test + fun testAscii2() { + val expected = + """ + T + h + i + s + + i + s + + a + + r + a + i + n + b + o + w + ! + """ + .trimIndent() + .replace("\n", "") + + assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) + } + + @Test + fun testEmoji1() { + assertEquals("""\uD83E\uDD1E""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🀞 + } + + @Test + fun testEmoji2() { + assertEquals("""🀞""", rainbowGenerator.generate("🀞")) + } + + @Test + fun testEmojiMix() { + val expected = """ + T + h + i + s + + i + s + + a + + r + a + i + n + b + o + w + ! + """ + .trimIndent() + .replace("\n", "") + + assertEquals(expected, rainbowGenerator.generate("Hello 🀞 world!")) + } +} \ No newline at end of file From b2338dfcd39da4795b5923749dd4fe2731022067 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 22:35:40 +0100 Subject: [PATCH 23/28] Make the TU passes --- .../java/im/vector/riotx/core/utils/Emoji.kt | 36 +++++++++++++++++++ .../composer/rainbow/RainbowGenerator.kt | 4 ++- .../composer/rainbow/RainbowGeneratorTest.kt | 30 +++++++--------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt index f9e5654726..e91a2896bc 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/Emoji.kt @@ -113,3 +113,39 @@ fun containsOnlyEmojis(str: String?): Boolean { return res } + +/** + * Same as split, but considering emojis + */ +fun CharSequence.splitEmoji(): List { + val result = mutableListOf() + + var index = 0 + + while (index < length) { + val firstChar = get(index) + + if (firstChar.toInt() == 0x200e) { + // Left to right mark. What should I do with it? + } else if (firstChar.toInt() in 0xD800..0xDBFF && index + 1 < length) { + // We have the start of a surrogate pair + val secondChar = get(index + 1) + + if (secondChar.toInt() in 0xDC00..0xDFFF) { + // We have an emoji + result.add("$firstChar$secondChar") + index++ + } else { + // Not sure what we have here... + result.add("$firstChar") + } + } else { + // Regular char + result.add("$firstChar") + } + + index++ + } + + return result +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt index c9defac8e8..9662e2d372 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.composer.rainbow +import im.vector.riotx.core.utils.splitEmoji import javax.inject.Inject import kotlin.math.abs import kotlin.math.roundToInt @@ -30,9 +31,10 @@ class RainbowGenerator @Inject constructor() { val frequency = 360f / text.length return text + .splitEmoji() .mapIndexed { idx, letter -> // Do better than React-Sdk: Avoid adding font color for spaces - if (letter == ' ') { + if (letter == " ") { "$letter" } else { val dashColor = hueToRGB(idx * frequency, 1.0f, 0.5f).toDashColor() diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 521fb15bf2..6a798638f2 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -64,7 +64,7 @@ class RainbowGeneratorTest { @Test fun testEmoji1() { - assertEquals("""\uD83E\uDD1E""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🀞 + assertEquals("""🀞""", rainbowGenerator.generate("\uD83E\uDD1E")) // 🀞 } @Test @@ -75,24 +75,20 @@ class RainbowGeneratorTest { @Test fun testEmojiMix() { val expected = """ - T - h - i - s + H + e + l + l + o - i - s + 🀞 - a - - r - a - i - n - b - o - w - ! + w + o + r + l + d + ! """ .trimIndent() .replace("\n", "") From 007b0cabf21323f773e63dd1e9563379466a44c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Jan 2020 22:43:10 +0100 Subject: [PATCH 24/28] Add a few TUs --- .../composer/rainbow/RainbowGeneratorTest.kt | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 6a798638f2..3e1092d288 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -56,8 +56,7 @@ class RainbowGeneratorTest { w ! """ - .trimIndent() - .replace("\n", "") + .trimIndentOnLine() assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) } @@ -73,7 +72,18 @@ class RainbowGeneratorTest { } @Test - fun testEmojiMix() { + fun testEmoji3() { + val expected = """ + 🀞 + πŸ™‚ + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("πŸ€žπŸ™‚")) + } + + @Test + fun testEmojiMix1() { val expected = """ H e @@ -90,9 +100,37 @@ class RainbowGeneratorTest { d ! """ - .trimIndent() - .replace("\n", "") + .trimIndentOnLine() assertEquals(expected, rainbowGenerator.generate("Hello 🀞 world!")) } -} \ No newline at end of file + + @Test + fun testEmojiMix2() { + val expected = """ + a + 🀞 + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("a🀞")) + } + + @Test + fun testEmojiMix3() { + val expected = """ + 🀞 + a + """ + .trimIndentOnLine() + + assertEquals(expected, rainbowGenerator.generate("🀞a")) + } + + @Test + fun testError1() { + assertEquals("\uD83E", rainbowGenerator.generate("\uD83E")) + } +} + +fun String.trimIndentOnLine() = trimIndent().replace("\n", "") \ No newline at end of file From 27fe4e3680193641f1ff03a9cb4b0d620ac5d4f8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Jan 2020 09:44:53 +0100 Subject: [PATCH 25/28] Fix build and add tests --- .../composer/rainbow/RainbowGenerator.kt | 6 +- .../composer/rainbow/RainbowGeneratorTest.kt | 60 ++++++++++--------- .../java/im/vector/riotx/test/Extensions.kt | 19 ++++++ 3 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 vector/src/test/java/im/vector/riotx/test/Extensions.kt diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt index 9662e2d372..3868be4e2e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGenerator.kt @@ -28,10 +28,10 @@ import kotlin.math.roundToInt class RainbowGenerator @Inject constructor() { fun generate(text: String): String { - val frequency = 360f / text.length + val split = text.splitEmoji() + val frequency = 360f / split.size - return text - .splitEmoji() + return split .mapIndexed { idx, letter -> // Do better than React-Sdk: Avoid adding font color for spaces if (letter == " ") { diff --git a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt index 3e1092d288..5a9fdc0ab7 100644 --- a/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt +++ b/vector/src/test/java/im/vector/riotx/features/home/room/detail/composer/rainbow/RainbowGeneratorTest.kt @@ -16,9 +16,11 @@ package im.vector.riotx.features.home.room.detail.composer.rainbow +import im.vector.riotx.test.trimIndentOneLine import org.junit.Assert.assertEquals import org.junit.Test +@Suppress("SpellCheckingInspection") class RainbowGeneratorTest { private val rainbowGenerator = RainbowGenerator() @@ -35,8 +37,17 @@ class RainbowGeneratorTest { @Test fun testAscii2() { - val expected = - """ + val expected = """ + a + b + """.trimIndentOneLine() + + assertEquals(expected, rainbowGenerator.generate("ab")) + } + + @Test + fun testAscii3() { + val expected = """ T h i @@ -55,8 +66,7 @@ class RainbowGeneratorTest { o w ! - """ - .trimIndentOnLine() + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("This is a rainbow!")) } @@ -75,9 +85,8 @@ class RainbowGeneratorTest { fun testEmoji3() { val expected = """ 🀞 - πŸ™‚ - """ - .trimIndentOnLine() + πŸ™‚ + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("πŸ€žπŸ™‚")) } @@ -86,21 +95,20 @@ class RainbowGeneratorTest { fun testEmojiMix1() { val expected = """ H - e - l - l - o + e + l + l + o - 🀞 + 🀞 - w - o - r - l - d - ! - """ - .trimIndentOnLine() + w + o + r + l + d + ! + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("Hello 🀞 world!")) } @@ -109,9 +117,8 @@ class RainbowGeneratorTest { fun testEmojiMix2() { val expected = """ a - 🀞 - """ - .trimIndentOnLine() + 🀞 + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("a🀞")) } @@ -120,9 +127,8 @@ class RainbowGeneratorTest { fun testEmojiMix3() { val expected = """ 🀞 - a - """ - .trimIndentOnLine() + a + """.trimIndentOneLine() assertEquals(expected, rainbowGenerator.generate("🀞a")) } @@ -132,5 +138,3 @@ class RainbowGeneratorTest { assertEquals("\uD83E", rainbowGenerator.generate("\uD83E")) } } - -fun String.trimIndentOnLine() = trimIndent().replace("\n", "") \ No newline at end of file diff --git a/vector/src/test/java/im/vector/riotx/test/Extensions.kt b/vector/src/test/java/im/vector/riotx/test/Extensions.kt new file mode 100644 index 0000000000..31781ce00e --- /dev/null +++ b/vector/src/test/java/im/vector/riotx/test/Extensions.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.test + +fun String.trimIndentOneLine() = trimIndent().replace("\n", "") From 57a13fa30df86c12177a0600c478822f1af6b9b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 29 Jan 2020 18:07:57 +0100 Subject: [PATCH 26/28] Sort room members by display names --- CHANGES.md | 1 + .../members/RoomMemberListViewModel.kt | 11 ++-- .../members/RoomMemberSummaryComparator.kt | 61 +++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt diff --git a/CHANGES.md b/CHANGES.md index 3993bf755b..7c235d8704 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Features ✨: Improvements πŸ™Œ: - Sharing things to RiotX: sort list by recent room first (#771) - Hide the algorithm when turning on e2e (#897) + - Sort room members by display names Other changes: - Add support for /rainbow and /rainbowme commands (#879) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index 8472d4a2a5..2556e3b78c 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -40,6 +40,7 @@ import io.reactivex.Observable import io.reactivex.functions.BiFunction class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, + private val roomMemberSummaryComparator: RoomMemberSummaryComparator, private val session: Session) : VectorViewModel(initialState) { @@ -113,11 +114,11 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } return listOf( - PowerLevelCategory.ADMIN to admins, - PowerLevelCategory.MODERATOR to moderators, - PowerLevelCategory.CUSTOM to customs, - PowerLevelCategory.INVITE to invites, - PowerLevelCategory.USER to users + PowerLevelCategory.ADMIN to admins.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.MODERATOR to moderators.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.CUSTOM to customs.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.INVITE to invites.sortedWith(roomMemberSummaryComparator), + PowerLevelCategory.USER to users.sortedWith(roomMemberSummaryComparator) ) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt new file mode 100644 index 0000000000..cc1dd29d13 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberSummaryComparator.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.roomprofile.members + +import im.vector.matrix.android.api.session.room.model.RoomMemberSummary +import javax.inject.Inject + +class RoomMemberSummaryComparator @Inject constructor() : Comparator { + + override fun compare(leftRoomMemberSummary: RoomMemberSummary?, rightRoomMemberSummary: RoomMemberSummary?): Int { + return when (leftRoomMemberSummary) { + null -> + when (rightRoomMemberSummary) { + null -> 0 + else -> 1 + } + else -> + when (rightRoomMemberSummary) { + null -> -1 + else -> + when { + leftRoomMemberSummary.displayName.isNullOrBlank() -> + when { + rightRoomMemberSummary.displayName.isNullOrBlank() -> { + // No display names, compare ids + leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) + } + else -> 1 + } + else -> + when { + rightRoomMemberSummary.displayName.isNullOrBlank() -> -1 + else -> { + when (leftRoomMemberSummary.displayName) { + rightRoomMemberSummary.displayName -> + // Same display name, compare id + leftRoomMemberSummary.userId.compareTo(rightRoomMemberSummary.userId) + else -> + leftRoomMemberSummary.displayName!!.compareTo(rightRoomMemberSummary.displayName!!, true) + } + } + } + } + } + } + } +} From b848d0530f6c9792d10c34eaa2e1db0e6a65589b Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 21 Jan 2020 15:15:29 +0100 Subject: [PATCH 27/28] Update realm to 6.1.0: should fix some of the native crashes --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e510d11efb..ca18c2b56e 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -10,7 +10,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:6.0.2" + classpath "io.realm:realm-gradle-plugin:6.1.0" } } From e44dc347c6209b07fa22ee2d7f06027f42bbe2c0 Mon Sep 17 00:00:00 2001 From: Christopher Rossbach <31703168+duncanturk@users.noreply.github.com> Date: Thu, 30 Jan 2020 12:07:33 +0100 Subject: [PATCH 28/28] Add "get it on F-Droid" RiotX is on F-Droid now --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1848c7baba..b43bcf643c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ RiotX is an Android Matrix Client currently in beta but in active development. It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented. [Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.riotx) +[Get it on F-Droid](https://f-droid.org/app/im.vector.riotx) Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)