diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewActions.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewActions.kt index 6554e50d63..830af7de01 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewActions.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewActions.kt @@ -18,4 +18,12 @@ package im.vector.app.features.call.conference import im.vector.app.core.platform.VectorViewModelAction -sealed class JitsiCallViewActions : VectorViewModelAction +sealed class JitsiCallViewActions : VectorViewModelAction { + data class SwitchTo(val args: VectorJitsiActivity.Args, + val withConfirmation: Boolean) : JitsiCallViewActions() + + /** + * The ViewModel will either ask the View to finish, or to join another conf. + */ + object OnConferenceLeft: JitsiCallViewActions() +} diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewEvents.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewEvents.kt index 4c62e34da1..d41f758f52 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewEvents.kt @@ -27,4 +27,11 @@ sealed class JitsiCallViewEvents : VectorViewEvents { val confId: String, val userInfo: JitsiMeetUserInfo ) : JitsiCallViewEvents() + + data class ConfirmSwitchingConference( + val args: VectorJitsiActivity.Args + ) : JitsiCallViewEvents() + + object LeaveConference : JitsiCallViewEvents() + object Finish : JitsiCallViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt index b46bdd50e5..cf330ee4d6 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt @@ -19,12 +19,15 @@ package im.vector.app.features.call.conference import com.airbnb.mvrx.Fail import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.themes.ThemeProvider +import io.reactivex.disposables.Disposable import org.jitsi.meet.sdk.JitsiMeetUserInfo import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -36,7 +39,6 @@ import java.net.URL class JitsiCallViewModel @AssistedInject constructor( @Assisted initialState: JitsiCallViewState, - @Assisted private val args: VectorJitsiActivity.Args, private val session: Session, private val jitsiMeetPropertiesFactory: JitsiWidgetPropertiesFactory, private val themeProvider: ThemeProvider @@ -44,15 +46,23 @@ class JitsiCallViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(initialState: JitsiCallViewState, args: VectorJitsiActivity.Args): JitsiCallViewModel + fun create(initialState: JitsiCallViewState): JitsiCallViewModel } + private var currentWidgetObserver: Disposable? = null private val widgetService = session.widgetService() private var confIsStarted = false + private var pendingArgs: VectorJitsiActivity.Args? = null init { - widgetService.getRoomWidgetsLive(args.roomId, QueryStringValue.Equals(args.widgetId), WidgetType.Jitsi.values()) + observeWidget(initialState.roomId, initialState.widgetId) + } + + private fun observeWidget(roomId: String, widgetId: String) { + confIsStarted = false + currentWidgetObserver?.dispose() + currentWidgetObserver = widgetService.getRoomWidgetsLive(roomId, QueryStringValue.Equals(widgetId), WidgetType.Jitsi.values()) .asObservable() .distinctUntilChanged() .subscribe { @@ -77,19 +87,19 @@ class JitsiCallViewModel @AssistedInject constructor( .disposeOnClear() } - private fun startConference(jitsiWidget: Widget) { - val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem() + private fun startConference(jitsiWidget: Widget) = withState { state -> + val me = session.getRoomMember(session.myUserId, state.roomId)?.toMatrixItem() val userInfo = JitsiMeetUserInfo().apply { displayName = me?.getBestName() avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) } } - val roomName = session.getRoomSummary(args.roomId)?.displayName + val roomName = session.getRoomSummary(state.roomId)?.displayName val ppt = widgetService.getWidgetComputedUrl(jitsiWidget, themeProvider.isLightTheme()) ?.let { url -> jitsiMeetPropertiesFactory.create(url) } _viewEvents.post(JitsiCallViewEvents.StartConference( - enableVideo = args.enableVideo, + enableVideo = state.enableVideo, jitsiUrl = "https://${ppt?.domain}", subject = roomName ?: "", confId = ppt?.confId ?: "", @@ -98,6 +108,45 @@ class JitsiCallViewModel @AssistedInject constructor( } override fun handle(action: JitsiCallViewActions) { + when (action) { + is JitsiCallViewActions.SwitchTo -> handleSwitchTo(action) + JitsiCallViewActions.OnConferenceLeft -> handleOnConferenceLeft() + }.exhaustive + } + + private fun handleSwitchTo(action: JitsiCallViewActions.SwitchTo) = withState { state -> + // Check if it is the same conf + if (action.args.roomId != state.roomId + || action.args.widgetId != state.widgetId) { + if (action.withConfirmation) { + // Ask confirmation to switch + _viewEvents.post(JitsiCallViewEvents.ConfirmSwitchingConference(action.args)) + } else { + // Ask the view to leave the conf, then the view will tell us when it's done, to join the new conf + pendingArgs = action.args + _viewEvents.post(JitsiCallViewEvents.LeaveConference) + } + } + } + + private fun handleOnConferenceLeft() { + val safePendingArgs = pendingArgs + pendingArgs = null + + if (safePendingArgs == null) { + // Quit + _viewEvents.post(JitsiCallViewEvents.Finish) + } else { + setState { + copy( + roomId = safePendingArgs.roomId, + widgetId = safePendingArgs.widgetId, + enableVideo = safePendingArgs.enableVideo, + widget = Uninitialized + ) + } + observeWidget(safePendingArgs.roomId, safePendingArgs.widgetId) + } } companion object : MvRxViewModelFactory { @@ -107,8 +156,7 @@ class JitsiCallViewModel @AssistedInject constructor( @JvmStatic override fun create(viewModelContext: ViewModelContext, state: JitsiCallViewState): JitsiCallViewModel? { val callActivity: VectorJitsiActivity = viewModelContext.activity() - val callArgs: VectorJitsiActivity.Args = viewModelContext.args() - return callActivity.viewModelFactory.create(state, callArgs) + return callActivity.viewModelFactory.create(state) } override fun initialState(viewModelContext: ViewModelContext): JitsiCallViewState? { @@ -116,7 +164,8 @@ class JitsiCallViewModel @AssistedInject constructor( return JitsiCallViewState( roomId = args.roomId, - widgetId = args.widgetId + widgetId = args.widgetId, + enableVideo = args.enableVideo ) } } diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt index fe327f5867..1fd04542e0 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewState.kt @@ -24,5 +24,6 @@ import org.matrix.android.sdk.api.session.widgets.model.Widget data class JitsiCallViewState( val roomId: String = "", val widgetId: String = "", + val enableVideo: Boolean = false, val widget: Async = Uninitialized ) : MvRxState 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 45e5f5a2da..16478192f7 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 @@ -25,6 +25,7 @@ import android.content.res.Configuration import android.os.Bundle import android.os.Parcelable import android.widget.FrameLayout +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.airbnb.mvrx.Fail @@ -32,6 +33,7 @@ import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.Success import com.airbnb.mvrx.viewModel import com.facebook.react.modules.core.PermissionListener +import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity @@ -85,13 +87,31 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee jitsiViewModel.observeViewEvents { when (it) { - is JitsiCallViewEvents.StartConference -> configureJitsiView(it) + is JitsiCallViewEvents.StartConference -> configureJitsiView(it) + is JitsiCallViewEvents.ConfirmSwitchingConference -> handleConfirmSwitching(it) + JitsiCallViewEvents.Finish -> finish() + JitsiCallViewEvents.LeaveConference -> handleLeaveConference() }.exhaustive } registerForBroadcastMessages() } + private fun handleLeaveConference() { + jitsiMeetView?.leave() + } + + private fun handleConfirmSwitching(action: JitsiCallViewEvents.ConfirmSwitchingConference) { + AlertDialog.Builder(this) + .setTitle(R.string.dialog_title_warning) + .setMessage(R.string.jitsi_leave_conf_to_join_another_one_content) + .setPositiveButton(R.string.action_switch) { _, _ -> + jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(action.args, false)) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) @@ -170,6 +190,14 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee override fun onNewIntent(intent: Intent?) { JitsiMeetActivityDelegate.onNewIntent(intent) + + // Is it a switch to another conf? + intent?.takeIf { it.hasExtra(MvRx.KEY_ARG) } + ?.let { intent.getParcelableExtra(MvRx.KEY_ARG) } + ?.let { + jitsiViewModel.handle(JitsiCallViewActions.SwitchTo(it, true)) + } + super.onNewIntent(intent) } @@ -211,7 +239,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMee Timber.v("JitsiMeetViewListener.onConferenceTerminated()") // Do not finish if there is an error if (data["error"] == null) { - finish() + jitsiViewModel.handle(JitsiCallViewActions.OnConferenceLeft) } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a3eade1f02..320f14eccd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -133,6 +133,7 @@ Close Copy Add + Switch Unpublish Copied to clipboard Disable @@ -1318,6 +1319,8 @@ Sorry, conference calls with Jitsi are not supported on old devices (devices with Android OS below 6.0) + Leave the current conference and switch to the other one? + This widget wants to use the following resources: Allow Block All