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 a87eb92b13..1e29dfff5e 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 @@ -128,9 +128,11 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseActivity::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { hideWaitingView() observer(it) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index ad7a86c899..a44fb1c9ac 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 @@ -205,9 +205,11 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseBottomSheetDialogFragment::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { observer(it) } diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 9f79db9c66..a82cef54e5 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 @@ -277,9 +277,11 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorBaseFragment::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { dismissLoadingDialog() observer(it) 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 c9d58f9545..3dd38c455f 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 @@ -18,15 +18,16 @@ package im.vector.app.core.platform import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksViewModel -import im.vector.app.core.utils.DataSource -import im.vector.app.core.utils.PublishDataSource +import im.vector.app.core.utils.EventQueue +import im.vector.app.core.utils.SharedEvents abstract class VectorViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(initialState: S) : MavericksViewModel<S>(initialState) { // Used to post transient events to the View - protected val _viewEvents = PublishDataSource<VE>() - val viewEvents: DataSource<VE> = _viewEvents + protected val _viewEvents = EventQueue<VE>(capacity = 64) + val viewEvents: SharedEvents<VE> + get() = _viewEvents abstract fun handle(action: VA) } diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt new file mode 100644 index 0000000000..e712769c48 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.utils + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.transform +import java.util.concurrent.CopyOnWriteArraySet + +interface SharedEvents<out T> { + fun stream(consumerId: String): Flow<T> +} + +class EventQueue<T>(capacity: Int) : SharedEvents<T> { + + private val innerQueue = MutableSharedFlow<OneTimeEvent<T>>(replay = capacity) + + fun post(event: T) { + innerQueue.tryEmit(OneTimeEvent(event)) + } + + override fun stream(consumerId: String): Flow<T> = innerQueue.filterNotHandledBy(consumerId) +} + +/** + * Event designed to be delivered only once to a concrete entity, + * but it can also be delivered to multiple different entities. + * + * Keeps track of who has already handled its content. + */ +private class OneTimeEvent<out T>(private val content: T) { + + private val handlers = CopyOnWriteArraySet<String>() + + /** + * @param asker Used to identify, whether this "asker" has already handled this Event. + * @return Event content or null if it has been already handled by asker + */ + fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null +} + +private fun <T> Flow<OneTimeEvent<T>>.filterNotHandledBy(consumerId: String): Flow<T> = transform { event -> + event.getIfNotHandled(consumerId)?.let { emit(it) } +} diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 9f9488e35d..0d240b376b 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -239,11 +239,12 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt } private fun observeViewEvents() { + val tag = this::class.simpleName.toString() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel .viewEvents - .stream() + .stream(tag) .collect(::handleViewEvents) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt index 6299d8962d..724807a81e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt @@ -72,10 +72,11 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick protected fun <T : VectorViewEvents> VectorViewModel<*, *, T>.observeViewEvents( observer: (T) -> Unit, ) { + val tag = this@VectorSettingsBaseFragment::class.simpleName.toString() lifecycleScope.launch { - repeatOnLifecycle(state) { repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewEvents.stream() + viewEvents + .stream(tag) .collect { observer(it) }