From 8e2a1d3bcd539bd8754313c90468b8633afb1292 Mon Sep 17 00:00:00 2001 From: ganfra <francoisg@element.io> Date: Wed, 7 Jul 2021 21:43:39 +0200 Subject: [PATCH] Jitsi call: implement RemoveJitsiWidgetView --- .../app/core/ui/views/ActiveConferenceView.kt | 110 ------------ .../call/conference/RemoveJitsiWidgetView.kt | 159 ++++++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 48 ++---- .../home/room/detail/RoomDetailViewModel.kt | 48 ++++-- .../home/room/detail/RoomDetailViewState.kt | 3 +- .../timeline/factory/WidgetItemFactory.kt | 5 - .../main/res/layout/fragment_room_detail.xml | 15 +- .../layout/view_active_conference_view.xml | 43 ----- .../res/layout/view_remove_jitsi_widget.xml | 123 ++++++++++++++ vector/src/main/res/values/strings.xml | 5 + 10 files changed, 345 insertions(+), 214 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt create mode 100644 vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt delete mode 100644 vector/src/main/res/layout/view_active_conference_view.xml create mode 100644 vector/src/main/res/layout/view_remove_jitsi_widget.xml diff --git a/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt b/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt deleted file mode 100644 index 256f2d963e..0000000000 --- a/vector/src/main/java/im/vector/app/core/ui/views/ActiveConferenceView.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2020 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.core.ui.views - -import android.content.Context -import android.text.SpannableString -import android.text.method.LinkMovementMethod -import android.text.style.ClickableSpan -import android.util.AttributeSet -import android.view.View -import android.widget.RelativeLayout -import androidx.core.view.isVisible -import im.vector.app.R -import im.vector.app.core.utils.tappableMatchingText -import im.vector.app.databinding.ViewActiveConferenceViewBinding -import im.vector.app.features.home.room.detail.RoomDetailViewState -import im.vector.app.features.themes.ThemeUtils -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.session.widgets.model.WidgetType - -class ActiveConferenceView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : RelativeLayout(context, attrs, defStyleAttr) { - - interface Callback { - fun onTapJoinAudio(jitsiWidget: Widget) - fun onTapJoinVideo(jitsiWidget: Widget) - fun onDelete(jitsiWidget: Widget) - } - - var callback: Callback? = null - private var jitsiWidget: Widget? = null - - private lateinit var views: ViewActiveConferenceViewBinding - - init { - setupView() - } - - private fun setupView() { - inflate(context, R.layout.view_active_conference_view, this) - views = ViewActiveConferenceViewBinding.bind(this) - setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary)) - - // "voice" and "video" texts are underlined and clickable - val voiceString = context.getString(R.string.ongoing_conference_call_voice) - val videoString = context.getString(R.string.ongoing_conference_call_video) - - val fullMessage = context.getString(R.string.ongoing_conference_call, voiceString, videoString) - - val styledText = SpannableString(fullMessage) - styledText.tappableMatchingText(voiceString, object : ClickableSpan() { - override fun onClick(widget: View) { - jitsiWidget?.let { - callback?.onTapJoinAudio(it) - } - } - }) - styledText.tappableMatchingText(videoString, object : ClickableSpan() { - override fun onClick(widget: View) { - jitsiWidget?.let { - callback?.onTapJoinVideo(it) - } - } - }) - - views.activeConferenceInfo.apply { - text = styledText - movementMethod = LinkMovementMethod.getInstance() - } - - views.deleteWidgetButton.setOnClickListener { - jitsiWidget?.let { callback?.onDelete(it) } - } - } - - fun render(state: RoomDetailViewState) { - val summary = state.asyncRoomSummary() - if (summary?.membership == Membership.JOIN) { - // We only display banner for 'live' widgets - jitsiWidget = state.activeRoomWidgets()?.firstOrNull { - // for now only jitsi? - it.type == WidgetType.Jitsi - } - - isVisible = jitsiWidget != null - // if sent by me or if i can moderate? - views.deleteWidgetButton.isVisible = state.isAllowedToManageWidgets - } else { - isVisible = false - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt new file mode 100644 index 0000000000..cb26d5416b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/call/conference/RemoveJitsiWidgetView.kt @@ -0,0 +1,159 @@ +/* + * 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.annotation.SuppressLint +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.core.widget.ImageViewCompat +import im.vector.app.R +import im.vector.app.databinding.ViewRemoveJitsiWidgetBinding +import im.vector.app.features.home.room.detail.RoomDetailViewState +import org.matrix.android.sdk.api.session.room.model.Membership + +@SuppressLint("ClickableViewAccessibility") class RemoveJitsiWidgetView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private sealed class State { + object Unmount : State() + object Idle : State() + data class Sliding(val initialX: Float, val translationX: Float, val hasReachedActivationThreshold: Boolean) : State() + object Progress : State() + } + + private val views: ViewRemoveJitsiWidgetBinding + private var state: State = State.Unmount + var onCompleteSliding: (() -> Unit)? = null + + init { + inflate(context, R.layout.view_remove_jitsi_widget, this) + views = ViewRemoveJitsiWidgetBinding.bind(this) + views.removeJitsiSlidingContainer.setOnTouchListener { _, event -> + val currentState = state + return@setOnTouchListener when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (currentState == State.Idle) { + val initialX = views.removeJitsiSlidingContainer.x - event.rawX + updateState(State.Sliding(initialX, 0f, false)) + } + true + } + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> { + if (currentState is State.Sliding) { + if (currentState.hasReachedActivationThreshold) { + updateState(State.Progress) + } else { + updateState(State.Idle) + } + } + true + } + MotionEvent.ACTION_MOVE -> { + if (currentState is State.Sliding) { + val translationX = (currentState.initialX + event.rawX).coerceAtLeast(0f) + val hasReachedActivationThreshold = views.removeJitsiSlidingContainer.width + translationX >= views.removeJitsiHangupContainer.x + updateState(State.Sliding(currentState.initialX, translationX, hasReachedActivationThreshold)) + } + true + } + else -> false + } + } + renderInternalState(state) + } + + fun render(roomDetailViewState: RoomDetailViewState) { + val summary = roomDetailViewState.asyncRoomSummary() + val newState = if (summary?.membership != Membership.JOIN || !roomDetailViewState.isAllowedToManageWidgets || roomDetailViewState.jitsiState.widgetId == null) { + State.Unmount + } else if (roomDetailViewState.jitsiState.deleteWidgetInProgress) { + State.Progress + } else { + State.Idle + } + // Don't force Idle if we are already sliding + if (state is State.Sliding && newState is State.Idle) { + return + } else { + updateState(newState) + } + } + + private fun updateState(newState: State) { + if (newState == state) { + return + } + renderInternalState(newState) + state = newState + if (state == State.Progress) { + onCompleteSliding?.invoke() + } + } + + private fun renderInternalState(state: State) { + isVisible = state != State.Unmount + when (state) { + State.Progress -> { + isVisible = true + views.updateVisibilities(true) + views.updateHangupColors(true) + } + State.Idle -> { + isVisible = true + views.updateVisibilities(false) + views.removeJitsiSlidingContainer.translationX = 0f + views.updateHangupColors(false) + } + is State.Sliding -> { + isVisible = true + views.updateVisibilities(false) + views.removeJitsiSlidingContainer.translationX = state.translationX + views.updateHangupColors(state.hasReachedActivationThreshold) + } + else -> Unit + } + } + + private fun ViewRemoveJitsiWidgetBinding.updateVisibilities(isProgress: Boolean) { + removeJitsiProgressContainer.isVisible = isProgress + removeJitsiHangupContainer.isVisible = !isProgress + removeJitsiSlidingContainer.isVisible = !isProgress + } + + private fun ViewRemoveJitsiWidgetBinding.updateHangupColors(activated: Boolean) { + val iconTintColor: Int + val bgColor: Int + if (activated) { + bgColor = ContextCompat.getColor(context, R.color.palette_vermilion) + iconTintColor = ContextCompat.getColor(context, R.color.palette_white) + } else { + bgColor = ContextCompat.getColor(context, android.R.color.transparent) + iconTintColor = ContextCompat.getColor(context, R.color.palette_vermilion) + } + removeJitsiHangupContainer.setBackgroundColor(bgColor) + ImageViewCompat.setImageTintList(removeJitsiHangupIcon, ColorStateList.valueOf(iconTintColor)) + } +} 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 3e55b2b924..224c52b30f 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 @@ -67,7 +67,6 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState -import com.facebook.react.bridge.JavaOnlyMap import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jakewharton.rxbinding3.view.focusChanges import com.jakewharton.rxbinding3.widget.textChanges @@ -90,7 +89,6 @@ 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.ActiveConferenceView import im.vector.app.core.ui.views.CurrentCallsCardView import im.vector.app.core.ui.views.FailedMessagesWarningView import im.vector.app.core.ui.views.NotificationAreaView @@ -124,7 +122,6 @@ import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.conference.JitsiBroadcastEmitter import im.vector.app.features.call.conference.JitsiBroadcastEventObserver import im.vector.app.features.call.conference.JitsiCallViewModel -import im.vector.app.features.call.conference.extractConferenceUrl import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.command.Command import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity @@ -178,7 +175,6 @@ import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.commonmark.parser.Parser -import org.jitsi.meet.sdk.BroadcastEmitter import org.jitsi.meet.sdk.BroadcastEvent import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData @@ -331,9 +327,10 @@ class RoomDetailFragment @Inject constructor( setupJumpToReadMarkerView() setupActiveCallView() setupJumpToBottomView() - setupConfBannerView() setupEmojiPopup() setupFailedMessagesWarningView() + setupRemoveJitsiWidgetView() + views.roomToolbarContentView.debouncedClicks { navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) @@ -397,7 +394,7 @@ class RoomDetailFragment @Inject constructor( RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked() is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) - RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference() + RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference() RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) @@ -420,6 +417,18 @@ class RoomDetailFragment @Inject constructor( } } + private fun setupRemoveJitsiWidgetView() { + views.removeJitsiWidgetView.onCompleteSliding = { + withState(roomDetailViewModel) { + val jitsiWidgetId = it.jitsiState.widgetId ?: return@withState + if (it.jitsiState.hasJoined) { + leaveJitsiConference() + } + roomDetailViewModel.handle(RoomDetailAction.RemoveWidget(jitsiWidgetId)) + } + } + } + private fun leaveJitsiConference() { JitsiBroadcastEmitter(vectorBaseActivity).emitConferenceEnded() } @@ -530,31 +539,6 @@ class RoomDetailFragment @Inject constructor( ) } - private fun setupConfBannerView() { - views.activeConferenceView.callback = object : ActiveConferenceView.Callback { - override fun onTapJoinAudio(jitsiWidget: Widget) { - // need to check if allowed first - roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed( - widget = jitsiWidget, - userJustAccepted = false, - grantedEvents = RoomDetailViewEvents.JoinJitsiConference(jitsiWidget, false)) - ) - } - - override fun onTapJoinVideo(jitsiWidget: Widget) { - roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed( - widget = jitsiWidget, - userJustAccepted = false, - grantedEvents = RoomDetailViewEvents.JoinJitsiConference(jitsiWidget, true)) - ) - } - - override fun onDelete(jitsiWidget: Widget) { - roomDetailViewModel.handle(RoomDetailAction.RemoveWidget(jitsiWidget.widgetId)) - } - } - } - private fun setupEmojiPopup() { emojiPopup = EmojiPopup .Builder @@ -1261,7 +1245,7 @@ class RoomDetailFragment @Inject constructor( invalidateOptionsMenu() val summary = state.asyncRoomSummary() renderToolbar(summary, state.typingMessage) - views.activeConferenceView.render(state) + views.removeJitsiWidgetView.render(state) views.failedMessagesWarningView.render(state.hasFailedSending) val inviter = state.asyncInviter() if (summary?.membership == Membership.JOIN) { 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 106c753068..29b50eb77e 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 @@ -60,12 +60,12 @@ import io.reactivex.Observable import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.jitsi.meet.sdk.BroadcastEvent -import org.jitsi.meet.sdk.JitsiMeet import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.orFalse @@ -241,18 +241,25 @@ class RoomDetailViewModel @AssistedInject constructor( widgets.filter { it.isActive } } .execute { widgets -> - val jitsiWidget = widgets()?.firstOrNull { it.type == WidgetType.Jitsi } - val jitsiConfId = jitsiWidget?.let { - jitsiService.extractProperties(it)?.confId - } copy( activeRoomWidgets = widgets, - jitsiState = jitsiState.copy( - confId = jitsiConfId, - widgetId = jitsiWidget?.widgetId - ) ) } + + asyncSubscribe(RoomDetailViewState::activeRoomWidgets) { widgets -> + setState { + val jitsiWidget = widgets.firstOrNull { it.type == WidgetType.Jitsi } + val jitsiConfId = jitsiWidget?.let { + jitsiService.extractProperties(it)?.confId + } + copy( + jitsiState = jitsiState.copy( + confId = jitsiConfId, + widgetId = jitsiWidget?.widgetId + ) + ) + } + } } private fun observeMyRoomMember() { @@ -318,8 +325,8 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) is RoomDetailAction.UpdateJoinJitsiCallStatus -> handleJitsiCallJoinStatus(action) - is RoomDetailAction.JoinJitsiCall -> handleJoinJitsiCall() - is RoomDetailAction.LeaveJitsiCall -> handleLeaveJitsiCall() + is RoomDetailAction.JoinJitsiCall -> handleJoinJitsiCall() + is RoomDetailAction.LeaveJitsiCall -> handleLeaveJitsiCall() is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) is RoomDetailAction.CancelSend -> handleCancel(action) @@ -363,8 +370,8 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.LeaveJitsiConference) } - private fun handleJoinJitsiCall() = withState{ state -> - val jitsiWidget = state.activeRoomWidgets()?.firstOrNull { it.widgetId == state.jitsiState.widgetId} ?: return@withState + private fun handleJoinJitsiCall() = withState { state -> + val jitsiWidget = state.activeRoomWidgets()?.firstOrNull { it.widgetId == state.jitsiState.widgetId } ?: return@withState val action = RoomDetailAction.EnsureNativeWidgetAllowed(jitsiWidget, false, RoomDetailViewEvents.JoinJitsiConference(jitsiWidget, true)) handleCheckWidgetAllowed(action) } @@ -477,10 +484,15 @@ class RoomDetailViewModel @AssistedInject constructor( } } - private fun handleDeleteWidget(widgetId: String) { - _viewEvents.post(RoomDetailViewEvents.ShowWaitingView) + private fun handleDeleteWidget(widgetId: String) = withState { state -> + val isJitsiWidget = state.jitsiState.widgetId == widgetId viewModelScope.launch(Dispatchers.IO) { try { + if (isJitsiWidget) { + setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) } + } else { + _viewEvents.post(RoomDetailViewEvents.ShowWaitingView) + } session.widgetService().destroyRoomWidget(room.roomId, widgetId) // local echo setState { @@ -496,7 +508,11 @@ class RoomDetailViewModel @AssistedInject constructor( } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_remove_widget))) } finally { - _viewEvents.post(RoomDetailViewEvents.HideWaitingView) + if (isJitsiWidget) { + setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = false)) } + } else { + _viewEvents.post(RoomDetailViewEvents.HideWaitingView) + } } } } 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 75650ed322..f368036b9e 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 @@ -59,7 +59,8 @@ data class JitsiState( val hasJoined: Boolean = false, // Not null if we have an active jitsi widget on the room val confId: String? = null, - val widgetId: String? = null + val widgetId: String? = null, + val deleteWidgetInProgress: Boolean = false ) data class RoomDetailViewState( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt index 84867e15c6..e856907717 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt @@ -38,13 +38,8 @@ class WidgetItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val messageColorProvider: MessageColorProvider, private val avatarRenderer: AvatarRenderer, - private val activeSessionDataSource: ActiveSessionDataSource, private val roomSummariesHolder: RoomSummariesHolder ) { - private val currentUserId: String? - get() = activeSessionDataSource.currentValue?.orNull()?.myUserId - - private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { val event = params.event diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 65da9156c9..3b96146c4d 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -100,13 +100,14 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appBarLayout" /> - <im.vector.app.core.ui.views.ActiveConferenceView - android:id="@+id/activeConferenceView" + <im.vector.app.features.call.conference.RemoveJitsiWidgetView + android:id="@+id/removeJitsiWidgetView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="gone" - app:layout_constraintTop_toBottomOf="@id/syncStateView" - tools:visibility="visible" /> + android:visibility="visible" + android:background="?android:colorBackground" + android:minHeight="54dp" + app:layout_constraintTop_toBottomOf="@id/syncStateView"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/timelineRecyclerView" @@ -116,7 +117,7 @@ app:layout_constraintBottom_toTopOf="@+id/timelineRecyclerViewBarrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/activeConferenceView" + app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" tools:listitem="@layout/item_timeline_event_base" /> <com.google.android.material.chip.Chip @@ -132,7 +133,7 @@ android:visibility="invisible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/activeConferenceView" + app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/view_active_conference_view.xml b/vector/src/main/res/layout/view_active_conference_view.xml deleted file mode 100644 index 9f26ed9a1a..0000000000 --- a/vector/src/main/res/layout/view_active_conference_view.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<merge xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?colorPrimary" - tools:parentTag="android.widget.RelativeLayout"> - - <TextView - android:id="@+id/activeConferenceInfo" - style="@style/Widget.Vector.TextView.Body" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_toStartOf="@id/deleteWidgetButton" - android:drawablePadding="10dp" - android:gravity="center_vertical" - android:paddingStart="16dp" - android:paddingTop="12dp" - android:paddingEnd="16dp" - android:paddingBottom="12dp" - android:textColor="?colorOnPrimary" - android:textColorLink="?colorOnPrimary" - app:drawableStartCompat="@drawable/ic_call_answer" - app:drawableTint="?colorOnPrimary" - tools:text="@string/ongoing_conference_call" /> - - <Button - android:id="@+id/deleteWidgetButton" - style="@style/Widget.Vector.Button.Text.OnPrimary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@+id/activeConferenceInfo" - android:layout_alignBottom="@+id/activeConferenceInfo" - android:layout_alignParentEnd="true" - android:clickable="false" - android:focusable="false" - android:paddingStart="8dp" - android:paddingEnd="16dp" - android:text="@string/action_close" - android:textStyle="bold" /> - -</merge> diff --git a/vector/src/main/res/layout/view_remove_jitsi_widget.xml b/vector/src/main/res/layout/view_remove_jitsi_widget.xml new file mode 100644 index 0000000000..0da5493ce9 --- /dev/null +++ b/vector/src/main/res/layout/view_remove_jitsi_widget.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="54dp" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + <LinearLayout android:id="@+id/removeJitsiProgressContainer" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + android:orientation="horizontal" + android:visibility="gone" + android:gravity="center_vertical" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <ProgressBar + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:indeterminateTintMode="src_atop" + android:indeterminateTint="?vctr_content_primary" + android:layout_width="16dp" + android:layout_height="16dp" /> + + <TextView + android:text="@string/call_remove_jitsi_widget_progress" + style="@style/Widget.Vector.TextView.Body" + android:textColor="?vctr_content_primary" + android:layout_marginStart="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + </LinearLayout> + + + <LinearLayout + android:id="@+id/removeJitsiSlidingContainer" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:visibility="visible" + android:gravity="center_vertical" + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + + <TextView + android:id="@+id/removeJitsiSlidingTextView" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/layout_horizontal_margin" + android:text="@string/call_slide_to_end_conference" + android:textColor="?vctr_content_primary" /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:src="@drawable/ic_arrow_right" + android:tint="?vctr_content_quaternary" /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:alpha="0.5" + android:src="@drawable/ic_arrow_right" + android:tint="?vctr_content_quaternary" /> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:alpha="0.2" + android:src="@drawable/ic_arrow_right" + android:tint="?vctr_content_quaternary" /> + + </LinearLayout> + + <FrameLayout + android:id="@+id/removeJitsiHangupContainer" + android:layout_width="88dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:background="@color/vector_warning_color_2"> + + <ImageView + android:id="@+id/removeJitsiHangupIcon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:src="@drawable/ic_call_hangup" /> + + </FrameLayout> + + <View + android:layout_width="0dp" + android:layout_height="1dp" + android:background="?attr/vctr_system" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <View + android:layout_width="0dp" + android:layout_height="1dp" + android:background="?attr/vctr_system" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + +</merge> \ 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 da7ed473ff..632ecf3409 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -742,6 +742,8 @@ <string name="call_error_camera_init_failed">Cannot initialize the camera</string> <string name="call_error_answered_elsewhere">call answered elsewhere</string> + <string name="call_remove_jitsi_widget_progress">Ending call…</string> + <!-- medias picker string --> <string name="media_picker_both_capture_title">Take a picture or a video"</string> <string name="media_picker_cannot_record_video">Cannot record video"</string> @@ -3247,6 +3249,8 @@ <string name="call_transfer_transfer_to_title">Transfer to %1$s</string> <string name="call_transfer_unknown_person">Unknown person</string> + <string name="call_slide_to_end_conference">Slide to end the call for everyone</string> + <string name="re_authentication_activity_title">Re-Authentication Needed</string> <!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name --> <string name="template_re_authentication_default_confirm_text">${app_name} requires you to enter your credentials to perform this action.</string> @@ -3409,4 +3413,5 @@ <string name="teammate_spaces_arent_quite_ready">"Teammate spaces aren’t quite ready but you can still give them a try"</string> <string name="teammate_spaces_might_not_join">"At the moment people might not be able to join any private rooms you make.\n\nWe’ll be improving this as part of the beta, but just wanted to let you know."</string> <string name="error_failed_to_join_room">Sorry, an error occurred while trying to join: %s</string> + </resources>