diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 4a3379cb5a..25121dc415 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -31,6 +31,7 @@ import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager import im.vector.app.core.utils.AssetReader import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.crypto.keysrequest.KeyRequestHandler @@ -65,6 +66,7 @@ import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.widgets.model.WidgetType import javax.inject.Singleton @Component(modules = [VectorModule::class]) @@ -167,6 +169,8 @@ interface VectorComponent { fun roomSummaryHolder(): RoomSummariesHolder + fun jitsiActiveConferenceHolder(): JitsiActiveConferenceHolder + @Component.Factory interface Factory { fun create(@BindsInstance context: Context): VectorComponent diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiActiveConferenceHolder.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiActiveConferenceHolder.kt new file mode 100644 index 0000000000..1a9fc5ea10 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiActiveConferenceHolder.kt @@ -0,0 +1,46 @@ +/* + * 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.call.conference + +import android.content.Context +import androidx.lifecycle.ProcessLifecycleOwner +import org.jitsi.meet.sdk.BroadcastEvent +import org.matrix.android.sdk.api.extensions.orFalse +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class JitsiActiveConferenceHolder @Inject constructor(context: Context) { + + private var activeConference: String? = null + + init { + ProcessLifecycleOwner.get().lifecycle.addObserver(JitsiBroadcastEventObserver(context, this::onBroadcastEvent)) + } + + fun isJoined(confId: String?): Boolean { + return confId != null && activeConference?.endsWith(confId).orFalse() + } + + private fun onBroadcastEvent(broadcastEvent: BroadcastEvent) { + when (broadcastEvent.type) { + BroadcastEvent.Type.CONFERENCE_JOINED -> activeConference = broadcastEvent.extractConferenceUrl() + BroadcastEvent.Type.CONFERENCE_TERMINATED -> activeConference = null + else -> Unit + } + } +} 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 eb30a16de2..0c271592fc 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 @@ -87,9 +87,8 @@ import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.ui.views.CurrentCallsViewPresenter - import im.vector.app.core.ui.views.CurrentCallsView +import im.vector.app.core.ui.views.CurrentCallsViewPresenter import im.vector.app.core.ui.views.FailedMessagesWarningView import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer @@ -831,14 +830,8 @@ class RoomDetailFragment @Inject constructor( val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps) val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0 - if (widgetsCount > 0) { - val actionView = matrixAppsMenuItem.actionView - actionView - .findViewById(R.id.action_view_icon_image) - .setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary)) - actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount") - matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) - } else { + val hasOnlyJitsiWidget = widgetsCount == 1 && state.hasActiveJitsiWidget() + if (widgetsCount == 0 || hasOnlyJitsiWidget) { // icon should be default color no badge val actionView = matrixAppsMenuItem.actionView actionView @@ -846,6 +839,13 @@ class RoomDetailFragment @Inject constructor( .setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary)) actionView.findViewById(R.id.cart_badge).isVisible = false matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } else { + val actionView = matrixAppsMenuItem.actionView + actionView + .findViewById(R.id.action_view_icon_image) + .setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary)) + actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount") + matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) } } } 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 c0a76aeb8f..8da667aac3 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 @@ -38,8 +38,8 @@ 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.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiService -import im.vector.app.features.call.conference.extractConferenceUrl import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.CommandParser @@ -67,7 +67,6 @@ import org.commonmark.renderer.html.HtmlRenderer import org.jitsi.meet.sdk.BroadcastEvent import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -121,6 +120,7 @@ class RoomDetailViewModel @AssistedInject constructor( private val chatEffectManager: ChatEffectManager, private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, + private val activeConferenceHolder: JitsiActiveConferenceHolder, timelineFactory: TimelineFactory ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { @@ -254,7 +254,8 @@ class RoomDetailViewModel @AssistedInject constructor( copy( jitsiState = jitsiState.copy( confId = jitsiConfId, - widgetId = jitsiWidget?.widgetId + widgetId = jitsiWidget?.widgetId, + hasJoined = activeConferenceHolder.isJoined(jitsiConfId) ) ) } @@ -363,9 +364,7 @@ class RoomDetailViewModel @AssistedInject constructor( when (action.broadcastEvent.type) { BroadcastEvent.Type.CONFERENCE_JOINED, BroadcastEvent.Type.CONFERENCE_TERMINATED -> { - if (action.broadcastEvent.extractConferenceUrl()?.endsWith(state.jitsiState.confId).orFalse()) { - setState { copy(jitsiState = jitsiState.copy(hasJoined = action.broadcastEvent.type == BroadcastEvent.Type.CONFERENCE_JOINED)) } - } + setState { copy(jitsiState = jitsiState.copy(hasJoined = activeConferenceHolder.isJoined(jitsiState.confId))) } } else -> Unit } @@ -683,8 +682,9 @@ class RoomDetailViewModel @AssistedInject constructor( R.id.invite -> state.canInvite R.id.open_matrix_apps -> true R.id.voice_call -> state.isWebRTCCallOptionAvailable() - R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.widgetId == null || state.jitsiState.hasJoined - R.id.join_conference -> state.jitsiState.widgetId != null && !state.jitsiState.hasJoined + R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined + // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ + R.id.join_conference -> state.jitsiState.confId != null && !state.jitsiState.hasJoined R.id.search -> true R.id.dev_tools -> vectorPreferences.developerMode() else -> false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 89afe3ec7c..35127ad037 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -18,7 +18,10 @@ package im.vector.app.features.home.room.detail import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized +import im.vector.app.core.platform.ButtonStateView +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary @@ -26,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.widgets.model.Widget +import org.matrix.android.sdk.api.session.widgets.model.WidgetType /** * Describes the current send mode: @@ -96,7 +100,9 @@ data class RoomDetailViewState( fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2 - fun hasActiveJitsiWidget() = jitsiState.confId != null + // This checks directly on the active room widgets. + // It can differs for a short period of time on the JitsiState as its computed async. + fun hasActiveJitsiWidget() = activeRoomWidgets()?.any { it.type == WidgetType.Jitsi && it.isActive }.orFalse() fun isDm() = asyncRoomSummary()?.isDirect == true }