From bbc0a84af08038f110d6e3fa1bef07d90eec36ca Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:45:47 +0000 Subject: [PATCH 001/229] Remove redundant text in feature request template Same deal as PR #4076. --- .github/ISSUE_TEMPLATE/enhancement.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 5d9cfb3c88..d6c84720bc 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -10,10 +10,10 @@ body: id: usecase attributes: label: Your use case - description: What would you like to be able to do? Please feel welcome to include screenshots or mock ups. + description: Please feel welcome to include screenshots or mock ups. placeholder: Tell us what you would like to do! value: | - #### What would you like to do? + #### What would you like to be able to do? #### Why would you like to do it? From 44ab38aa392b8fffc05b35c4cfa2a662327ec885 Mon Sep 17 00:00:00 2001 From: opusforlife2 <53176348+opusforlife2@users.noreply.github.com> Date: Fri, 15 Oct 2021 19:55:15 +0000 Subject: [PATCH 002/229] Add changelog file for PR #4257 --- changelog.d/4257.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/4257.misc diff --git a/changelog.d/4257.misc b/changelog.d/4257.misc new file mode 100644 index 0000000000..fa0657bfea --- /dev/null +++ b/changelog.d/4257.misc @@ -0,0 +1 @@ +Remove redundant text in feature request issue form From 3e03db200c306ac1e0ef01af5c92453caa0deb8f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 25 Oct 2021 14:47:57 +0300 Subject: [PATCH 003/229] Add poll icon to attachment type selector. --- .../attachments/AttachmentTypeSelectorView.kt | 5 +++- .../ic_attachment_poll_white_24dp.xml | 10 +++++++ .../layout/view_attachment_type_selector.xml | 30 +++++++++++++++++++ vector/src/main/res/values/strings.xml | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index 35644e1843..6c349d18dc 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -75,6 +75,7 @@ class AttachmentTypeSelectorView(context: Context, views.attachmentStickersButton.configure(Type.STICKER) views.attachmentAudioButton.configure(Type.AUDIO) views.attachmentContactButton.configure(Type.CONTACT) + views.attachmentPollButton.configure(Type.POLL) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -108,6 +109,7 @@ class AttachmentTypeSelectorView(context: Context, animateButtonIn(views.attachmentAudioButton, 0) animateButtonIn(views.attachmentContactButton, ANIMATION_DURATION / 4) animateButtonIn(views.attachmentStickersButton, ANIMATION_DURATION / 2) + animateButtonIn(views.attachmentPollButton, ANIMATION_DURATION / 4) } override fun dismiss() { @@ -212,6 +214,7 @@ class AttachmentTypeSelectorView(context: Context, FILE(PERMISSIONS_EMPTY), STICKER(PERMISSIONS_EMPTY), AUDIO(PERMISSIONS_EMPTY), - CONTACT(PERMISSIONS_FOR_PICKING_CONTACT) + CONTACT(PERMISSIONS_FOR_PICKING_CONTACT), + POLL(PERMISSIONS_EMPTY) } } diff --git a/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml b/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml new file mode 100644 index 0000000000..8cbcc6e47c --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_poll_white_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index 648ca91820..22ed6ec0e9 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -163,5 +163,35 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 274753ee3f..02b27b1a94 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2429,6 +2429,7 @@ "Audio" "Gallery" "Sticker" + Poll Rotate and crop Couldn\'t handle share data From 4af42902a08246c229993e90f2942b497978cb83 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 25 Oct 2021 16:41:37 +0300 Subject: [PATCH 004/229] Create poll screen components implemented. --- vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../features/createpoll/CreatePollAction.kt | 22 ++++++++ .../features/createpoll/CreatePollActivity.kt | 51 +++++++++++++++++++ .../createpoll/CreatePollViewEvents.kt | 21 ++++++++ .../createpoll/CreatePollViewModel.kt | 51 +++++++++++++++++++ .../createpoll/CreatePollViewState.kt | 23 +++++++++ .../home/room/detail/RoomDetailFragment.kt | 1 + .../features/navigation/DefaultNavigator.kt | 6 +++ .../app/features/navigation/Navigator.kt | 2 + 10 files changed, 180 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 376e0e869a..cf8e1b92da 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -339,6 +339,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 76b511d2bd..07b3ae02b7 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -31,6 +31,7 @@ import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.conference.VectorJitsiActivity import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity +import im.vector.app.features.createpoll.CreatePollActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.app.features.crypto.quads.SharedSecureStorageActivity @@ -174,6 +175,7 @@ interface ScreenComponent { fun inject(activity: SpaceManageActivity) fun inject(activity: RoomJoinRuleActivity) fun inject(activity: SpaceLeaveAdvancedActivity) + fun inject(activity: CreatePollActivity) /* ========================================================================================== * BottomSheets diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt new file mode 100644 index 0000000000..ad8da6e208 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class CreatePollAction : VectorViewModelAction { +} diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt new file mode 100644 index 0000000000..2aefdb51d7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.viewModel +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.platform.SimpleFragmentActivity +import javax.inject.Inject + +class CreatePollActivity : SimpleFragmentActivity(), CreatePollViewModel.Factory { + + private val viewModel: CreatePollViewModel by viewModel() + @Inject lateinit var viewModelFactory: CreatePollViewModel.Factory + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + override fun create(initialState: CreatePollViewState) = viewModelFactory.create(initialState) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + views.toolbar.visibility = View.GONE + } + + companion object { + + fun getIntent(context: Context): Intent { + return Intent(context, CreatePollActivity::class.java) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt new file mode 100644 index 0000000000..8541a1d482 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewEvents.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import im.vector.app.core.platform.VectorViewEvents + +sealed class CreatePollViewEvents : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt new file mode 100644 index 0000000000..a93285dfc2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewModel + +class CreatePollViewModel @AssistedInject constructor(@Assisted + initialState: CreatePollViewState) : + VectorViewModel(initialState) { + + @AssistedFactory + interface Factory { + fun create(initialState: CreatePollViewState): CreatePollViewModel + } + + companion object : MavericksViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: CreatePollViewState): CreatePollViewModel { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + override fun handle(action: CreatePollAction) { + } +} diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt new file mode 100644 index 0000000000..f53e7b2843 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import com.airbnb.mvrx.MavericksState + +data class CreatePollViewState( + val question: String = "" +) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index fa0ca24289..d275dccf39 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -2145,6 +2145,7 @@ class RoomDetailFragment @Inject constructor( AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext()) }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index debdf3739c..d727f24ade 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -40,6 +40,7 @@ import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity import im.vector.app.features.call.transfer.CallTransferActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity +import im.vector.app.features.createpoll.CreatePollActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.app.features.crypto.recover.BootstrapBottomSheet @@ -498,6 +499,11 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openCreatePoll(context: Context) { + val intent = CreatePollActivity.getIntent(context) + context.startActivity(intent) + } + private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) { if (buildTask) { val stackBuilder = TaskStackBuilder.create(context) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 612643c804..0ff17887f9 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -140,4 +140,6 @@ interface Navigator { fun openDevTools(context: Context, roomId: String) fun openCallTransfer(context: Context, callId: String) + + fun openCreatePoll(context: Context) } From cb1d5e888d27a1309a5f36c6ddbffdcf5701601f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 25 Oct 2021 20:14:10 +0300 Subject: [PATCH 005/229] Create poll fragment with a title. --- .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../features/createpoll/CreatePollActivity.kt | 9 +++ .../features/createpoll/CreatePollFragment.kt | 41 ++++++++++++ .../main/res/layout/fragment_create_poll.xml | 63 +++++++++++++++++++ vector/src/main/res/values/strings.xml | 3 + 5 files changed, 122 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt create mode 100644 vector/src/main/res/layout/fragment_create_poll.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 3bc8e30851..45f29aa43d 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -24,6 +24,7 @@ import dagger.Module import dagger.multibindings.IntoMap import im.vector.app.features.attachments.preview.AttachmentsPreviewFragment import im.vector.app.features.contactsbook.ContactsBookFragment +import im.vector.app.features.createpoll.CreatePollFragment import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment @@ -834,4 +835,9 @@ interface FragmentModule { @IntoMap @FragmentKey(SpaceLeaveAdvancedFragment::class) fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(CreatePollFragment::class) + fun bindCreatePollFragment(fragment: CreatePollFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt index 2aefdb51d7..c325cd0609 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollActivity.kt @@ -21,7 +21,9 @@ import android.content.Intent import android.os.Bundle import android.view.View import com.airbnb.mvrx.viewModel +import im.vector.app.R import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.addFragment import im.vector.app.core.platform.SimpleFragmentActivity import javax.inject.Inject @@ -40,6 +42,13 @@ class CreatePollActivity : SimpleFragmentActivity(), CreatePollViewModel.Factory override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) views.toolbar.visibility = View.GONE + + if (isFirstCreation()) { + addFragment( + R.id.container, + CreatePollFragment::class.java + ) + } } companion object { diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt new file mode 100644 index 0000000000..706d58e489 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentCreatePollBinding +import javax.inject.Inject + +class CreatePollFragment @Inject constructor() : VectorBaseFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding { + return FragmentCreatePollBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + vectorBaseActivity.setSupportActionBar(views.createPollToolbar) + + views.createPollClose.debouncedClicks { + requireActivity().finish() + } + } +} diff --git a/vector/src/main/res/layout/fragment_create_poll.xml b/vector/src/main/res/layout/fragment_create_poll.xml new file mode 100644 index 0000000000..76c744c6c5 --- /dev/null +++ b/vector/src/main/res/layout/fragment_create_poll.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 02b27b1a94..86dc7153a5 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3625,4 +3625,7 @@ Link this email with your account %s in Settings to receive invites directly in Element. + + + Create Poll From c90dbf2f38c7fdc3f58cda79d50c23004013db03 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 26 Oct 2021 11:55:52 +0100 Subject: [PATCH 006/229] allowing null users in the email search, fixes missing indentity server helpers when inviting by email --- .../vector/app/features/userdirectory/UserListViewModel.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index fde69ce9ba..8b32bddca2 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -177,11 +177,9 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private suspend fun executeSearchEmail(search: String) { suspend { val params = listOf(ThreePid.Email(search)) - val foundThreePid = tryOrNull { - session.identityService().lookUp(params).firstOrNull() - } + val foundThreePid = session.identityService().lookUp(params).firstOrNull() if (foundThreePid == null) { - null + ThreePidUser(email = search, user = null) } else { try { val json = session.getProfile(foundThreePid.matrixId) From 10df75bd57ab2effbccbb03f1f10fa4180fd00a3 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 26 Oct 2021 12:25:17 +0100 Subject: [PATCH 007/229] allowing the re-emission of identical search terms, fixes the finish setup/give consent steps from not properly updating the UI - also captures the fragmet resumed event in order to handle returning from the settings page and applying a identity server --- .../features/userdirectory/UserListAction.kt | 1 + .../userdirectory/UserListFragment.kt | 5 +++ .../userdirectory/UserListViewModel.kt | 36 +++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt index 83829c1119..86de26ac23 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt @@ -25,4 +25,5 @@ sealed class UserListAction : VectorViewModelAction { data class RemovePendingSelection(val pendingSelection: PendingSelection) : UserListAction() object ComputeMatrixToLinkForSharing : UserListAction() data class UpdateUserConsent(val consent: Boolean) : UserListAction() + object Resumed : UserListAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index aed134816a..8935f93671 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -222,6 +222,11 @@ class UserListFragment @Inject constructor( ) } + override fun onResume() { + super.onResume() + viewModel.handle(UserListAction.Resumed) + } + override fun giveIdentityServerConsent() { withState(viewModel) { state -> requireContext().showIdentityServerConsentDialog( diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 8b32bddca2..5798fb86f1 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.userdirectory import androidx.lifecycle.asFlow +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Uninitialized import dagger.assisted.Assisted @@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService @@ -57,7 +59,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val knownUsersSearch = MutableStateFlow("") private val directoryUsersSearch = MutableStateFlow("") - private val identityServerUsersSearch = MutableStateFlow("") + private val identityServerUsersSearch = MutableStateFlow(UserSearch(searchTerm = "")) @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -69,7 +71,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val identityServerListener = object : IdentityServiceListener { override fun onIdentityServerChange() { withState { - identityServerUsersSearch.tryEmit(it.searchTerm) + identityServerUsersSearch.tryEmit(UserSearch(it.searchTerm)) val identityServerURL = cleanISURL(session.identityService().getCurrentIdentityServerUrl()) setState { copy(configuredIdentityServer = identityServerURL) @@ -105,16 +107,29 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User is UserListAction.RemovePendingSelection -> handleRemoveSelectedUser(action) UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink() is UserListAction.UpdateUserConsent -> handleISUpdateConsent(action) + UserListAction.Resumed -> handleResumed() }.exhaustive } private fun handleISUpdateConsent(action: UserListAction.UpdateUserConsent) { session.identityService().setUserConsent(action.consent) withState { - identityServerUsersSearch.tryEmit(it.searchTerm) + retryUserSearch(it) } } + private fun handleResumed() { + withState { + if (it.hasNoIdentityServerConfigured()) { + retryUserSearch(it) + } + } + } + + private fun retryUserSearch(state: UserListViewState) { + identityServerUsersSearch.tryEmit(UserSearch(state.searchTerm, cacheBuster = System.currentTimeMillis())) + } + private fun handleSearchUsers(searchTerm: String) { setState { copy( @@ -130,7 +145,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User ) } } - identityServerUsersSearch.tryEmit(searchTerm) + identityServerUsersSearch.tryEmit(UserSearch(searchTerm)) knownUsersSearch.tryEmit(searchTerm) directoryUsersSearch.tryEmit(searchTerm) } @@ -144,7 +159,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleClearSearchUsers() { knownUsersSearch.tryEmit("") directoryUsersSearch.tryEmit("") - identityServerUsersSearch.tryEmit("") + identityServerUsersSearch.tryEmit(UserSearch("")) setState { copy(searchTerm = "") } @@ -152,10 +167,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun observeUsers() = withState { state -> identityServerUsersSearch - .filter { it.isEmail() } + .filter { it.searchTerm.isEmail() } .sample(300) .onEach { search -> - executeSearchEmail(search) + executeSearchEmail(search.searchTerm) }.launchIn(viewModelScope) knownUsersSearch @@ -239,3 +254,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User setState { copy(pendingSelections = selections) } } } + +private fun UserListViewState.hasNoIdentityServerConfigured() = matchingEmail is Fail && matchingEmail.error == IdentityServiceError.NoIdentityServerConfigured + +/** + * Wrapper class to allow identical search terms to be re-emitted + */ +private data class UserSearch(val searchTerm: String, val cacheBuster: Long = 0) From c936954119b920eb14fc6a9734599f0ed54f2700 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 14:24:23 +0200 Subject: [PATCH 008/229] Flow migration: start replacing Rx by Flow --- vector/build.gradle | 3 - .../java/im/vector/app/AppStateHandler.kt | 20 +- .../app/core/platform/VectorBaseActivity.kt | 12 +- .../VectorBaseBottomSheetDialogFragment.kt | 12 +- .../app/core/platform/VectorBaseFragment.kt | 14 +- .../app/core/platform/VectorViewModel.kt | 40 +--- .../im/vector/app/core/utils/DataSource.kt | 26 +-- .../features/call/CallControlsBottomSheet.kt | 2 +- .../app/features/call/VectorCallActivity.kt | 14 +- .../call/conference/VectorJitsiActivity.kt | 2 +- .../createdirect/CreateDirectRoomActivity.kt | 9 +- .../quads/SharedSecureStorageActivity.kt | 2 +- .../SharedSecuredStorageResetAllFragment.kt | 2 +- .../features/devtools/RoomDevToolActivity.kt | 2 +- .../vector/app/features/home/HomeActivity.kt | 12 +- .../app/features/home/HomeDetailFragment.kt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 30 +-- .../home/PromoteRestrictedViewModel.kt | 1 + .../home/UnreadMessagesSharedViewModel.kt | 172 +++++++++--------- .../home/room/detail/RoomDetailActivity.kt | 9 +- .../home/room/detail/RoomDetailFragment.kt | 8 +- .../home/room/detail/RoomDetailViewModel.kt | 19 +- .../detail/composer/TextComposerViewModel.kt | 2 +- .../reactions/ViewReactionsViewModel.kt | 52 +++--- .../home/room/list/RoomListFragment.kt | 9 +- .../home/room/list/RoomListSectionBuilder.kt | 2 - .../room/list/RoomListSectionBuilderGroup.kt | 32 ++-- .../room/list/RoomListSectionBuilderSpace.kt | 54 +++--- .../home/room/list/RoomListViewModel.kt | 5 +- .../invite/InviteUsersToRoomActivity.kt | 9 +- .../app/features/invite/InvitesAcceptor.kt | 12 +- .../app/features/login/LoginActivity.kt | 7 +- .../app/features/login2/LoginActivity2.kt | 13 +- .../powerlevel/PowerLevelsFlowFactory.kt | 2 +- .../room/RequireActiveMembershipViewModel.kt | 2 +- .../roomdirectory/RoomDirectoryActivity.kt | 9 +- .../createroom/CreateRoomActivity.kt | 9 +- .../createroom/CreateRoomFragment.kt | 9 +- .../roomprofile/RoomProfileActivity.kt | 9 +- .../roomprofile/RoomProfileFragment.kt | 9 +- .../roomprofile/alias/RoomAliasFragment.kt | 9 +- .../members/RoomMemberListViewModel.kt | 1 - .../settings/RoomSettingsFragment.kt | 15 +- .../VectorSettingsSecurityPrivacyFragment.kt | 4 +- .../settings/devices/DevicesViewModel.kt | 12 +- .../signout/soft/SoftLogoutActivity.kt | 2 +- .../signout/soft/SoftLogoutActivity2.kt | 2 +- .../signout/soft/SoftLogoutFragment.kt | 2 +- .../features/spaces/SpaceCreationActivity.kt | 2 +- .../app/features/spaces/SpaceListViewModel.kt | 17 +- .../features/spaces/SpacePreviewActivity.kt | 9 +- .../CreateSpaceAdd3pidInvitesFragment.kt | 2 +- .../create/CreateSpaceDefaultRoomsFragment.kt | 2 +- .../create/CreateSpaceDetailsFragment.kt | 2 +- .../leave/SpaceLeaveAdvancedActivity.kt | 2 +- .../spaces/manage/SpaceManageActivity.kt | 9 +- .../spaces/manage/SpaceSettingsFragment.kt | 9 +- .../spaces/people/SpacePeopleActivity.kt | 9 +- .../spaces/people/SpacePeopleFragment.kt | 2 +- .../userdirectory/UserListViewModel.kt | 4 +- .../java/im/vector/app/test/Extensions.kt | 8 +- 61 files changed, 408 insertions(+), 375 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index 5f032e55c2..35d1acfe8b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -388,9 +388,6 @@ dependencies { kapt libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks - //TODO: remove when entirely migrated to Flow - implementation libs.airbnb.mavericksRx - // Work implementation libs.androidx.work diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 30078963f4..650047787e 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -25,12 +25,19 @@ import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.CancelableBag import javax.inject.Inject import javax.inject.Singleton @@ -54,10 +61,10 @@ class AppStateHandler @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) : LifecycleObserver { - private val compositeDisposable = CompositeDisposable() + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) - val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() + val selectedRoomGroupingObservable = selectedSpaceDataSource.stream() fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? { // XXX we should somehow make it live :/ just a work around @@ -105,9 +112,9 @@ class AppStateHandler @Inject constructor( } private fun observeActiveSession() { - sessionDataSource.observe() + sessionDataSource.stream() .distinctUntilChanged() - .subscribe { + .onEach { // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { @@ -116,9 +123,8 @@ class AppStateHandler @Inject constructor( setCurrentGroup(uiStateRepository.getSelectedGroup(session.sessionId), session) } } - }.also { - compositeDisposable.add(it) } + .launchIn(coroutineScope) } fun safeActiveSpaceId(): String? { @@ -136,7 +142,7 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { - compositeDisposable.clear() + coroutineScope.coroutineContext.cancelChildren() val session = activeSessionHolder.getSafeActiveSession() ?: return when (val currentMethod = selectedSpaceDataSource.currentValue?.orNull() ?: RoomGroupingMethod.BySpace(null)) { is RoomGroupingMethod.BySpace -> { diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 4d06dbe6a2..a28d9fa355 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -39,6 +39,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util @@ -80,6 +81,10 @@ import im.vector.app.receivers.DebugReceiver import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.GlobalError import timber.log.Timber @@ -104,13 +109,12 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { hideWaitingView() observer(it) } - .disposeOnDestroy() + .launchIn(lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 711b2b144b..04a34a8876 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.CallSuper import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.MavericksView @@ -39,6 +40,10 @@ import im.vector.app.core.utils.DimensionConverter import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.util.concurrent.TimeUnit @@ -193,11 +198,10 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { observer(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index d3c66ec61d..7dce2bc954 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -29,6 +29,7 @@ import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util.assertMainThread @@ -47,6 +48,12 @@ import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import timber.log.Timber import java.util.concurrent.TimeUnit @@ -237,13 +244,12 @@ abstract class VectorBaseFragment : Fragment(), MavericksView protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) { viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { dismissLoadingDialog() observer(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } /* ========================================================================================== diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 6e7c24d4e9..c9d58f9545 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -16,53 +16,17 @@ package im.vector.app.core.platform -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.BaseMvRxViewModel -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.Success +import com.airbnb.mvrx.MavericksViewModel import im.vector.app.core.utils.DataSource import im.vector.app.core.utils.PublishDataSource -import io.reactivex.Observable -import io.reactivex.Single abstract class VectorViewModel(initialState: S) : - BaseMvRxViewModel(initialState) { - - interface Factory { - fun create(state: S): BaseMvRxViewModel - } + MavericksViewModel(initialState) { // 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 - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Single.toAsync(stateReducer: S.(Async) -> S): Single> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnSuccess { setState { stateReducer(it) } } - } - - /** - * This method does the same thing as the execute function, but it doesn't subscribe to the stream - * so you can use this in a switchMap or a flatMap - */ - // False positive - @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") - fun Observable.toAsync(stateReducer: S.(Async) -> S): Observable> { - setState { stateReducer(Loading()) } - return map { Success(it) as Async } - .onErrorReturn { Fail(it) } - .doOnNext { setState { stateReducer(it) } } - } - abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt index fc4ee330bb..6338768723 100644 --- a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt +++ b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt @@ -17,12 +17,12 @@ package im.vector.app.core.utils import com.jakewharton.rxrelay2.BehaviorRelay -import com.jakewharton.rxrelay2.PublishRelay -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow interface DataSource { - fun observe(): Observable + fun stream(): Flow } interface MutableDataSource : DataSource { @@ -34,17 +34,17 @@ interface MutableDataSource : DataSource { */ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableDataSource { - private val behaviorRelay = createRelay() + private val mutableFlow = MutableSharedFlow(replay = 1) val currentValue: T? - get() = behaviorRelay.value + get() = mutableFlow.replayCache.firstOrNull() - override fun observe(): Observable { - return behaviorRelay.hide().observeOn(AndroidSchedulers.mainThread()) + override fun stream(): Flow { + return mutableFlow } override fun post(value: T) { - behaviorRelay.accept(value!!) + mutableFlow.tryEmit(value) } private fun createRelay(): BehaviorRelay { @@ -61,13 +61,13 @@ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableD */ open class PublishDataSource : MutableDataSource { - private val publishRelay = PublishRelay.create() + private val mutableFlow = MutableSharedFlow(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - override fun observe(): Observable { - return publishRelay.hide().observeOn(AndroidSchedulers.mainThread()) + override fun stream(): Flow { + return mutableFlow } override fun post(value: T) { - publishRelay.accept(value!!) + mutableFlow.tryEmit(value) } } diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt index b4f49db781..e38b53c858 100644 --- a/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/call/CallControlsBottomSheet.kt @@ -39,7 +39,7 @@ class CallControlsBottomSheet : VectorBaseBottomSheetDialogFragment(), CallContro setSupportActionBar(views.callToolbar) configureCallViews() - callViewModel.subscribe(this) { + callViewModel.onEach { renderState(it) } @@ -141,12 +146,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro } callViewModel.viewEvents - .observe() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { + .stream() + .onEach { handleViewEvents(it) } - .disposeOnDestroy() + .launchIn(lifecycleScope) callViewModel.onEach(VectorCallViewState::callId, VectorCallViewState::isVideoCall) { _, isVideoCall -> if (isVideoCall) { diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index 3fcefc9c8e..0fdfea8bff 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -68,7 +68,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - jitsiViewModel.subscribe(this) { + jitsiViewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 28da72714a..3ff989da5a 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -46,6 +47,8 @@ import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import java.net.HttpURLConnection @@ -64,8 +67,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> when (action) { UserListSharedAction.Close -> finish() UserListSharedAction.GoBack -> onBackPressed() @@ -74,7 +77,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { UserListSharedAction.AddByQrCode -> openAddByQrCode() }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { addFragment( R.id.container, diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index 61c8ab8f0a..bb854aca26 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -63,7 +63,7 @@ class SharedSecureStorageActivity : viewModel.observeViewEvents { observeViewEvents(it) } - viewModel.subscribe(this) { renderState(it) } + viewModel.onEach { renderState(it) } } override fun onDestroy() { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt index 670e5c610a..200b2b73c2 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -55,7 +55,7 @@ class SharedSecuredStorageResetAllFragment @Inject constructor() : } } - sharedViewModel.subscribe(this) { state -> + sharedViewModel.onEach { state -> views.ssssResetOtherDevices.setTextOrHide( state.activeDeviceCount .takeIf { it > 0 } diff --git a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt index 772ef99931..2c7a15e6ad 100644 --- a/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt +++ b/vector/src/main/java/im/vector/app/features/devtools/RoomDevToolActivity.kt @@ -66,7 +66,7 @@ class RoomDevToolActivity : SimpleFragmentActivity(), FragmentManager.OnBackStac override fun initUiAndData() { super.initUiAndData() - viewModel.subscribe(this) { + viewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index e8af044bbd..04ca25332f 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -73,6 +73,8 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.push.fcm.FcmHelper +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.initsync.SyncStatusService @@ -178,8 +180,8 @@ class HomeActivity : } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START) @@ -222,7 +224,7 @@ class HomeActivity : } }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) val args = intent.getParcelableExtra(Mavericks.KEY_ARG) @@ -243,13 +245,13 @@ class HomeActivity : is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } - homeActivityViewModel.subscribe(this) { renderState(it) } + homeActivityViewModel.onEach { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() if (!vectorPreferences.didPromoteNewRestrictedFeature()) { - promoteRestrictedViewModel.subscribe(this) { + promoteRestrictedViewModel.onEach { if (it.activeSpaceSummary != null && !it.activeSpaceSummary.isPublic && it.activeSpaceSummary.otherMemberIds.isNotEmpty()) { // It's a private space with some members show this once diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 80351a437e..55d8e2e09a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -299,7 +299,7 @@ class HomeDetailFragment @Inject constructor( private fun setupKeysBackupBanner() { serverBackupStatusViewModel - .subscribe(this) { + .onEach { when (val banState = it.bannerState.invoke()) { is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false) BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 73e50ad5f1..8bfc1a8db4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -39,7 +39,13 @@ import im.vector.app.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.flow.switchMap import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -66,7 +72,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val directRoomHelper: DirectRoomHelper, private val appStateHandler: AppStateHandler, private val autoAcceptInvites: AutoAcceptInvites) : - VectorViewModel(initialState), + VectorViewModel(initialState), CallProtocolsChecker.Listener { @AssistedFactory @@ -194,18 +200,15 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private fun observeRoomGroupingMethod() { appStateHandler.selectedRoomGroupingObservable - .subscribe { - setState { - copy( - roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) - ) - } + .setOnEach { + copy( + roomGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) + ) } - .disposeOnClear() } private fun observeRoomSummaries() { - appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().switchMap { + appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().flatMapLatest { // we use it as a trigger to all changes in room, but do not really load // the actual models session.getPagedRoomSummariesLive( @@ -213,11 +216,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE - ).asObservable() + ).asFlow() } - .observeOn(Schedulers.computation()) - .throttleFirst(300, TimeUnit.MILLISECONDS) - .subscribe { + .sample(300) + .onEach { when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { is RoomGroupingMethod.ByLegacyGroup -> { // TODO!! @@ -274,6 +276,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } } } - .disposeOnClear() + .launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt index 218574c03e..77ee23f732 100644 --- a/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/PromoteRestrictedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.flow.distinctUntilChanged import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 5bdbc95b48..3434f9dfb0 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -30,8 +31,12 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.sample import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -57,7 +62,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia private val vectorPreferences: VectorPreferences, appStateHandler: AppStateHandler, private val autoAcceptInvites: AutoAcceptInvites) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { @AssistedFactory interface Factory : MavericksAssistedViewModelFactory { @@ -75,8 +80,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.memberships = listOf(Membership.JOIN) this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }, sortOrder = RoomSortOrder.NONE - ).asObservable() - .throttleFirst(300, TimeUnit.MILLISECONDS) + ).asFlow() + .sample(300) .execute { val counts = session.getNotificationCountForRooms( roomSummaryQueryParams { @@ -103,91 +108,92 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia ) } - Observable.combineLatest( + combine( appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(), - appStateHandler.selectedRoomGroupingObservable.switchMap { + appStateHandler.selectedRoomGroupingObservable.flatMapLatest { session.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = Membership.activeMemberships() }, sortOrder = RoomSortOrder.NONE - ).asObservable() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(Schedulers.computation()) - }, - { groupingMethod, _ -> - when (groupingMethod.orNull()) { - is RoomGroupingMethod.ByLegacyGroup -> { - // currently not supported - CountInfo( - RoomAggregateNotificationCount(0, 0), - RoomAggregateNotificationCount(0, 0) - ) - } - is RoomGroupingMethod.BySpace -> { - val selectedSpace = appStateHandler.safeActiveSpaceId() + ).asFlow() + .sample(300) - val inviteCount = if (autoAcceptInvites.hideInvites) { - 0 - } else { - session.getRoomSummaries( - roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } - ).size - } - - val spaceInviteCount = if (autoAcceptInvites.hideInvites) { - 0 - } else { - session.getRoomSummaries( - spaceSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - } - ).size - } - - val totalCount = session.getNotificationCountForRooms( - roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { - !vectorPreferences.prefSpacesShowAllRoomInHome() - } ?: ActiveSpaceFilter.None - } - ) - - val counts = RoomAggregateNotificationCount( - totalCount.notificationCount + inviteCount, - totalCount.highlightCount + inviteCount - ) - val rootCounts = session.spaceService().getRootSpaceSummaries() - .filter { - // filter out current selection - it.roomId != selectedSpace - } - - CountInfo( - homeCount = counts, - otherCount = RoomAggregateNotificationCount( - notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) + - (counts.notificationCount.takeIf { selectedSpace != null } ?: 0) + - spaceInviteCount, - highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) + - (counts.highlightCount.takeIf { selectedSpace != null } ?: 0) + - spaceInviteCount - ) - ) - } - null -> { - CountInfo( - RoomAggregateNotificationCount(0, 0), - RoomAggregateNotificationCount(0, 0) - ) - } - } } - ).execute { - copy( - homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0), - otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0) - ) + ) { groupingMethod, _ -> + when (groupingMethod.orNull()) { + is RoomGroupingMethod.ByLegacyGroup -> { + // currently not supported + CountInfo( + RoomAggregateNotificationCount(0, 0), + RoomAggregateNotificationCount(0, 0) + ) + } + is RoomGroupingMethod.BySpace -> { + val selectedSpace = appStateHandler.safeActiveSpaceId() + + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } + + val spaceInviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + spaceSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + ).size + } + + val totalCount = session.getNotificationCountForRooms( + roomSummaryQueryParams { + this.memberships = listOf(Membership.JOIN) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null).takeIf { + !vectorPreferences.prefSpacesShowAllRoomInHome() + } ?: ActiveSpaceFilter.None + } + ) + + val counts = RoomAggregateNotificationCount( + totalCount.notificationCount + inviteCount, + totalCount.highlightCount + inviteCount + ) + val rootCounts = session.spaceService().getRootSpaceSummaries() + .filter { + // filter out current selection + it.roomId != selectedSpace + } + + CountInfo( + homeCount = counts, + otherCount = RoomAggregateNotificationCount( + notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) + + (counts.notificationCount.takeIf { selectedSpace != null } ?: 0) + + spaceInviteCount, + highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) + + (counts.highlightCount.takeIf { selectedSpace != null } ?: 0) + + spaceInviteCount + ) + ) + } + null -> { + CountInfo( + RoomAggregateNotificationCount(0, 0), + RoomAggregateNotificationCount(0, 0) + ) + } + } } + .flowOn(Dispatchers.Default) + .execute { + copy( + homeSpaceUnread = it.invoke()?.homeCount ?: RoomAggregateNotificationCount(0, 0), + otherSpacesUnread = it.invoke()?.otherCount ?: RoomAggregateNotificationCount(0, 0) + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index ba53f75eca..415ca7bc04 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -24,6 +24,7 @@ import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -40,6 +41,8 @@ import im.vector.app.features.navigation.Navigator import im.vector.app.features.room.RequireActiveMembershipAction import im.vector.app.features.room.RequireActiveMembershipViewEvents import im.vector.app.features.room.RequireActiveMembershipViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class RoomDetailActivity : @@ -97,13 +100,13 @@ class RoomDetailActivity : sharedActionViewModel = viewModelProvider.get(RoomDetailSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDetailSharedAction.SwitchToRoom -> switchToRoom(sharedAction) } } - .disposeOnDestroy() + .launchIn(lifecycleScope) requireActiveMembershipViewModel.observeViewEvents { when (it) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index d20c9796d2..9b103cebb5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -184,6 +184,8 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape @@ -365,11 +367,11 @@ class RoomDetailFragment @Inject constructor( } sharedActionViewModel - .observe() - .subscribe { + .stream() + .onEach { handleActions(it) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) knownCallsViewModel .liveKnownCalls diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 03bde7d4cc..ee929243b5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -27,7 +27,6 @@ import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -37,6 +36,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder @@ -56,7 +56,6 @@ import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper -import io.reactivex.rxkotlin.subscribeBy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect @@ -123,8 +122,8 @@ class RoomDetailViewModel @AssistedInject constructor( private val room = session.getRoom(initialState.roomId)!! private val eventId = initialState.eventId - private val invisibleEventsObservable = BehaviorRelay.create() - private val visibleEventsObservable = BehaviorRelay.create() + private val invisibleEventsSource = BehaviorDataSource() + private val visibleEventsSource = BehaviorDataSource() private var timelineEvents = MutableSharedFlow>(0) val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId) @@ -562,7 +561,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEventInvisible(action: RoomDetailAction.TimelineEventTurnsInvisible) { - invisibleEventsObservable.accept(action) + invisibleEventsSource.post(action) } fun getMember(userId: String): RoomMemberSummary? { @@ -711,12 +710,12 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) { viewModelScope.launch(Dispatchers.Default) { if (action.event.root.sendState.isSent()) { // ignore pending/local events - visibleEventsObservable.accept(action) + visibleEventsSource.post(action) } // We need to update this with the related m.replace also (to move read receipt) action.event.annotations?.editSummary?.sourceEvents?.forEach { room.getTimeLineEvent(it)?.let { event -> - visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event)) + visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event)) } } @@ -864,7 +863,9 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. - visibleEventsObservable + /* + visibleEventsSource + .stream() .buffer(1, TimeUnit.SECONDS) .filter { it.isNotEmpty() } .subscribeBy(onNext = { actions -> @@ -884,6 +885,8 @@ class RoomDetailViewModel @AssistedInject constructor( } }) .disposeOnClear() + + */ } private fun handleMarkAllAsRead() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index e80f25de2f..3d7c4c71f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -104,7 +104,7 @@ class TextComposerViewModel @AssistedInject constructor( } private fun subscribeToStateInternal() { - selectSubscribe(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> + onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> updateIsSendButtonVisibility(false) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 1f4d67db03..324164bf58 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -32,17 +32,17 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs import io.reactivex.Observable -import io.reactivex.Single +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary -import org.matrix.android.sdk.rx.RxRoom -import org.matrix.android.sdk.rx.unwrap +import org.matrix.android.sdk.flow.FlowRoom +import org.matrix.android.sdk.flow.flow +import org.matrix.android.sdk.flow.unwrap data class DisplayReactionsViewState( val eventId: String, val roomId: String, val mapReactionKeyToMemberList: Async> = Uninitialized) : - MavericksState { + MavericksState { constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId) } @@ -81,39 +81,31 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted } private fun observeEventAnnotationSummaries() { - RxRoom(room) + room.flow() .liveAnnotationSummary(eventId) .unwrap() - .flatMapSingle { summaries -> - Observable - .fromIterable(summaries.reactionsSummary) - // .filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } - .toReactionInfoList() + .map { annotationsSummary -> + annotationsSummary.reactionsSummary + .flatMap { reactionsSummary -> + reactionsSummary.sourceEvents.map { + val event = room.getTimeLineEvent(it) + ?: throw RuntimeException("Your eventId is not valid") + ReactionInfo( + event.root.eventId!!, + reactionsSummary.key, + event.root.senderId ?: "", + event.senderInfo.disambiguatedDisplayName, + dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) + + ) + } + } } .execute { copy(mapReactionKeyToMemberList = it) } } - private fun Observable.toReactionInfoList(): Single> { - return flatMap { summary -> - Observable - .fromIterable(summary.sourceEvents) - .map { - val event = room.getTimeLineEvent(it) - ?: throw RuntimeException("Your eventId is not valid") - ReactionInfo( - event.root.eventId!!, - summary.key, - event.root.senderId ?: "", - event.senderInfo.disambiguatedDisplayName, - dateFormatter.format(event.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - - ) - } - }.toList() - } - override fun handle(action: EmptyAction) { // No op } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 1c173e12e8..0e049e22b1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -49,6 +50,8 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -118,9 +121,9 @@ class RoomListFragment @Inject constructor( views.createChatFabMenu.listener = this sharedActionViewModel - .observe() - .subscribe { handleQuickActions(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleQuickActions(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) roomListViewModel.onEach(RoomListViewState::roomMembershipChanges) { ms -> // it's for invites local echo diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt index 2b3152f8cf..c98f613c40 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt @@ -20,6 +20,4 @@ import im.vector.app.features.home.RoomListDisplayMode interface RoomListSectionBuilder { fun buildSections(mode: RoomListDisplayMode): List - - fun dispose() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index f101669af3..58db2a4030 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes +import androidx.lifecycle.asFlow import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.RoomGroupingMethod @@ -24,17 +25,21 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.showInvites -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.rx.asObservable class RoomListSectionBuilderGroup( + private val coroutineScope: CoroutineScope, private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, @@ -42,8 +47,6 @@ class RoomListSectionBuilderGroup( private val onUpdatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { - private val disposables = CompositeDisposable() - override fun buildSections(mode: RoomListDisplayMode): List { val activeGroupAwareQueries = mutableListOf() val sections = mutableListOf() @@ -103,16 +106,14 @@ class RoomListSectionBuilderGroup( appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { groupingMethod -> + .onEach { groupingMethod -> val selectedGroupId = (groupingMethod.orNull() as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId activeGroupAwareQueries.onEach { updater -> updater.updateQuery { query -> query.copy(activeGroupId = selectedGroupId) } } - }.also { - disposables.add(it) - } + }.launchIn(coroutineScope) return sections } @@ -251,15 +252,14 @@ class RoomListSectionBuilderGroup( }.livePagedList .let { livePagedList -> // use it also as a source to update count - livePagedList.asObservable() - .observeOn(Schedulers.computation()) - .subscribe { + livePagedList.asFlow() + .onEach { sections.find { it.sectionName == name } ?.notificationCount ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) - }.also { - disposables.add(it) } + .flowOn(Dispatchers.Default) + .launchIn(coroutineScope) sections.add( RoomsSection( @@ -280,8 +280,4 @@ class RoomListSectionBuilderGroup( .build() .let { block(it) } } - - override fun dispose() { - disposables.dispose() - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 7063281853..0bf7087618 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asFlow import androidx.lifecycle.liveData import androidx.paging.PagedList import com.airbnb.mvrx.Async @@ -31,10 +32,17 @@ import im.vector.app.features.invite.showInvites import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.Observables import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -45,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.rx.asObservable +import timber.log.Timber class RoomListSectionBuilderSpace( private val session: Session, @@ -57,8 +66,6 @@ class RoomListSectionBuilderSpace( private val onlyOrphansInHome: Boolean = false ) : RoomListSectionBuilder { - private val disposables = CompositeDisposable() - private val pagedListConfig = PagedList.Config.Builder() .setPageSize(10) .setInitialLoadSizeHint(20) @@ -132,14 +139,12 @@ class RoomListSectionBuilderSpace( appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { groupingMethod -> + .onEach { groupingMethod -> val selectedSpace = groupingMethod.orNull()?.space() activeSpaceAwareQueries.onEach { updater -> updater.updateForSpaceId(selectedSpace?.roomId) } - }.also { - disposables.add(it) - } + }.launchIn(viewModelScope) return sections } @@ -221,13 +226,13 @@ class RoomListSectionBuilderSpace( } // add suggested rooms - val suggestedRoomsObservable = // MutableLiveData>() + val suggestedRoomsFlow = // MutableLiveData>() appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .switchMap { groupingMethod -> + .flatMapLatest { groupingMethod -> val selectedSpace = groupingMethod.orNull()?.space() if (selectedSpace == null) { - Observable.just(emptyList()) + flowOf(emptyList()) } else { liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { val spaceSum = tryOrNull { @@ -240,24 +245,23 @@ class RoomListSectionBuilderSpace( session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true } emit(filtered) - }.asObservable() + }.asFlow() } } val liveSuggestedRooms = MutableLiveData() - Observables.combineLatest( - suggestedRoomsObservable, - suggestedRoomJoiningState.asObservable() + combine( + suggestedRoomsFlow, + suggestedRoomJoiningState.asFlow() ) { rooms, joinStates -> SuggestedRoomInfo( rooms, joinStates ) - }.subscribe { + }.onEach { liveSuggestedRooms.postValue(it) - }.also { - disposables.add(it) - } + }.launchIn(viewModelScope) + sections.add( RoomsSection( sectionName = stringProvider.getString(R.string.suggested_header), @@ -373,9 +377,9 @@ class RoomListSectionBuilderSpace( }.livePagedList .let { livePagedList -> // use it also as a source to update count - livePagedList.asObservable() - .observeOn(Schedulers.computation()) - .subscribe { + livePagedList.asFlow() + .onEach { + Timber.v("Thread space list: ${Thread.currentThread()}") sections.find { it.sectionName == name } ?.notificationCount ?.postValue( @@ -387,9 +391,9 @@ class RoomListSectionBuilderSpace( ) } ) - }.also { - disposables.add(it) } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) sections.add( RoomsSection( @@ -432,8 +436,4 @@ class RoomListSectionBuilderSpace( RoomListViewModel.SpaceFilterStrategy.NONE -> this } } - - override fun dispose() { - disposables.dispose() - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 89f5aec8fb..2fd55eb7e5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -135,6 +135,7 @@ class RoomListViewModel @AssistedInject constructor( ) } else { RoomListSectionBuilderGroup( + viewModelScope, session, stringProvider, appStateHandler, @@ -336,8 +337,4 @@ class RoomListViewModel @AssistedInject constructor( } } - override fun onCleared() { - super.onCleared() - roomListSectionBuilder.dispose() - } } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 6f4aff0041..1bf1c12a48 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.View +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -41,6 +42,8 @@ import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.userdirectory.UserListFragmentArgs import im.vector.app.features.userdirectory.UserListSharedAction import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.failure.Failure import java.net.HttpURLConnection @@ -63,8 +66,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { UserListSharedAction.Close -> finish() UserListSharedAction.GoBack -> onBackPressed() @@ -75,7 +78,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { } } } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { addFragment( R.id.container, diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt index 09eff756d5..a1cc6d6d5e 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -19,10 +19,14 @@ package im.vector.app.features.invite import im.vector.app.ActiveSessionDataSource import im.vector.app.features.session.coroutineScope import io.reactivex.disposables.Disposable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -51,7 +55,8 @@ class InvitesAcceptor @Inject constructor( private val autoAcceptInvites: AutoAcceptInvites ) : Session.Listener { - private lateinit var activeSessionDisposable: Disposable + private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + private val shouldRejectRoomIds = mutableSetOf() private val activeSessionIds = mutableSetOf() private val semaphore = Semaphore(1) @@ -61,13 +66,14 @@ class InvitesAcceptor @Inject constructor( } private fun observeActiveSession() { - activeSessionDisposable = sessionDataSource.observe() + sessionDataSource.stream() .distinctUntilChanged() - .subscribe { + .onEach { it.orNull()?.let { session -> onSessionActive(session) } } + .launchIn(coroutineScope) } private fun onSessionActive(session: Session) { diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt index b3606a68ca..bcde8fd37e 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt @@ -85,10 +85,9 @@ open class LoginActivity : VectorBaseActivity(), ToolbarCo addFirstFragment() } - loginViewModel - .subscribe(this) { - updateWithState(it) - } + loginViewModel.onEach { + updateWithState(it) + } loginViewModel.observeViewEvents { handleLoginViewEvents(it) } diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt index 40dd1d2872..8f1b20aa7f 100644 --- a/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/login2/LoginActivity2.kt @@ -92,10 +92,9 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC addFirstFragment() } - loginViewModel - .subscribe(this) { - updateWithState(it) - } + loginViewModel.onEach { + updateWithState(it) + } loginViewModel.observeViewEvents { handleLoginViewEvents(it) } @@ -201,19 +200,19 @@ open class LoginActivity2 : VectorBaseActivity(), ToolbarC // Go back to the login fragment supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE) } - is LoginViewEvents2.OnSendEmailSuccess -> + is LoginViewEvents2.OnSendEmailSuccess -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginWaitForEmailFragment2::class.java, LoginWaitForEmailFragmentArgument(event.email), tag = FRAGMENT_REGISTRATION_STAGE_TAG, option = commonOption) - is LoginViewEvents2.OpenSigninPasswordScreen -> { + is LoginViewEvents2.OpenSigninPasswordScreen -> { addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragmentSigninPassword2::class.java, tag = FRAGMENT_LOGIN_TAG, option = commonOption) } - is LoginViewEvents2.OpenSignupPasswordScreen -> { + is LoginViewEvents2.OpenSignupPasswordScreen -> { addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragmentSignupPassword2::class.java, tag = FRAGMENT_REGISTRATION_STAGE_TAG, diff --git a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt index 767d6f1ba7..d8857b3be3 100644 --- a/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt +++ b/vector/src/main/java/im/vector/app/features/powerlevel/PowerLevelsFlowFactory.kt @@ -33,8 +33,8 @@ class PowerLevelsFlowFactory(private val room: Room) { fun createFlow(): Flow { return room.flow() .liveStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - .flowOn(Dispatchers.Default) .mapOptional { it.content.toModel() } + .flowOn(Dispatchers.Default) .unwrap() } } diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt index 6ad93abe0c..d2ee3a56ec 100644 --- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewModel.kt @@ -77,8 +77,8 @@ class RequireActiveMembershipViewModel @AssistedInject constructor( room.flow() .liveRoomSummary() .unwrap() - .flowOn(Dispatchers.Default) .map { mapToLeftViewEvent(room, it) } + .flowOn(Dispatchers.Default) } .unwrap() .onEach { event -> diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index dd4011a865..e59cfafa42 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -31,6 +32,8 @@ import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @AndroidEntryPoint @@ -53,8 +56,8 @@ class RoomDirectoryActivity : VectorBaseActivity() { } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back -> popBackstack() is RoomDirectorySharedAction.CreateRoom -> { @@ -72,7 +75,7 @@ class RoomDirectoryActivity : VectorBaseActivity() { is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) } override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index eeb7d217c0..b3a21dadb9 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.roomdirectory.createroom import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -28,6 +29,8 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach /** * Simple container for [CreateRoomFragment] @@ -62,14 +65,14 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarC super.onCreate(savedInstanceState) sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index c61da211a4..1244a0f64e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -23,6 +23,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.args @@ -44,6 +45,8 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.toOption +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -103,11 +106,11 @@ class CreateRoomFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(CreateRoomAction.SetVisibility(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun showFailure(throwable: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index fdb639e7d6..c06a2927c5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile import android.content.Context import android.content.Intent import android.widget.Toast +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.appbar.MaterialToolbar @@ -41,6 +42,8 @@ import im.vector.app.features.roomprofile.notifications.RoomNotificationSettings import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import javax.inject.Inject @AndroidEntryPoint @@ -93,8 +96,8 @@ class RoomProfileActivity : } } sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() @@ -105,7 +108,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() }.exhaustive } - .disposeOnDestroy() + .launchIn(lifecycleScope) requireActiveMembershipViewModel.observeViewEvents { when (it) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 23234f8bbd..e1a5cae907 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -52,6 +53,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.util.toMatrixItem @@ -124,9 +127,9 @@ class RoomProfileFragment @Inject constructor( }.exhaustive } roomListQuickActionsSharedActionViewModel - .observe() - .subscribe { handleQuickActions(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleQuickActions(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) setupClicks() setupLongClicks() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index e281c0f84d..15686a6848 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -38,6 +39,8 @@ import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.util.toMatrixItem @@ -77,9 +80,9 @@ class RoomAliasFragment @Inject constructor( } sharedActionViewModel - .observe() - .subscribe { handleAliasAction(it) } - .disposeOnDestroyView() + .stream() + .onEach { handleAliasAction(it) } + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt index adf5a31f2a..1ea9d59229 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListViewModel.kt @@ -95,7 +95,6 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState if (room.isEncrypted()) { room.flow().liveRoomMembers(roomMemberQueryParams) - .flowOn(Dispatchers.Main) .flatMapLatest { membersSummary -> session.cryptoService().getLiveCryptoDeviceInfo(membersSummary.map { it.userId }) .asFlow() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index ce059881b8..0a5f8f4d9a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -24,6 +24,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -46,6 +47,8 @@ import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistory import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID @@ -101,21 +104,21 @@ class RoomSettingsFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun setupRoomHistoryVisibilitySharedActionViewModel() { roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) roomHistoryVisibilitySharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private fun showSuccess() { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index b622d8aab4..2c32070c76 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -149,11 +149,11 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( refreshMyDevice() refreshXSigningStatus() session.liveSecretSynchronisationInfo() - .flowOn(Dispatchers.Main) .onEach { refresh4SSection(it) refreshXSigningStatus() - }.launchIn(viewLifecycleOwner.lifecycleScope) + } + .launchIn(viewLifecycleOwner.lifecycleScope) lifecycleScope.launchWhenResumed { findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt index e8300a1097..154518778d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.PublishDataSource import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.login.ReAuthHelper import io.reactivex.subjects.PublishSubject @@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber import java.util.concurrent.TimeUnit import javax.net.ssl.HttpsURLConnection +import javax.sql.DataSource import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -103,7 +105,7 @@ class DevicesViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - private val refreshPublisher: PublishSubject = PublishSubject.create() + private val refreshSource= PublishDataSource() init { @@ -166,12 +168,12 @@ class DevicesViewModel @AssistedInject constructor( // ) // } - refreshPublisher.throttleFirst(4_000, TimeUnit.MILLISECONDS) - .subscribe { + refreshSource.stream().sample(4_000) + .onEach { session.cryptoService().fetchDevicesList(NoOpMatrixCallback()) session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback()) } - .disposeOnClear() + .launchIn(viewModelScope) // then force download queryRefreshDevicesList() } @@ -193,7 +195,7 @@ class DevicesViewModel @AssistedInject constructor( * It can be any mobile devices, and any browsers. */ private fun queryRefreshDevicesList() { - refreshPublisher.onNext(Unit) + refreshSource.post(Unit) } override fun handle(action: DevicesAction) { diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt index 6e70b34002..4fba8fc3de 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity.kt @@ -50,7 +50,7 @@ class SoftLogoutActivity : LoginActivity() { override fun initUiAndData() { super.initUiAndData() - softLogoutViewModel.subscribe(this) { + softLogoutViewModel.onEach { updateWithState(it) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt index ed45069e92..26500b60f0 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutActivity2.kt @@ -52,7 +52,7 @@ class SoftLogoutActivity2 : LoginActivity2() { override fun initUiAndData() { super.initUiAndData() - softLogoutViewModel.subscribe(this) { + softLogoutViewModel.onEach { updateWithState(it) } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 2aa7f15172..016d340f80 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -55,7 +55,7 @@ class SoftLogoutFragment @Inject constructor( setupRecyclerView() - softLogoutViewModel.subscribe(this) { softLogoutViewState -> + softLogoutViewModel.onEach { softLogoutViewState -> softLogoutController.update(softLogoutViewState) when (val mode = softLogoutViewState.asyncHomeServerLoginFlowRequest.invoke()) { is LoginMode.SsoAndPassword -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index 75373775f9..44acfa8ee3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -71,7 +71,7 @@ class SpaceCreationActivity : SimpleFragmentActivity() { override fun initUiAndData() { super.initUiAndData() - viewModel.subscribe(this) { + viewModel.onEach { renderState(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index 4487833773..a762e13cba 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -35,6 +35,7 @@ import im.vector.app.group import im.vector.app.space import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -89,14 +90,11 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa // observeSelectionState() appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { - setState { - copy( - selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) - ) - } + .setOnEach { + copy( + selectedGroupingMethod = it.orNull() ?: RoomGroupingMethod.BySpace(null) + ) } - .disposeOnClear() session.getGroupSummariesLive(groupSummaryQueryParams {}) .asFlow() @@ -114,7 +112,6 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa }, sortOrder = RoomSortOrder.NONE ).asFlow() .sample(300) - .flowOn(Dispatchers.Default) .onEach { val inviteCount = if (autoAcceptInvites.hideInvites) { 0 @@ -140,7 +137,9 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa homeAggregateCount = counts ) } - }.launchIn(viewModelScope) + } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) } override fun handle(action: SpaceListAction) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt index 59166529b9..fcbbf6a752 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt @@ -19,6 +19,7 @@ package im.vector.app.features.spaces import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction @@ -26,6 +27,8 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleBinding import im.vector.app.features.spaces.preview.SpacePreviewArgs import im.vector.app.features.spaces.preview.SpacePreviewFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class SpacePreviewActivity : VectorBaseActivity() { @@ -37,8 +40,8 @@ class SpacePreviewActivity : VectorBaseActivity() { super.onCreate(savedInstanceState) sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> when (action) { SpacePreviewSharedAction.DismissAction -> finish() SpacePreviewSharedAction.ShowModalLoading -> showWaitingView() @@ -46,7 +49,7 @@ class SpacePreviewActivity : VectorBaseActivity() { is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) } } } - .disposeOnDestroy() + .launchIn(lifecycleScope) if (isFirstCreation()) { val simpleName = SpacePreviewFragment::class.java.simpleName diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt index 6dc3ad8c21..4328c46188 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceAdd3pidInvitesFragment.kt @@ -50,7 +50,7 @@ class CreateSpaceAdd3pidInvitesFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { invalidateState(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt index 53a4ee689b..4ed7e91417 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt @@ -46,7 +46,7 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { epoxyController.setData(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt index 544c33948b..920ceed33c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt @@ -50,7 +50,7 @@ class CreateSpaceDetailsFragment @Inject constructor( views.recyclerView.configureWith(epoxyController) epoxyController.listener = this - sharedViewModel.subscribe(this) { + sharedViewModel.onEach { epoxyController.setData(it) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt index 541d883405..69de39e436 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/leave/SpaceLeaveAdvancedActivity.kt @@ -86,7 +86,7 @@ class SpaceLeaveAdvancedActivity : VectorBaseActivity + leaveViewModel.onEach { state -> when (state.leaveState) { is Loading -> { showWaitingView() diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt index 2dae088c2e..932110d0e3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceManageActivity.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.os.Parcelable import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState @@ -41,6 +42,8 @@ import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @Parcelize @@ -80,14 +83,14 @@ class SpaceManageActivity : VectorBaseActivity(), sharedDirectoryActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) sharedDirectoryActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { is RoomDirectorySharedAction.Back, is RoomDirectorySharedAction.Close -> finish() } } - .disposeOnDestroy() + .launchIn(lifecycleScope) val args = intent?.getParcelableExtra(Mavericks.KEY_ARG) if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt index c2ab015858..a0ab055311 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceSettingsFragment.kt @@ -24,6 +24,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel @@ -49,6 +50,8 @@ import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewState import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -142,11 +145,11 @@ class SpaceSettingsFragment @Inject constructor( private fun setupRoomJoinRuleSharedActionViewModel() { roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) roomJoinRuleSharedActionViewModel - .observe() - .subscribe { action -> + .stream() + .onEach { action -> viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule)) } - .disposeOnDestroyView() + .launchIn(viewLifecycleOwner.lifecycleScope) } private var ignoreChanges = false diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt index 3b84a12bc1..bc778a1b34 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.Mavericks import im.vector.app.R import im.vector.app.core.extensions.commitTransaction @@ -29,6 +30,8 @@ import im.vector.app.core.platform.GenericIdArgs import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivitySimpleLoadingBinding import im.vector.app.features.spaces.share.ShareSpaceBottomSheet +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class SpacePeopleActivity : VectorBaseActivity() { @@ -73,8 +76,8 @@ class SpacePeopleActivity : VectorBaseActivity() { sharedActionViewModel = viewModelProvider.get(SpacePeopleSharedActionViewModel::class.java) sharedActionViewModel - .observe() - .subscribe { sharedAction -> + .stream() + .onEach { sharedAction -> when (sharedAction) { SpacePeopleSharedAction.Dismiss -> finish() is SpacePeopleSharedAction.NavigateToRoom -> navigateToRooms(sharedAction) @@ -86,7 +89,7 @@ class SpacePeopleActivity : VectorBaseActivity() { ShareSpaceBottomSheet.show(supportFragmentManager, sharedAction.spaceId) } } - }.disposeOnDestroy() + }.launchIn(lifecycleScope) } private fun navigateToRooms(action: SpacePeopleSharedAction.NavigateToRoom) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt index 6e14893f77..dad8ecfcca 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/people/SpacePeopleFragment.kt @@ -91,7 +91,7 @@ class SpacePeopleFragment @Inject constructor( handleViewEvents(it) } - viewModel.subscribe(this) { + viewModel.onEach { when (it.createAndInviteState) { is Loading -> sharedActionViewModel.post(SpacePeopleSharedAction.ShowModalLoading) Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index fde69ce9ba..c7ffc43727 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -160,10 +160,10 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User knownUsersSearch .sample(300) - .flowOn(Dispatchers.Main) .flatMapLatest { search -> session.getPagedUsersLive(search, state.excludedUserIds).asFlow() - }.execute { + } + .execute { copy(knownUsers = it) } diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index f2a087fd52..5c0cfd265b 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -27,17 +27,17 @@ fun String.trimIndentOneLine() = trimIndent().replace("\n", "") fun VectorViewModel.test(): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } - val viewEvents = viewEvents.observe().test() - return ViewModelTest(state, viewEvents) + //val viewEvents = viewEvents.stream().test() + return ViewModelTest(state) } class ViewModelTest( val state: () -> S, - val viewEvents: TestObserver + //val viewEvents: TestObserver ) { fun assertEvents(vararg expected: VE) { - viewEvents.assertValues(*expected) + //viewEvents.assertValues(*expected) } fun assertState(expected: S) { From 9479342a643aee1803ffeadb4bed27790c4ca397 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 15:16:10 +0200 Subject: [PATCH 009/229] Flow: remove more rx --- dependencies.gradle | 1 - vector/build.gradle | 1 - .../vector/app/features/home/HomeActivity.kt | 3 +-- .../app/features/home/HomeDetailViewModel.kt | 2 -- .../app/features/home/ShortcutsHandler.kt | 20 ++++++++++--------- .../home/UnreadMessagesSharedViewModel.kt | 2 -- .../room/list/RoomListSectionBuilderSpace.kt | 1 - .../settings/SecretsSynchronisationInfo.kt | 1 - .../VectorSettingsSecurityPrivacyFragment.kt | 1 - 9 files changed, 12 insertions(+), 20 deletions(-) diff --git a/dependencies.gradle b/dependencies.gradle index 1e77b6354b..776e89f9b3 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -102,7 +102,6 @@ ext.libs = [ 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", 'mavericks' : "com.airbnb.android:mavericks:$mavericks", - 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks", 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" ], mockk : [ diff --git a/vector/build.gradle b/vector/build.gradle index 35d1acfe8b..a5d10e222b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -333,7 +333,6 @@ configurations { dependencies { implementation project(":matrix-sdk-android") - implementation project(":matrix-sdk-android-rx") implementation project(":matrix-sdk-android-flow") implementation project(":diff-match-patch") implementation project(":multipicker") diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 04ca25332f..039b7c3e7b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -247,8 +247,7 @@ class HomeActivity : } homeActivityViewModel.onEach { renderState(it) } - shortcutsHandler.observeRoomsAndBuildShortcuts() - .disposeOnDestroy() + shortcutsHandler.observeRoomsAndBuildShortcuts(lifecycleScope) if (!vectorPreferences.didPromoteNewRestrictedFeature()) { promoteRestrictedViewModel.onEach { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 8bfc1a8db4..a7d361c757 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -56,9 +56,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.asObservable import timber.log.Timber -import java.util.concurrent.TimeUnit /** * View model used to update the home bottom bar notification counts, observe the sync state and diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 7514d455aa..ff553577a0 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -21,13 +21,15 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat +import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder -import io.reactivex.disposables.Disposable -import io.reactivex.disposables.Disposables +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.asObservable import javax.inject.Inject class ShortcutsHandler @Inject constructor( @@ -36,12 +38,11 @@ class ShortcutsHandler @Inject constructor( private val activeSessionHolder: ActiveSessionHolder ) { - fun observeRoomsAndBuildShortcuts(): Disposable { + fun observeRoomsAndBuildShortcuts(coroutineScope: CoroutineScope): Job { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op - return Disposables.empty() + return Job() } - return activeSessionHolder.getSafeActiveSession() ?.getPagedRoomSummariesLive( roomSummaryQueryParams { @@ -49,8 +50,8 @@ class ShortcutsHandler @Inject constructor( }, sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) - ?.asObservable() - ?.subscribe { rooms -> + ?.asFlow() + ?.onEach { rooms -> // Remove dead shortcuts (i.e. deleted rooms) val roomIds = rooms.map { it.roomId } val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) @@ -66,7 +67,8 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) } } - ?: Disposables.empty() + ?.launchIn(coroutineScope) + ?: Job() } fun clearShortcuts() { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 3434f9dfb0..4cfe0f7546 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -44,8 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.rx.asObservable -import java.util.concurrent.TimeUnit data class UnreadMessagesState( val homeSpaceUnread: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0), diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 0bf7087618..1e806bb2d8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -52,7 +52,6 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.rx.asObservable import timber.log.Timber class RoomListSectionBuilderSpace( diff --git a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt index 5afcb77587..e21366db02 100644 --- a/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt +++ b/vector/src/main/java/im/vector/app/features/settings/SecretsSynchronisationInfo.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NA import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.flow.flow -import org.matrix.android.sdk.rx.SecretsSynchronisationInfo data class SecretsSynchronisationInfo( val isBackupSetup: Boolean, diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 2c32070c76..ca96d6a6bc 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -71,7 +71,6 @@ import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse -import org.matrix.android.sdk.rx.SecretsSynchronisationInfo import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( From 8cf5b727e1eba92386bcf813f18a7e42f14779e4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 15:57:18 +0200 Subject: [PATCH 010/229] Flow: restore read receipts --- .../im/vector/app/core/flow/ChunkOperator.kt | 79 +++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 20 +++-- 2 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt diff --git a/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt b/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt new file mode 100644 index 0000000000..533a060883 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/flow/ChunkOperator.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.flow + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.produce +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.selects.select + +@ExperimentalCoroutinesApi +fun Flow.chunk(durationInMillis: Long): Flow> { + require(durationInMillis> 0) { "Duration should be greater than 0" } + return flow { + coroutineScope { + val events = ArrayList() + val ticker = fixedPeriodTicker(durationInMillis) + try { + val upstreamValues = produce(capacity = Channel.CONFLATED) { + collect { value -> send(value) } + } + while (isActive) { + var hasTimedOut = false + select { + upstreamValues.onReceive { + events.add(it) + } + ticker.onReceive { + hasTimedOut = true + } + } + if (hasTimedOut && events.isNotEmpty()) { + emit(events.toList()) + events.clear() + } + } + } catch (e: ClosedReceiveChannelException) { + // drain remaining events + if (events.isNotEmpty()) emit(events.toList()) + } finally { + ticker.cancel() + } + } + } +} + +private fun CoroutineScope.fixedPeriodTicker(delayMillis: Long, initialDelayMillis: Long = delayMillis): ReceiveChannel { + require(delayMillis >= 0) { "Expected non-negative delay, but has $delayMillis ms" } + require(initialDelayMillis >= 0) { "Expected non-negative initial delay, but has $initialDelayMillis ms" } + return produce(capacity = 0) { + delay(initialDelayMillis) + while (true) { + channel.send(Unit) + delay(delayMillis) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index ee929243b5..1b765f81b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -33,6 +33,7 @@ import dagger.assisted.AssistedInject import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.flow.chunk import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -58,13 +59,17 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.VoicePlayerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixPatterns @@ -863,13 +868,13 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeEventDisplayedActions() { // We are buffering scroll events for one second // and keep the most recent one to set the read receipt on. - /* + visibleEventsSource .stream() - .buffer(1, TimeUnit.SECONDS) + .chunk(1000) .filter { it.isNotEmpty() } - .subscribeBy(onNext = { actions -> - val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@subscribeBy + .onEach { actions -> + val bufferedMostRecentDisplayedEvent = actions.maxByOrNull { it.event.displayIndex }?.event ?: return@onEach val globalMostRecentDisplayedEvent = mostRecentDisplayedEvent if (trackUnreadMessages.get()) { if (globalMostRecentDisplayedEvent == null) { @@ -883,10 +888,9 @@ class RoomDetailViewModel @AssistedInject constructor( tryOrNull { room.setReadReceipt(eventId) } } } - }) - .disposeOnClear() - - */ + } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) } private fun handleMarkAllAsRead() { From a9d192fa399c6bddcc6d3bdede485f64f7765a7e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 18:09:07 +0200 Subject: [PATCH 011/229] Flow migration: add back some test --- dependencies.gradle | 3 +- vector/build.gradle | 2 + .../quads/SharedSecureStorageViewModelTest.kt | 110 +++++++++++------- .../java/im/vector/app/test/Extensions.kt | 22 ++-- .../im/vector/app/test/FlowTestObserver.kt | 53 +++++++++ 5 files changed, 138 insertions(+), 52 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/FlowTestObserver.kt diff --git a/dependencies.gradle b/dependencies.gradle index 776e89f9b3..15660f17f2 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -41,7 +41,8 @@ ext.libs = [ jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", - 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" + 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines", + 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ 'appCompat' : "androidx.appcompat:appcompat:1.3.1", diff --git a/vector/build.gradle b/vector/build.gradle index a5d10e222b..493a26dcc6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -508,6 +508,7 @@ dependencies { // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting + testImplementation libs.jetbrains.coroutinesTest // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' @@ -521,6 +522,7 @@ dependencies { androidTestImplementation libs.androidx.espressoIntents androidTestImplementation libs.tests.kluent androidTestImplementation libs.androidx.coreTesting + androidTestImplementation libs.jetbrains.coroutinesTest // Plant Timber tree for test androidTestImplementation libs.tests.timberJunitRule // "The one who serves a great Espresso" diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index 506ac9c7d0..00828acbb8 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -22,6 +22,7 @@ import im.vector.app.test.InstantRxRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.test +import kotlinx.coroutines.test.runBlockingTest import org.junit.Rule import org.junit.Test import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -41,6 +42,7 @@ class SharedSecureStorageViewModelTest { @get:Rule val instantRx = InstantRxRule() + @get:Rule val mvrxTestRule = MvRxTestRule() @@ -50,78 +52,100 @@ class SharedSecureStorageViewModelTest { @Test fun `given a key info with passphrase when initialising then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - - val viewModel = createViewModel() - - viewModel.test().assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given a key info without passphrase when initialising then step is EnterKey`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() + val viewModel = createViewModel() - viewModel.test().assertState(aViewState( - hasPassphrase = false, - step = SharedSecureStorageViewState.Step.EnterKey - )) + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = false, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given on EnterKey step when going back then dismisses`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() - - viewModel.handle(SharedSecureStorageAction.Back) - - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + val viewModel = createViewModel() + val test = viewModel.test(this) + viewModel.handle(SharedSecureStorageAction.Back) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } @Test fun `given on passphrase step when using key then step is EnterKey`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.UseKey) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterKey - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given on passphrase step when going back then dismisses`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } private fun createViewModel(): SharedSecureStorageViewModel { diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 5c0cfd265b..67b5090785 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -20,27 +20,33 @@ import com.airbnb.mvrx.MavericksState import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import io.reactivex.observers.TestObserver +import kotlinx.coroutines.CoroutineScope import org.amshove.kluent.shouldBeEqualTo fun String.trimIndentOneLine() = trimIndent().replace("\n", "") -fun VectorViewModel.test(): ViewModelTest { +fun VectorViewModel.test(coroutineScope: CoroutineScope): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } - //val viewEvents = viewEvents.stream().test() - return ViewModelTest(state) + val viewEvents = viewEvents.stream().test(coroutineScope) + return ViewModelTest(state, viewEvents) } class ViewModelTest( val state: () -> S, - //val viewEvents: TestObserver + val viewEvents: FlowTestObserver ) { - fun assertEvents(vararg expected: VE) { - //viewEvents.assertValues(*expected) + fun assertEvents(vararg expected: VE): ViewModelTest { + viewEvents.assertValues(*expected) + return this } - fun assertState(expected: S) { + fun assertState(expected: S): ViewModelTest { state() shouldBeEqualTo expected + return this + } + + fun finish(){ + viewEvents.finish() } } diff --git a/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt new file mode 100644 index 0000000000..955922d0c4 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.junit.Assert.assertEquals + +fun Flow.test(scope: CoroutineScope): FlowTestObserver { + return FlowTestObserver(scope, this) +} + +class FlowTestObserver( + scope: CoroutineScope, + flow: Flow +) { + private val values = mutableListOf() + private val job: Job = flow + .onEach { + values.add(it) + }.launchIn(scope) + + fun assertNoValues(): FlowTestObserver { + assertEquals(emptyList(), this.values) + return this + } + + fun assertValues(vararg values: T): FlowTestObserver { + assertEquals(values.toList(), this.values) + return this + } + + fun finish() { + job.cancel() + } +} From 6cee266a959fa58c0ff55d312399cb7da85f4164 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 27 Oct 2021 13:10:01 +0300 Subject: [PATCH 012/229] Create poll UI implementation. --- .../features/createpoll/CreatePollAction.kt | 5 + .../createpoll/CreatePollController.kt | 100 ++++++++++++++++++ .../features/createpoll/CreatePollFragment.kt | 36 ++++++- .../createpoll/CreatePollViewModel.kt | 57 ++++++++++ .../createpoll/CreatePollViewState.kt | 3 +- .../form/FormEditTextWithDeleteItem.kt | 87 +++++++++++++++ .../main/res/layout/fragment_create_poll.xml | 18 ++++ .../item_form_text_input_with_delete.xml | 43 ++++++++ vector/src/main/res/values/strings.xml | 6 ++ 9 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt create mode 100644 vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt create mode 100644 vector/src/main/res/layout/item_form_text_input_with_delete.xml diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt index ad8da6e208..0812248487 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollAction.kt @@ -19,4 +19,9 @@ package im.vector.app.features.createpoll import im.vector.app.core.platform.VectorViewModelAction sealed class CreatePollAction : VectorViewModelAction { + data class OnQuestionChanged(val question: String) : CreatePollAction() + data class OnOptionChanged(val index: Int, val option: String) : CreatePollAction() + data class OnDeleteOption(val index: Int) : CreatePollAction() + object OnAddOption : CreatePollAction() + object OnCreatePoll : CreatePollAction() } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt new file mode 100644 index 0000000000..cf4ec19581 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollController.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createpoll + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.R +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.ItemStyle +import im.vector.app.core.ui.list.genericButtonItem +import im.vector.app.core.ui.list.genericItem +import im.vector.app.features.form.formEditTextItem +import im.vector.app.features.form.formEditTextWithDeleteItem +import javax.inject.Inject + +class CreatePollController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + private var state: CreatePollViewState? = null + var callback: Callback? = null + + fun setData(state: CreatePollViewState) { + this.state = state + requestModelBuild() + } + + override fun buildModels() { + val currentState = state ?: return + val host = this + + genericItem { + id("question_title") + style(ItemStyle.BIG_TEXT) + title(host.stringProvider.getString(R.string.create_poll_question_title)) + } + + formEditTextItem { + id("question") + value(currentState.question) + hint(host.stringProvider.getString(R.string.create_poll_question_hint)) + singleLine(false) + maxLength(500) + onTextChange { + host.callback?.onQuestionChanged(it) + } + } + + genericItem { + id("options_title") + style(ItemStyle.BIG_TEXT) + title(host.stringProvider.getString(R.string.create_poll_options_title)) + } + + currentState.options.forEachIndexed { index, option -> + formEditTextWithDeleteItem { + id("option_$index") + value(option) + hint(host.stringProvider.getString(R.string.create_poll_options_hint, (index + 1))) + onTextChange { + host.callback?.onOptionChanged(index, it) + } + onDeleteClicked { + host.callback?.onDeleteOption(index) + } + } + } + + genericButtonItem { + id("add_option") + text(host.stringProvider.getString(R.string.create_poll_add_option)) + textColor(host.colorProvider.getColor(R.color.palette_element_green)) + buttonClickAction { + host.callback?.onAddOption() + } + } + } + + interface Callback { + fun onQuestionChanged(question: String) + fun onOptionChanged(index: Int, option: String) + fun onDeleteOption(index: Int) + fun onAddOption() + } +} diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt index 706d58e489..ad366da62e 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollFragment.kt @@ -20,11 +20,18 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentCreatePollBinding import javax.inject.Inject -class CreatePollFragment @Inject constructor() : VectorBaseFragment() { +class CreatePollFragment @Inject constructor( + private val controller: CreatePollController +) : VectorBaseFragment(), CreatePollController.Callback { + + private val viewModel: CreatePollViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCreatePollBinding { return FragmentCreatePollBinding.inflate(inflater, container, false) @@ -34,8 +41,35 @@ class CreatePollFragment @Inject constructor() : VectorBaseFragment handleOnCreatePoll() + CreatePollAction.OnAddOption -> handleOnAddOption() + is CreatePollAction.OnDeleteOption -> handleOnDeleteOption(action.index) + is CreatePollAction.OnOptionChanged -> handleOnOptionChanged(action.index, action.option) + is CreatePollAction.OnQuestionChanged -> handleOnQuestionChanged(action.question) + } + } + + private fun handleOnCreatePoll() = withState { state -> + Timber.d(state.toString()) + } + + private fun handleOnAddOption() { + setState { + val extendedOptions = options + "" + copy( + options = extendedOptions + ) + } + } + + private fun handleOnDeleteOption(index: Int) { + setState { + val filteredOptions = options.filterIndexed { ind, _ -> ind != index } + copy( + options = filteredOptions + ) + } + } + + private fun handleOnOptionChanged(index: Int, option: String) { + setState { + val changedOptions = options.mapIndexed { ind, s -> if(ind == index) option else s } + copy( + options = changedOptions + ) + } + } + + private fun handleOnQuestionChanged(question: String) { + setState { + copy( + question = question + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt index f53e7b2843..f9d46d5605 100644 --- a/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt +++ b/vector/src/main/java/im/vector/app/features/createpoll/CreatePollViewState.kt @@ -19,5 +19,6 @@ package im.vector.app.features.createpoll import com.airbnb.mvrx.MavericksState data class CreatePollViewState( - val question: String = "" + val question: String = "", + val options: List = emptyList() ) : MavericksState diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt new file mode 100644 index 0000000000..a4e378a5b9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithDeleteItem.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.form + +import android.text.Editable +import android.widget.ImageButton +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.TextListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.addTextChangedListenerOnce +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextIfDifferent +import im.vector.app.core.platform.SimpleTextWatcher + +@EpoxyModelClass(layout = R.layout.item_form_text_input_with_delete) +abstract class FormEditTextWithDeleteItem : VectorEpoxyModel() { + + @EpoxyAttribute + var hint: String? = null + + @EpoxyAttribute + var value: String? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onTextChange: TextListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var onDeleteClicked: ClickListener? = null + + private val onTextChangeListener = object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + onTextChange?.invoke(s.toString()) + } + } + + override fun bind(holder: Holder) { + super.bind(holder) + holder.textInputLayout.isEnabled = enabled + holder.textInputLayout.hint = hint + + holder.textInputEditText.setTextIfDifferent(value) + + holder.textInputEditText.isEnabled = enabled + + holder.textInputEditText.addTextChangedListenerOnce(onTextChangeListener) + + holder.textInputDeleteButton.onClick(onDeleteClicked) + } + + override fun shouldSaveViewState(): Boolean { + return false + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + holder.textInputEditText.removeTextChangedListener(onTextChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val textInputLayout by bind(R.id.formTextInputTextInputLayout) + val textInputEditText by bind(R.id.formTextInputTextInputEditText) + val textInputDeleteButton by bind(R.id.formTextInputDeleteButton) + } +} diff --git a/vector/src/main/res/layout/fragment_create_poll.xml b/vector/src/main/res/layout/fragment_create_poll.xml index 76c744c6c5..0616b76150 100644 --- a/vector/src/main/res/layout/fragment_create_poll.xml +++ b/vector/src/main/res/layout/fragment_create_poll.xml @@ -60,4 +60,22 @@ + + +