mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Add/Remove jitsi widget via option menu
This commit is contained in:
parent
42a24300a1
commit
3ce1e3e5d9
@ -42,6 +42,9 @@ import org.matrix.android.sdk.api.util.JsonDict
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* "im.vector.riot.jitsi": {
|
||||
* "preferredDomain": "https://jitsi.riot.im/"
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@ -57,7 +60,10 @@ data class WellKnown(
|
||||
val integrations: JsonDict? = null,
|
||||
|
||||
@Json(name = "im.vector.riot.e2ee")
|
||||
val e2eAdminSetting: E2EWellKnownConfig? = null
|
||||
val e2eAdminSetting: E2EWellKnownConfig? = null,
|
||||
|
||||
@Json(name = "im.vector.riot.jitsi")
|
||||
val jitsiServer: WellKnownPreferredConfig? = null
|
||||
|
||||
)
|
||||
|
||||
@ -66,3 +72,11 @@ data class E2EWellKnownConfig(
|
||||
@Json(name = "default")
|
||||
val e2eDefault: Boolean = true
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
class WellKnownPreferredConfig {
|
||||
|
||||
@JvmField
|
||||
@Json(name = "preferredDomain")
|
||||
var preferredDomain: String? = null
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ data class HomeServerCapabilities(
|
||||
* Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
|
||||
* (as it was before) for various environments where this is desired.
|
||||
*/
|
||||
val adminE2EByDefault: Boolean = true
|
||||
val adminE2EByDefault: Boolean = true,
|
||||
|
||||
val preferredJitsiDomain: String? = null
|
||||
) {
|
||||
companion object {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
@ -31,6 +31,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
if (oldVersion <= 0) migrateTo1(realm)
|
||||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
@ -52,4 +53,11 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun migrateTo3(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java)
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
context: Context) {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 2L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 3L
|
||||
}
|
||||
|
||||
// Keep legacy preferences name for compatibility reason
|
||||
|
@ -31,7 +31,8 @@ internal object HomeServerCapabilitiesMapper {
|
||||
maxUploadFileSize = entity.maxUploadFileSize,
|
||||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
|
||||
adminE2EByDefault = entity.adminE2EByDefault
|
||||
adminE2EByDefault = entity.adminE2EByDefault,
|
||||
preferredJitsiDomain = entity.preferredJitsiDomain
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ internal open class HomeServerCapabilitiesEntity(
|
||||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var adminE2EByDefault: Boolean = true,
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
var lastUpdatedTimestamp: Long = 0L,
|
||||
var preferredJitsiDomain: String? = null
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
@ -110,6 +110,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||
homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true
|
||||
homeServerCapabilitiesEntity.preferredJitsiDomain = getWellknownResult.wellKnown.jitsiServer?.preferredDomain
|
||||
// We are also checking for integration manager configurations
|
||||
val config = configExtractor.extract(getWellknownResult.wellKnown)
|
||||
if (config != null) {
|
||||
|
@ -24,10 +24,14 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.tappableMatchingText
|
||||
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,
|
||||
@ -38,6 +42,7 @@ class ActiveConferenceView @JvmOverloads constructor(
|
||||
interface Callback {
|
||||
fun onTapJoinAudio(jitsiWidget: Widget)
|
||||
fun onTapJoinVideo(jitsiWidget: Widget)
|
||||
fun onDelete(jitsiWidget: Widget)
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
@ -77,5 +82,33 @@ class ActiveConferenceView @JvmOverloads constructor(
|
||||
text = styledText
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
findViewById<TextView>(R.id.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
|
||||
val activeConf = // for now only jitsi?
|
||||
state.activeRoomWidgets()?.firstOrNull {
|
||||
// for now only jitsi?
|
||||
it.type == WidgetType.Jitsi
|
||||
}
|
||||
|
||||
if (activeConf == null) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
jitsiWidget = activeConf
|
||||
}
|
||||
// if sent by me or if i can moderate?
|
||||
findViewById<TextView>(R.id.deleteWidgetButton).isVisible = state.isAllowedToManageWidgets
|
||||
} else {
|
||||
isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.Success
|
||||
@ -82,9 +84,14 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
|
||||
when (viewState.widget) {
|
||||
is Fail -> finish()
|
||||
is Success -> {
|
||||
// val widget = viewState.widget.invoke()
|
||||
findViewById<View>(R.id.jitsi_progress_layout).isVisible = false
|
||||
jitsiMeetView?.isVisible = true
|
||||
configureJitsiView(viewState)
|
||||
}
|
||||
else -> {
|
||||
jitsiMeetView?.isVisible = false
|
||||
findViewById<View>(R.id.jitsi_progress_layout).isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,4 +81,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||
object SelectStickerAttachment : RoomDetailAction()
|
||||
object OpenIntegrationManager: RoomDetailAction()
|
||||
object ManageIntegrations: RoomDetailAction()
|
||||
data class AddJitsiWidget(val video: Boolean): RoomDetailAction()
|
||||
data class RemoveWidget(val widgetId: String): RoomDetailAction()
|
||||
}
|
||||
|
@ -356,6 +356,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager()
|
||||
is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it)
|
||||
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
|
||||
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
|
||||
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
|
||||
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
|
||||
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
@ -372,15 +376,23 @@ class RoomDetailFragment @Inject constructor(
|
||||
private fun setupConfBannerView() {
|
||||
activeConferenceView.callback = object : ActiveConferenceView.Callback {
|
||||
override fun onTapJoinAudio(jitsiWidget: Widget) {
|
||||
navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to false))
|
||||
joinJitsiRoom(jitsiWidget, false)
|
||||
}
|
||||
|
||||
override fun onTapJoinVideo(jitsiWidget: Widget) {
|
||||
navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to true))
|
||||
joinJitsiRoom(jitsiWidget, true)
|
||||
}
|
||||
|
||||
override fun onDelete(jitsiWidget: Widget) {
|
||||
roomDetailViewModel.handle(RoomDetailAction.RemoveWidget(jitsiWidget.widgetId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) {
|
||||
navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo))
|
||||
}
|
||||
|
||||
private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) {
|
||||
navigator.openStickerPicker(this, roomDetailArgs.roomId, event.widget)
|
||||
}
|
||||
@ -598,22 +610,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> {
|
||||
val activeCall = sharedCallActionViewModel.activeCall.value
|
||||
val isVideoCall = item.itemId == R.id.video_call
|
||||
if (activeCall != null) {
|
||||
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||
if (activeCall.roomId == roomDetailArgs.roomId) {
|
||||
onTapToReturnToCall()
|
||||
}
|
||||
// else {
|
||||
// TODO might not work well, and should prompt
|
||||
// webRtcPeerConnectionManager.endCall()
|
||||
// safeStartCall(it, isVideoCall)
|
||||
// }
|
||||
} else {
|
||||
safeStartCall(isVideoCall)
|
||||
}
|
||||
true
|
||||
handleCallRequest(item)
|
||||
}
|
||||
R.id.hangup_call -> {
|
||||
roomDetailViewModel.handle(RoomDetailAction.EndCall)
|
||||
@ -623,6 +620,66 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCallRequest(item: MenuItem): Boolean = withState(roomDetailViewModel) { state ->
|
||||
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState true
|
||||
val isVideoCall = item.itemId == R.id.video_call
|
||||
return@withState when (roomSummary.joinedMembersCount) {
|
||||
1 -> {
|
||||
val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0
|
||||
if (pendingInvite) {
|
||||
// wait for other to join
|
||||
showDialogWithMessage(getString(R.string.cannot_call_yourself_with_invite))
|
||||
} else {
|
||||
// You cannot place a call with yourself.
|
||||
showDialogWithMessage(getString(R.string.cannot_call_yourself))
|
||||
}
|
||||
true
|
||||
}
|
||||
2 -> {
|
||||
val activeCall = sharedCallActionViewModel.activeCall.value
|
||||
if (activeCall != null) {
|
||||
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||
if (activeCall.roomId == roomDetailArgs.roomId) {
|
||||
onTapToReturnToCall()
|
||||
}
|
||||
// else {
|
||||
// TODO might not work well, and should prompt
|
||||
// webRtcPeerConnectionManager.endCall()
|
||||
// safeStartCall(it, isVideoCall)
|
||||
// }
|
||||
} else {
|
||||
safeStartCall(isVideoCall)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
// it's jitsi call
|
||||
// can you add widgets??
|
||||
if (!state.isAllowedToManageWidgets) {
|
||||
// You do not have permission to start a conference call in this room
|
||||
showDialogWithMessage(getString(R.string.no_permissions_to_start_conf_call))
|
||||
} else {
|
||||
if (state.activeRoomWidgets()?.filter { it.type == WidgetType.Jitsi }?.any() == true) {
|
||||
// A conference is already in progress!
|
||||
showDialogWithMessage(getString(R.string.conference_call_in_progress))
|
||||
} else {
|
||||
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(if (isVideoCall) R.string.video_meeting else R.string.audio_meeting)
|
||||
.setMessage(R.string.audio_video_meeting_description)
|
||||
.setPositiveButton(getString(R.string.create)) { _, _ ->
|
||||
// create the widget, then navigate to it..
|
||||
roomDetailViewModel.handle(RoomDetailAction.AddJitsiWidget(isVideoCall))
|
||||
}
|
||||
.setNegativeButton(getString(R.string.cancel), null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun displayDisabledIntegrationDialog() {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.disabled_integration_dialog_title)
|
||||
@ -915,21 +972,9 @@ class RoomDetailFragment @Inject constructor(
|
||||
invalidateOptionsMenu()
|
||||
val summary = state.asyncRoomSummary()
|
||||
renderToolbar(summary, state.typingMessage)
|
||||
activeConferenceView.render(state)
|
||||
val inviter = state.asyncInviter()
|
||||
if (summary?.membership == Membership.JOIN) {
|
||||
// We only display banner for 'live' widgets
|
||||
val activeConf = // for now only jitsi?
|
||||
state.activeRoomWidgets()?.firstOrNull {
|
||||
// for now only jitsi?
|
||||
it.type == WidgetType.Jitsi
|
||||
}
|
||||
|
||||
if (activeConf == null) {
|
||||
activeConferenceView.isVisible = false
|
||||
} else {
|
||||
activeConferenceView.isVisible = true
|
||||
activeConferenceView.jitsiWidget = activeConf
|
||||
}
|
||||
jumpToBottomView.count = summary.notificationCount
|
||||
jumpToBottomView.drawBadge = summary.hasUnreadMessages
|
||||
scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline
|
||||
@ -1167,7 +1212,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
|
||||
override fun onUrlClicked(url: String, title: String): Boolean {
|
||||
permalinkHandler
|
||||
@ -1639,7 +1684,18 @@ class RoomDetailFragment @Inject constructor(
|
||||
Snackbar.make(requireView(), message, duration).show()
|
||||
}
|
||||
|
||||
// VectorInviteView.Callback
|
||||
private fun showDialogWithMessage(message: String) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(getString(R.string.ok), null)
|
||||
.show()
|
||||
}
|
||||
|
||||
// private fun joinCurrentJitsiCall(withVideo: Boolean) {
|
||||
//
|
||||
// }
|
||||
|
||||
// VectorInviteView.Callback
|
||||
|
||||
override fun onAcceptInvite() {
|
||||
notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId)
|
||||
@ -1651,7 +1707,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.RejectInvite)
|
||||
}
|
||||
|
||||
// JumpToReadMarkerView.Callback
|
||||
// JumpToReadMarkerView.Callback
|
||||
|
||||
override fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
|
||||
jumpToReadMarkerView.isVisible = false
|
||||
@ -1667,7 +1723,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
|
||||
}
|
||||
|
||||
// AttachmentTypeSelectorView.Callback
|
||||
// AttachmentTypeSelectorView.Callback
|
||||
|
||||
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
|
||||
if (checkPermissions(type.permissionsBit, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)) {
|
||||
@ -1688,7 +1744,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
// AttachmentsHelper.Callback
|
||||
// AttachmentsHelper.Callback
|
||||
|
||||
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
|
||||
if (roomDetailViewModel.preventAttachmentPreview) {
|
||||
|
@ -35,9 +35,15 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||
data class ActionFailure(val action: RoomDetailAction, val throwable: Throwable) : RoomDetailViewEvents()
|
||||
|
||||
data class ShowMessage(val message: String) : RoomDetailViewEvents()
|
||||
data class ShowInfoOkDialog(val message: String) : RoomDetailViewEvents()
|
||||
data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents()
|
||||
|
||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
|
||||
|
||||
object ShowWaitingView: RoomDetailViewEvents()
|
||||
object HideWaitingView: RoomDetailViewEvents()
|
||||
|
||||
|
||||
data class FileTooBigError(
|
||||
val filename: String,
|
||||
|
@ -43,6 +43,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.app.features.home.room.typing.TypingHelper
|
||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
@ -91,8 +92,12 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@ -188,8 +193,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
PowerLevelsObservableFactory(room).createObservable()
|
||||
.subscribe {
|
||||
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
|
||||
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
||||
setState {
|
||||
copy(canSendMessage = canSendMessage)
|
||||
copy(
|
||||
canSendMessage = canSendMessage,
|
||||
isAllowedToManageWidgets = isAllowedToManageWidgets
|
||||
)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
@ -269,7 +278,9 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@ -317,6 +328,89 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Build data for a jitsi widget
|
||||
|
||||
// Build data for a jitsi widget
|
||||
val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis()
|
||||
|
||||
// Create a random enough jitsi conference id
|
||||
// Note: the jitsi server automatically creates conference when the conference
|
||||
// id does not exist yet
|
||||
|
||||
// Create a random enough jitsi conference id
|
||||
// Note: the jitsi server automatically creates conference when the conference
|
||||
// id does not exist yet
|
||||
var widgetSessionId = UUID.randomUUID().toString()
|
||||
|
||||
if (widgetSessionId.length > 8) {
|
||||
widgetSessionId = widgetSessionId.substring(0, 7)
|
||||
}
|
||||
val roomId: String = room.roomId
|
||||
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
|
||||
|
||||
//DO NOT COMMIT
|
||||
val jitsiDomain = session.getHomeServerCapabilities().preferredJitsiDomain ?: stringProvider.getString(R.string.preferred_jitsi_domain)
|
||||
|
||||
// We use the default element wrapper for this widget
|
||||
// https://github.com/vector-im/element-web/blob/develop/docs/jitsi-dev.md
|
||||
val url = "https://app.element.io/jitsi.html?" +
|
||||
"confId=$confId#conferenceDomain=\$domain&conferenceId=\$conferenceId&isAudioOnly=${action.video}" +
|
||||
"&displayName=\$matrix_display_name&avatarUrl=\$matrix_avatar_url&userId=\$matrix_user_id"
|
||||
|
||||
val widgetEventContent = mapOf(
|
||||
"url" to url,
|
||||
"type" to WidgetType.Jitsi.legacy,
|
||||
"data" to mapOf(
|
||||
"conferenceId" to confId,
|
||||
"domain" to jitsiDomain,
|
||||
"isAudioOnly" to !action.video
|
||||
),
|
||||
"creatorUserId" to session.myUserId,
|
||||
"id" to widgetId,
|
||||
"name" to "jitsi"
|
||||
)
|
||||
|
||||
try {
|
||||
val widget = awaitCallback<Widget> {
|
||||
session.widgetService().createRoomWidget(roomId, widgetId, widgetEventContent, it)
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.video))
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget)))
|
||||
} finally {
|
||||
_viewEvents.post(RoomDetailViewEvents.HideWaitingView)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteWidget(widgetId: String) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
awaitCallback<Unit> { session.widgetService().destroyRoomWidget(room.roomId, widgetId, it) }
|
||||
// local echo
|
||||
setState {
|
||||
copy(
|
||||
activeRoomWidgets = when (activeRoomWidgets) {
|
||||
is Success -> {
|
||||
Success(activeRoomWidgets.invoke().filter { it.widgetId != widgetId })
|
||||
}
|
||||
else -> activeRoomWidgets
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_remove_widget)))
|
||||
} finally {
|
||||
_viewEvents.post(RoomDetailViewEvents.HideWaitingView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTrackingUnreadMessages() {
|
||||
trackUnreadMessages.set(true)
|
||||
setState { copy(canShowJumpToReadMarker = false) }
|
||||
@ -430,7 +524,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.open_matrix_apps -> true
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> state.asyncRoomSummary()?.canStartCall == true && webRtcPeerConnectionManager.currentCall == null
|
||||
R.id.video_call -> true // always show for discoverability
|
||||
R.id.hangup_call -> webRtcPeerConnectionManager.currentCall != null
|
||||
else -> false
|
||||
}
|
||||
|
@ -66,7 +66,8 @@ data class RoomDetailViewState(
|
||||
val unreadState: UnreadState = UnreadState.Unknown,
|
||||
val canShowJumpToReadMarker: Boolean = true,
|
||||
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
|
||||
val canSendMessage: Boolean = true
|
||||
val canSendMessage: Boolean = true,
|
||||
val isAllowedToManageWidgets: Boolean = false
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||
|
@ -15,16 +15,9 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/call_connecting"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||
android:indeterminate="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -12,7 +12,7 @@
|
||||
android:id="@+id/activeConferenceInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/returnToCallButton"
|
||||
android:layout_toStartOf="@id/deleteWidgetButton"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawableStart="@drawable/ic_call"
|
||||
android:drawablePadding="10dp"
|
||||
@ -27,7 +27,7 @@
|
||||
tools:text="@string/ongoing_conference_call" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/returnToCallButton"
|
||||
android:id="@+id/deleteWidgetButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/activeConferenceInfo"
|
||||
|
@ -18,6 +18,9 @@
|
||||
<!-- Note: pusher_app_id cannot exceed 64 chars -->
|
||||
<string name="pusher_app_id" translatable="false">im.vector.app.android</string>
|
||||
|
||||
<!-- preferred jitsi domain -->
|
||||
<string name="preferred_jitsi_domain" translatable="false">jitsi.riot.im</string>
|
||||
|
||||
<string-array name="room_directory_servers" translatable="false">
|
||||
<item>matrix.org</item>
|
||||
</string-array>
|
||||
|
@ -89,8 +89,17 @@
|
||||
<string name="missing_permissions_warning">"Due to missing permissions, some features may be missing…</string>
|
||||
<string name="missing_permissions_error">"Due to missing permissions, this action is not possible.</string>
|
||||
<string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string>
|
||||
<string name="no_permissions_to_start_conf_call">You do not have permission to start a conference call in this room</string>
|
||||
<string name="conference_call_in_progress">A conference is already in progress!</string>
|
||||
<string name="video_meeting">Start video meeting</string>
|
||||
<string name="audio_meeting">Start audio meeting</string>
|
||||
<string name="audio_video_meeting_description">Meetings use Jisti security and permission policies. All people currently in the room will see an invite to join while your meeting is happening.</string>
|
||||
<string name="missing_permissions_title_to_start_conf_call">Cannot start call</string>
|
||||
<string name="cannot_call_yourself">You cannot place a call with yourself</string>
|
||||
<string name="cannot_call_yourself_with_invite">You cannot place a call with yourself, wait for participants to accept invitation</string>
|
||||
<string name="device_information">Session information</string>
|
||||
<string name="failed_to_add_widget">Failed to add widget</string>
|
||||
<string name="failed_to_remove_widget">Failed to remove widget</string>
|
||||
<string name="room_no_conference_call_in_encrypted_rooms">Conference calls are not supported in encrypted rooms</string>
|
||||
<string name="call_anyway">Call Anyway</string>
|
||||
<string name="send_anyway">Send Anyway</string>
|
||||
|
Loading…
Reference in New Issue
Block a user