Benoit's review

- Cleanup
- Force refresh of HomeServerCapabilities
- add some doc
- remove dead code
- remove commented code
- remove duplicated comment
- use getBestName()
- improve code formatting
- Fix isAudioOnly parameter in jitsi url
- Fix layout issue between "Active conference" banner and "Jump to first unread message banner"
- Improve "Active conference" banner
- Remove Calendar permission from Manifest
This commit is contained in:
Benoit Marty 2020-08-14 15:40:02 +02:00
parent 157f22ac2d
commit 4f8fd7b994
25 changed files with 174 additions and 133 deletions

View File

@ -3,7 +3,7 @@ Changes in Element 1.0.5 (2020-XX-XX)
Features ✨: Features ✨:
- Protect access to the app by a pin code (#1700) - Protect access to the app by a pin code (#1700)
- Conference with Jitsi support (#43) - Conference with Jitsi support (#43)
Improvements 🙌: Improvements 🙌:
- Give user the possibility to prevent accidental call (#1869) - Give user the possibility to prevent accidental call (#1869)

View File

@ -137,7 +137,7 @@ dependencies {
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1")) implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1"))
implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor' implementation 'com.squareup.okhttp3:logging-interceptor'
implementation("com.squareup.okhttp3:okhttp-urlconnection") implementation 'com.squareup.okhttp3:okhttp-urlconnection'
implementation "com.squareup.moshi:moshi-adapters:$moshi_version" implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"

View File

@ -74,9 +74,7 @@ data class E2EWellKnownConfig(
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class WellKnownPreferredConfig { data class WellKnownPreferredConfig(
@Json(name = "preferredDomain")
@JvmField val preferredDomain: String? = null
@Json(name = "preferredDomain") )
var preferredDomain: String? = null
}

View File

@ -39,7 +39,9 @@ data class HomeServerCapabilities(
* (as it was before) for various environments where this is desired. * (as it was before) for various environments where this is desired.
*/ */
val adminE2EByDefault: Boolean = true, val adminE2EByDefault: Boolean = true,
/**
* Preferred Jitsi domain, provided in Wellknown
*/
val preferredJitsiDomain: String? = null val preferredJitsiDomain: String? = null
) { ) {
companion object { companion object {

View File

@ -17,10 +17,10 @@
package org.matrix.android.sdk.internal.database package org.matrix.android.sdk.internal.database
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -55,8 +55,12 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
} }
private fun migrateTo3(realm: DynamicRealm) { private fun migrateTo3(realm: DynamicRealm) {
Timber.d("Step 1 -> 2") Timber.d("Step 2 -> 3")
realm.schema.get("HomeServerCapabilitiesEntity") realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java) ?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java)
?.transform { obj ->
// Schedule a refresh of the capabilities
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
}
} }
} }

View File

@ -28,6 +28,14 @@
<!-- Needed for incoming calls --> <!-- Needed for incoming calls -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- Jitsi libs adds CALENDAR permissions, but we can remove them safely according to https://github.com/jitsi/jitsi-meet/issues/4068#issuecomment-480482481 -->
<uses-permission
android:name="android.permission.READ_CALENDAR"
tools:node="remove" />
<uses-permission
android:name="android.permission.WRITE_CALENDAR"
tools:node="remove" />
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore --> <!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
<!-- Tell that the Camera is not mandatory to install the application --> <!-- Tell that the Camera is not mandatory to install the application -->
<uses-feature <uses-feature

View File

@ -36,7 +36,7 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
var text: String? = null var text: String? = null
@EpoxyAttribute @EpoxyAttribute
var itemClickAction: View.OnClickListener? = null var buttonClickAction: View.OnClickListener? = null
@EpoxyAttribute @EpoxyAttribute
@ColorInt @ColorInt
@ -57,7 +57,7 @@ abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>()
holder.button.icon = null holder.button.icon = null
} }
itemClickAction?.let { holder.button.setOnClickListener(it) } buttonClickAction?.let { holder.button.setOnClickListener(it) }
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View File

@ -92,7 +92,7 @@ class ActiveConferenceView @JvmOverloads constructor(
val summary = state.asyncRoomSummary() val summary = state.asyncRoomSummary()
if (summary?.membership == Membership.JOIN) { if (summary?.membership == Membership.JOIN) {
// We only display banner for 'live' widgets // We only display banner for 'live' widgets
val activeConf = // for now only jitsi? val activeConf =
state.activeRoomWidgets()?.firstOrNull { state.activeRoomWidgets()?.firstOrNull {
// for now only jitsi? // for now only jitsi?
it.type == WidgetType.Jitsi it.type == WidgetType.Jitsi

View File

@ -0,0 +1,21 @@
/*
* 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.features.call.conference
import im.vector.app.core.platform.VectorViewModelAction
sealed class JitsiCallViewActions : VectorViewModelAction

View File

@ -0,0 +1,21 @@
/*
* 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.features.call.conference
import im.vector.app.core.platform.VectorViewEvents
sealed class JitsiCallViewEvents : VectorViewEvents

View File

@ -23,11 +23,8 @@ import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.WebRtcPeerConnectionManager
import org.jitsi.meet.sdk.JitsiMeetUserInfo import org.jitsi.meet.sdk.JitsiMeetUserInfo
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -36,16 +33,11 @@ import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.asObservable
import java.net.URL import java.net.URL
sealed class JitsiCallViewActions : VectorViewModelAction
sealed class JitsiCallViewEvents : VectorViewEvents
class JitsiCallViewModel @AssistedInject constructor( class JitsiCallViewModel @AssistedInject constructor(
@Assisted initialState: JitsiCallViewState, @Assisted initialState: JitsiCallViewState,
@Assisted val args: VectorJitsiActivity.Args, @Assisted val args: VectorJitsiActivity.Args,
val session: Session, private val session: Session,
val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val stringProvider: StringProvider
val stringProvider: StringProvider
) : VectorViewModel<JitsiCallViewState, JitsiCallViewActions, JitsiCallViewEvents>(initialState) { ) : VectorViewModel<JitsiCallViewState, JitsiCallViewActions, JitsiCallViewEvents>(initialState) {
@AssistedInject.Factory @AssistedInject.Factory
@ -56,7 +48,7 @@ class JitsiCallViewModel @AssistedInject constructor(
init { init {
val me = session.getUser(session.myUserId)?.toMatrixItem() val me = session.getUser(session.myUserId)?.toMatrixItem()
val userInfo = JitsiMeetUserInfo().apply { val userInfo = JitsiMeetUserInfo().apply {
displayName = me?.displayName displayName = me?.getBestName()
avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) } avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) }
} }
val roomName = session.getRoomSummary(args.roomId)?.displayName val roomName = session.getRoomSummary(args.roomId)?.displayName
@ -73,7 +65,7 @@ class JitsiCallViewModel @AssistedInject constructor(
if (jitsiWidget != null) { if (jitsiWidget != null) {
val uri = Uri.parse(jitsiWidget.computedUrl) val uri = Uri.parse(jitsiWidget.computedUrl)
val confId = uri.getQueryParameter("confId") val confId = uri.getQueryParameter("confId")
val ppt = jitsiWidget.computedUrl?.let { JitsiWidgetProperties(it, stringProvider) } val ppt = jitsiWidget.computedUrl?.let { url -> JitsiWidgetProperties(url, stringProvider) }
setState { setState {
copy( copy(
widget = Success(jitsiWidget), widget = Success(jitsiWidget),
@ -89,7 +81,8 @@ class JitsiCallViewModel @AssistedInject constructor(
) )
} }
} }
}.disposeOnClear() }
.disposeOnClear()
} }
override fun handle(action: JitsiCallViewActions) { override fun handle(action: JitsiCallViewActions) {
@ -108,7 +101,6 @@ class JitsiCallViewModel @AssistedInject constructor(
override fun initialState(viewModelContext: ViewModelContext): JitsiCallViewState? { override fun initialState(viewModelContext: ViewModelContext): JitsiCallViewState? {
val args: VectorJitsiActivity.Args = viewModelContext.args() val args: VectorJitsiActivity.Args = viewModelContext.args()
// val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession()
return JitsiCallViewState( return JitsiCallViewState(
roomId = args.roomId, roomId = args.roomId,

View File

@ -55,7 +55,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
@Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory @Inject lateinit var viewModelFactory: JitsiCallViewModel.Factory
var jitsiMeetView: JitsiMeetView? = null private var jitsiMeetView: JitsiMeetView? = null
private val jitsiViewModel: JitsiCallViewModel by viewModel() private val jitsiViewModel: JitsiCallViewModel by viewModel()

View File

@ -82,7 +82,8 @@ sealed class RoomDetailAction : VectorViewModelAction {
object SelectStickerAttachment : RoomDetailAction() object SelectStickerAttachment : RoomDetailAction()
object OpenIntegrationManager: RoomDetailAction() object OpenIntegrationManager: RoomDetailAction()
object ManageIntegrations: RoomDetailAction() object ManageIntegrations: RoomDetailAction()
data class AddJitsiWidget(val video: Boolean): RoomDetailAction() data class AddJitsiWidget(val withVideo: Boolean): RoomDetailAction()
data class RemoveWidget(val widgetId: String): RoomDetailAction() data class RemoveWidget(val widgetId: String): RoomDetailAction()
data class EnsureNativeWidgetAllowed(val widget: Widget, val grantedEvents: RoomDetailViewEvents) : RoomDetailAction() data class EnsureNativeWidgetAllowed(val widget: Widget,
val grantedEvents: RoomDetailViewEvents) : RoomDetailAction()
} }

View File

@ -154,6 +154,14 @@ import im.vector.app.features.widgets.WidgetActivity
import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetArgs
import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.WidgetKind
import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.permalinks.PermalinkFactory import org.matrix.android.sdk.api.permalinks.PermalinkFactory
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -176,21 +184,13 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
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.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.session.widgets.model.Widget
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.net.URL import java.net.URL
@ -370,24 +370,24 @@ class RoomDetailFragment @Inject constructor(
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) { private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
val tag = RoomWidgetPermissionBottomSheet::class.java.name val tag = RoomWidgetPermissionBottomSheet::class.java.name
val dFrag = childFragmentManager val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) { if (dFrag != null && dFrag.dialog?.isShowing == true && !dFrag.isRemoving) {
return return
} else { } else {
RoomWidgetPermissionBottomSheet RoomWidgetPermissionBottomSheet.newInstance(
.newInstance(WidgetArgs( WidgetArgs(
baseUrl = it.domain, baseUrl = it.domain,
kind = WidgetKind.ROOM, kind = WidgetKind.ROOM,
roomId = roomDetailArgs.roomId, roomId = roomDetailArgs.roomId,
widgetId = it.widget.widgetId widgetId = it.widget.widgetId
)).apply { )
directListener = { granted -> ).apply {
if (granted) { directListener = { granted ->
roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed(it.widget, it.grantedEvents)) if (granted) {
} roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed(it.widget, it.grantedEvents))
}
} }
}
}
.show(childFragmentManager, tag) .show(childFragmentManager, tag)
} }
} }
@ -592,7 +592,8 @@ class RoomDetailFragment @Inject constructor(
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
menu.findItem(R.id.open_matrix_apps).let { menuItem -> // We use a custom layout for this menu item, so we need to set a ClickListener
menu.findItem(R.id.open_matrix_apps)?.let { menuItem ->
menuItem.actionView.setOnClickListener { menuItem.actionView.setOnClickListener {
onOptionsItemSelected(menuItem) onOptionsItemSelected(menuItem)
} }
@ -604,24 +605,23 @@ class RoomDetailFragment @Inject constructor(
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId) it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
} }
withState(roomDetailViewModel) { state -> withState(roomDetailViewModel) { state ->
val findItem = menu.findItem(R.id.open_matrix_apps) val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
val widgetsCount = state.activeRoomWidgets.invoke()?.size val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
if (widgetsCount ?: 0 > 0) { if (widgetsCount > 0) {
val actionView = findItem.actionView val actionView = matrixAppsMenuItem.actionView
actionView actionView
.findViewById<ImageView>(R.id.action_view_icon_image) .findViewById<ImageView>(R.id.action_view_icon_image)
.setColorFilter(ContextCompat.getColor(requireContext(), R.color.riotx_accent)) .setColorFilter(ContextCompat.getColor(requireContext(), R.color.riotx_accent))
actionView.findViewById<TextView>(R.id.cart_badge).isVisible = true actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount")
actionView.findViewById<TextView>(R.id.cart_badge).text = "$widgetsCount" matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
findItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
} else { } else {
// icon should be default color no badge // icon should be default color no badge
val actionView = findItem.actionView val actionView = matrixAppsMenuItem.actionView
actionView actionView
.findViewById<ImageView>(R.id.action_view_icon_image) .findViewById<ImageView>(R.id.action_view_icon_image)
.setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.riotx_text_secondary)) .setColorFilter(ThemeUtils.getColor(requireContext(), R.attr.riotx_text_secondary))
actionView.findViewById<TextView>(R.id.cart_badge).isVisible = false actionView.findViewById<TextView>(R.id.cart_badge).isVisible = false
findItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
} }
} }
} }
@ -646,6 +646,7 @@ class RoomDetailFragment @Inject constructor(
R.id.voice_call, R.id.voice_call,
R.id.video_call -> { R.id.video_call -> {
handleCallRequest(item) handleCallRequest(item)
true
} }
R.id.hangup_call -> { R.id.hangup_call -> {
roomDetailViewModel.handle(RoomDetailAction.EndCall) roomDetailViewModel.handle(RoomDetailAction.EndCall)
@ -655,10 +656,10 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private fun handleCallRequest(item: MenuItem): Boolean = withState(roomDetailViewModel) { state -> private fun handleCallRequest(item: MenuItem) = withState(roomDetailViewModel) { state ->
val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState true val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
val isVideoCall = item.itemId == R.id.video_call val isVideoCall = item.itemId == R.id.video_call
return@withState when (roomSummary.joinedMembersCount) { when (roomSummary.joinedMembersCount) {
1 -> { 1 -> {
val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0 val pendingInvite = roomSummary.invitedMembersCount ?: 0 > 0
if (pendingInvite) { if (pendingInvite) {
@ -668,7 +669,6 @@ class RoomDetailFragment @Inject constructor(
// You cannot place a call with yourself. // You cannot place a call with yourself.
showDialogWithMessage(getString(R.string.cannot_call_yourself)) showDialogWithMessage(getString(R.string.cannot_call_yourself))
} }
true
} }
2 -> { 2 -> {
val activeCall = sharedCallActionViewModel.activeCall.value val activeCall = sharedCallActionViewModel.activeCall.value
@ -685,7 +685,6 @@ class RoomDetailFragment @Inject constructor(
} else { } else {
safeStartCall(isVideoCall) safeStartCall(isVideoCall)
} }
true
} }
else -> { else -> {
// it's jitsi call // it's jitsi call
@ -709,7 +708,6 @@ class RoomDetailFragment @Inject constructor(
.show() .show()
} }
} }
true
} }
} }
} }
@ -1725,10 +1723,6 @@ class RoomDetailFragment @Inject constructor(
.show() .show()
} }
// private fun joinCurrentJitsiCall(withVideo: Boolean) {
//
// }
// VectorInviteView.Callback // VectorInviteView.Callback
override fun onAcceptInvite() { override fun onAcceptInvite() {

View File

@ -72,7 +72,9 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
object OpenIntegrationManager: RoomDetailViewEvents() object OpenIntegrationManager: RoomDetailViewEvents()
object OpenActiveWidgetBottomSheet: RoomDetailViewEvents() object OpenActiveWidgetBottomSheet: RoomDetailViewEvents()
data class RequestNativeWidgetPermission(val widget: Widget, val domain: String, val grantedEvents: RoomDetailViewEvents) : RoomDetailViewEvents() data class RequestNativeWidgetPermission(val widget: Widget,
val domain: String,
val grantedEvents: RoomDetailViewEvents) : RoomDetailViewEvents()
object MessageSent : SendMessageResult() object MessageSent : SendMessageResult()
data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult()

View File

@ -332,15 +332,9 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) { private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
_viewEvents.post(RoomDetailViewEvents.ShowWaitingView) _viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
// Build data for a jitsi widget
// Build data for a jitsi widget // Build data for a jitsi widget
val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis() 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 // Create a random enough jitsi conference id
// Note: the jitsi server automatically creates conference when the conference // Note: the jitsi server automatically creates conference when the conference
// id does not exist yet // id does not exist yet
@ -356,9 +350,14 @@ class RoomDetailViewModel @AssistedInject constructor(
// We use the default element wrapper for this widget // We use the default element wrapper for this widget
// https://github.com/vector-im/element-web/blob/develop/docs/jitsi-dev.md // https://github.com/vector-im/element-web/blob/develop/docs/jitsi-dev.md
val url = "https://app.element.io/jitsi.html?" + val url = "https://app.element.io/jitsi.html" +
"confId=$confId#conferenceDomain=\$domain&conferenceId=\$conferenceId&isAudioOnly=${action.video}" + "?confId=$confId" +
"&displayName=\$matrix_display_name&avatarUrl=\$matrix_avatar_url&userId=\$matrix_user_id" "#conferenceDomain=\$domain" +
"&conferenceId=\$conferenceId" +
"&isAudioOnly=${!action.withVideo}" +
"&displayName=\$matrix_display_name" +
"&avatarUrl=\$matrix_avatar_url" +
"&userId=\$matrix_user_id"
val widgetEventContent = mapOf( val widgetEventContent = mapOf(
"url" to url, "url" to url,
@ -366,7 +365,7 @@ class RoomDetailViewModel @AssistedInject constructor(
"data" to mapOf( "data" to mapOf(
"conferenceId" to confId, "conferenceId" to confId,
"domain" to jitsiDomain, "domain" to jitsiDomain,
"isAudioOnly" to !action.video "isAudioOnly" to !action.withVideo
), ),
"creatorUserId" to session.myUserId, "creatorUserId" to session.myUserId,
"id" to widgetId, "id" to widgetId,
@ -377,7 +376,7 @@ class RoomDetailViewModel @AssistedInject constructor(
val widget = awaitCallback<Widget> { val widget = awaitCallback<Widget> {
session.widgetService().createRoomWidget(roomId, widgetId, widgetEventContent, it) session.widgetService().createRoomWidget(roomId, widgetId, widgetEventContent, it)
} }
_viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.video)) _viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.withVideo))
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget))) _viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget)))
} finally { } finally {

View File

@ -38,9 +38,9 @@ import javax.inject.Inject
/** /**
* Bottom sheet displaying active widgets in a room * Bottom sheet displaying active widgets in a room
*/ */
class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidgetController.Listener { class RoomWidgetsBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomWidgetsController.Listener {
@Inject lateinit var epoxyController: RoomWidgetController @Inject lateinit var epoxyController: RoomWidgetsController
@Inject lateinit var colorProvider: ColorProvider @Inject lateinit var colorProvider: ColorProvider
@Inject lateinit var navigator: Navigator @Inject lateinit var navigator: Navigator

View File

@ -29,7 +29,10 @@ import javax.inject.Inject
/** /**
* Epoxy controller for room widgets list * Epoxy controller for room widgets list
*/ */
class RoomWidgetController @Inject constructor(val stringProvider: StringProvider, val colorProvider: ColorProvider) : TypedEpoxyController<List<Widget>>() { class RoomWidgetsController @Inject constructor(
val stringProvider: StringProvider,
val colorProvider: ColorProvider)
: TypedEpoxyController<List<Widget>>() {
var listener: Listener? = null var listener: Listener? = null
@ -41,18 +44,18 @@ class RoomWidgetController @Inject constructor(val stringProvider: StringProvide
} }
} else { } else {
widgets.forEach { widgets.forEach {
RoomWidgetItem_() roomWidgetItem {
.id(it.widgetId) id(it.widgetId)
.widget(it) widget(it)
.widgetClicked { listener?.didSelectWidget(it) } widgetClicked { listener?.didSelectWidget(it) }
.addTo(this) }
} }
} }
genericButtonItem { genericButtonItem {
id("addIntegration") id("addIntegration")
text(stringProvider.getString(R.string.room_manage_integrations)) text(stringProvider.getString(R.string.room_manage_integrations))
textColor(colorProvider.getColor(R.color.riotx_accent)) textColor(colorProvider.getColor(R.color.riotx_accent))
itemClickAction(View.OnClickListener { listener?.didSelectManageWidgets() }) buttonClickAction(View.OnClickListener { listener?.didSelectManageWidgets() })
} }
} }

View File

@ -111,7 +111,7 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
// id("complete_security") // id("complete_security")
// iconRes(R.drawable.ic_shield_warning) // iconRes(R.drawable.ic_shield_warning)
// text(stringProvider.getString(R.string.complete_security)) // text(stringProvider.getString(R.string.complete_security))
// itemClickAction(DebouncedClickListener(View.OnClickListener { _ -> // buttonClickAction(DebouncedClickListener(View.OnClickListener { _ ->
// callback?.completeSecurity() // callback?.completeSecurity()
// })) // }))
// } // }

View File

@ -48,7 +48,7 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
injector.inject(this) injector.inject(this)
} }
// Use this if you don't need to full activity view model // Use this if you don't need the full activity view model
var directListener: ((Boolean) -> Unit)? = null var directListener: ((Boolean) -> Unit)? = null
override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
@ -91,16 +91,16 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
@OnClick(R.id.widgetPermissionDecline) @OnClick(R.id.widgetPermissionDecline)
fun doDecline() { fun doDecline() {
viewModel.handle(RoomWidgetPermissionActions.BlockWidget) viewModel.handle(RoomWidgetPermissionActions.BlockWidget)
// optimistic dismiss
directListener?.invoke(false) directListener?.invoke(false)
// optimistic dismiss
dismiss() dismiss()
} }
@OnClick(R.id.widgetPermissionContinue) @OnClick(R.id.widgetPermissionContinue)
fun doAccept() { fun doAccept() {
viewModel.handle(RoomWidgetPermissionActions.AllowWidget) viewModel.handle(RoomWidgetPermissionActions.AllowWidget)
// optimistic dismiss
directListener?.invoke(true) directListener?.invoke(true)
// optimistic dismiss
dismiss() dismiss()
} }

View File

@ -129,17 +129,17 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/activeCallView"> app:layout_constraintTop_toBottomOf="@id/activeConferenceView">
<!-- <im.vector.app.features.home.room.detail.widget.RoomWidgetsBannerView--> <!-- <im.vector.app.features.home.room.detail.widget.RoomWidgetsBannerView-->
<!-- android:id="@+id/roomWidgetsBannerView"--> <!-- android:id="@+id/roomWidgetsBannerView"-->
<!-- android:layout_width="match_parent"--> <!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"--> <!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="8dp"--> <!-- android:layout_marginStart="8dp"-->
<!-- android:layout_marginTop="8dp"--> <!-- android:layout_marginTop="8dp"-->
<!-- android:layout_marginEnd="8dp"--> <!-- android:layout_marginEnd="8dp"-->
<!-- android:visibility="gone"--> <!-- android:visibility="gone"-->
<!-- tools:visibility="visible" />--> <!-- tools:visibility="visible" />-->
<im.vector.app.core.ui.views.JumpToReadMarkerView <im.vector.app.core.ui.views.JumpToReadMarkerView
android:id="@+id/jumpToReadMarkerView" android:id="@+id/jumpToReadMarkerView"
@ -198,7 +198,7 @@
android:focusable="true" android:focusable="true"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/activeCallView"> app:layout_constraintTop_toBottomOf="@id/activeConferenceView">
<org.webrtc.SurfaceViewRenderer <org.webrtc.SurfaceViewRenderer
android:id="@+id/activeCallPiP" android:id="@+id/activeCallPiP"

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" android:gravity="center_vertical"
android:minHeight="64dp" android:minHeight="64dp"
@ -14,36 +13,35 @@
<ImageView <ImageView
android:id="@+id/roomWidgetAvatar" android:id="@+id/roomWidgetAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
app:layout_constraintStart_toEndOf="@id/roomWidgetAvatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/roomWidgetUrl"
app:layout_constraintVertical_chainStyle="packed"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:id="@+id/roomWidgetName" android:id="@+id/roomWidgetName"
style="@style/BottomSheetItemTextMain" style="@style/BottomSheetItemTextMain"
tools:text="@sample/matrix.json/data/displayName" /> android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toTopOf="@id/roomWidgetUrl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/roomWidgetAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Widget name" />
<TextView <TextView
app:layout_constraintStart_toStartOf="@id/roomWidgetName"
app:layout_constraintEnd_toEndOf="@id/roomWidgetName"
app:layout_constraintTop_toBottomOf="@id/roomWidgetName"
app:layout_constraintBottom_toBottomOf="parent"
android:textStyle="normal"
android:id="@+id/roomWidgetUrl" android:id="@+id/roomWidgetUrl"
style="@style/BottomSheetItemTextSecondary" style="@style/BottomSheetItemTextSecondary"
android:textStyle="normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/roomWidgetName"
app:layout_constraintStart_toStartOf="@id/roomWidgetName"
app:layout_constraintTop_toBottomOf="@id/roomWidgetName"
tools:text="https://foobar" /> tools:text="https://foobar" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -25,8 +25,9 @@
android:textColor="@color/white" android:textColor="@color/white"
app:drawableTint="@color/white" /> app:drawableTint="@color/white" />
<TextView <com.google.android.material.button.MaterialButton
android:id="@+id/returnToCallButton" android:id="@+id/returnToCallButton"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignTop="@+id/activeCallInfo" android:layout_alignTop="@+id/activeCallInfo"
@ -38,7 +39,6 @@
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:text="@string/return_to_call" android:text="@string/return_to_call"
android:textAllCaps="true"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" /> android:textStyle="bold" />

View File

@ -5,7 +5,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?colorPrimary" android:background="?colorPrimary"
android:foreground="?attr/selectableItemBackground"
tools:parentTag="android.widget.RelativeLayout"> tools:parentTag="android.widget.RelativeLayout">
<TextView <TextView
@ -13,7 +12,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toStartOf="@id/deleteWidgetButton" android:layout_toStartOf="@id/deleteWidgetButton"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_call" android:drawableStart="@drawable/ic_call"
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -26,8 +24,9 @@
app:drawableTint="@color/white" app:drawableTint="@color/white"
tools:text="@string/ongoing_conference_call" /> tools:text="@string/ongoing_conference_call" />
<TextView <com.google.android.material.button.MaterialButton
android:id="@+id/deleteWidgetButton" android:id="@+id/deleteWidgetButton"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignTop="@+id/activeConferenceInfo" android:layout_alignTop="@+id/activeConferenceInfo"
@ -39,7 +38,6 @@
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:text="@string/action_close" android:text="@string/action_close"
android:textAllCaps="true"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" /> android:textStyle="bold" />

View File

@ -93,7 +93,7 @@
<string name="conference_call_in_progress">A conference is already in progress!</string> <string name="conference_call_in_progress">A conference is already in progress!</string>
<string name="video_meeting">Start video meeting</string> <string name="video_meeting">Start video meeting</string>
<string name="audio_meeting">Start audio meeting</string> <string name="audio_meeting">Start audio meeting</string>
<string name="audio_video_meeting_description">Meetings use Jitsi security and permission policies. All people currently in the room will see an invite to join while your meeting is happening.</string> <string name="audio_video_meeting_description">Meetings use Jitsi 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="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">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="cannot_call_yourself_with_invite">You cannot place a call with yourself, wait for participants to accept invitation</string>