Compute WidgetUrl "onDemand" to properly handle the theme value

This commit is contained in:
Benoit Marty 2021-02-10 14:44:33 +01:00 committed by Benoit Marty
parent 463f2a7ad7
commit 1978a180ff
12 changed files with 73 additions and 30 deletions

View file

@ -56,6 +56,11 @@ interface WidgetService {
excludedTypes: Set<String>? = null excludedTypes: Set<String>? = null
): List<Widget> ): List<Widget>
/**
* Return the computed URL of a widget
*/
fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String?
/** /**
* Returns the live room widgets so you can listen to them. * Returns the live room widgets so you can listen to them.
* Some widgets can be deactivated, so be sure to check for isActive. * Some widgets can be deactivated, so be sure to check for isActive.

View file

@ -25,7 +25,6 @@ data class Widget(
val widgetId: String, val widgetId: String,
val senderInfo: SenderInfo?, val senderInfo: SenderInfo?,
val isAddedByMe: Boolean, val isAddedByMe: Boolean,
val computedUrl: String?,
val type: WidgetType val type: WidgetType
) { ) {

View file

@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataS
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -55,7 +54,6 @@ import javax.inject.Inject
*/ */
@SessionScope @SessionScope
internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration, internal class IntegrationManager @Inject constructor(matrixConfiguration: MatrixConfiguration,
private val taskExecutor: TaskExecutor,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val accountDataDataSource: AccountDataDataSource, private val accountDataDataSource: AccountDataDataSource,

View file

@ -50,6 +50,10 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
return widgetManager.getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes) return widgetManager.getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
} }
override fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String? {
return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
}
override fun getRoomWidgetsLive( override fun getRoomWidgetsLive(
roomId: String, roomId: String,
widgetId: QueryStringValue, widgetId: QueryStringValue,

View file

@ -104,6 +104,10 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
return widgetEvents.mapEventsToWidgets(widgetTypes, excludedTypes) return widgetEvents.mapEventsToWidgets(widgetTypes, excludedTypes)
} }
fun getWidgetComputedUrl(widget: Widget, isLightTheme: Boolean): String? {
return widgetFactory.computeURL(widget, isLightTheme)
}
private fun List<Event>.mapEventsToWidgets(widgetTypes: Set<String>? = null, private fun List<Event>.mapEventsToWidgets(widgetTypes: Set<String>? = null,
excludedTypes: Set<String>? = null): List<Widget> { excludedTypes: Set<String>? = null): List<Widget> {
val widgetEvents = this val widgetEvents = this

View file

@ -55,30 +55,29 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
} }
} }
val isAddedByMe = widgetEvent.senderId == userId val isAddedByMe = widgetEvent.senderId == userId
val computedUrl = widgetContent.computeURL(widgetEvent.roomId, widgetId)
return Widget( return Widget(
widgetContent = widgetContent, widgetContent = widgetContent,
event = widgetEvent, event = widgetEvent,
widgetId = widgetId, widgetId = widgetId,
senderInfo = senderInfo, senderInfo = senderInfo,
isAddedByMe = isAddedByMe, isAddedByMe = isAddedByMe,
computedUrl = computedUrl,
type = WidgetType.fromString(type) type = WidgetType.fromString(type)
) )
} }
// Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33 // Ref: https://github.com/matrix-org/matrix-widget-api/blob/master/src/templating/url-template.ts#L29-L33
private fun WidgetContent.computeURL(roomId: String?, widgetId: String): String? { fun computeURL(widget: Widget, isLightTheme: Boolean): String? {
var computedUrl = url ?: return null var computedUrl = widget.widgetContent.url ?: return null
val myUser = userDataSource.getUser(userId) val myUser = userDataSource.getUser(userId)
val keyValue = data.mapKeys { "\$${it.key}" }.toMutableMap() val keyValue = widget.widgetContent.data.mapKeys { "\$${it.key}" }.toMutableMap()
keyValue[WIDGET_PATTERN_MATRIX_USER_ID] = userId keyValue[WIDGET_PATTERN_MATRIX_USER_ID] = userId
keyValue[WIDGET_PATTERN_MATRIX_DISPLAY_NAME] = myUser?.getBestName() ?: userId keyValue[WIDGET_PATTERN_MATRIX_DISPLAY_NAME] = myUser?.getBestName() ?: userId
keyValue[WIDGET_PATTERN_MATRIX_AVATAR_URL] = urlResolver.resolveFullSize(myUser?.avatarUrl) ?: "" keyValue[WIDGET_PATTERN_MATRIX_AVATAR_URL] = urlResolver.resolveFullSize(myUser?.avatarUrl) ?: ""
keyValue[WIDGET_PATTERN_MATRIX_WIDGET_ID] = widgetId keyValue[WIDGET_PATTERN_MATRIX_WIDGET_ID] = widget.widgetId
keyValue[WIDGET_PATTERN_MATRIX_ROOM_ID] = roomId ?: "" keyValue[WIDGET_PATTERN_MATRIX_ROOM_ID] = widget.event.roomId ?: ""
keyValue[WIDGET_PATTERN_THEME] = getTheme(isLightTheme)
for ((key, value) in keyValue) { for ((key, value) in keyValue) {
computedUrl = computedUrl.replace(key, URLEncoder.encode(value.toString(), "utf-8")) computedUrl = computedUrl.replace(key, URLEncoder.encode(value.toString(), "utf-8"))
@ -86,6 +85,10 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
return computedUrl return computedUrl
} }
private fun getTheme(isLightTheme: Boolean): String {
return if (isLightTheme) "light" else "dark"
}
companion object { companion object {
// Value to be replaced in URLS // Value to be replaced in URLS
const val WIDGET_PATTERN_MATRIX_USER_ID = "\$matrix_user_id" const val WIDGET_PATTERN_MATRIX_USER_ID = "\$matrix_user_id"
@ -93,5 +96,6 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
const val WIDGET_PATTERN_MATRIX_AVATAR_URL = "\$matrix_avatar_url" const val WIDGET_PATTERN_MATRIX_AVATAR_URL = "\$matrix_avatar_url"
const val WIDGET_PATTERN_MATRIX_WIDGET_ID = "\$matrix_widget_id" const val WIDGET_PATTERN_MATRIX_WIDGET_ID = "\$matrix_widget_id"
const val WIDGET_PATTERN_MATRIX_ROOM_ID = "\$matrix_room_id" const val WIDGET_PATTERN_MATRIX_ROOM_ID = "\$matrix_room_id"
const val WIDGET_PATTERN_THEME = "\$theme"
} }
} }

View file

@ -25,6 +25,7 @@ import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.themes.ThemeProvider
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
@ -37,7 +38,8 @@ class JitsiCallViewModel @AssistedInject constructor(
@Assisted initialState: JitsiCallViewState, @Assisted initialState: JitsiCallViewState,
@Assisted val args: VectorJitsiActivity.Args, @Assisted val args: VectorJitsiActivity.Args,
private val session: Session, private val session: Session,
private val stringProvider: StringProvider private val stringProvider: StringProvider,
private val themeProvider: ThemeProvider
) : VectorViewModel<JitsiCallViewState, JitsiCallViewActions, JitsiCallViewEvents>(initialState) { ) : VectorViewModel<JitsiCallViewState, JitsiCallViewActions, JitsiCallViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -45,6 +47,8 @@ class JitsiCallViewModel @AssistedInject constructor(
fun create(initialState: JitsiCallViewState, args: VectorJitsiActivity.Args): JitsiCallViewModel fun create(initialState: JitsiCallViewState, args: VectorJitsiActivity.Args): JitsiCallViewModel
} }
private val widgetService = session.widgetService()
init { init {
val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem() val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem()
val userInfo = JitsiMeetUserInfo().apply { val userInfo = JitsiMeetUserInfo().apply {
@ -57,13 +61,14 @@ class JitsiCallViewModel @AssistedInject constructor(
copy(userInfo = userInfo) copy(userInfo = userInfo)
} }
session.widgetService().getRoomWidgetsLive(args.roomId, QueryStringValue.Equals(args.widgetId), WidgetType.Jitsi.values()) widgetService.getRoomWidgetsLive(args.roomId, QueryStringValue.Equals(args.widgetId), WidgetType.Jitsi.values())
.asObservable() .asObservable()
.distinctUntilChanged() .distinctUntilChanged()
.subscribe { .subscribe {
val jitsiWidget = it.firstOrNull() val jitsiWidget = it.firstOrNull()
if (jitsiWidget != null) { if (jitsiWidget != null) {
val ppt = jitsiWidget.computedUrl?.let { url -> JitsiWidgetProperties(url, stringProvider) } val ppt = widgetService.getWidgetComputedUrl(jitsiWidget, themeProvider.isLightTheme())
?.let { url -> JitsiWidgetProperties(url, stringProvider) }
setState { setState {
copy( copy(
widget = Success(jitsiWidget), widget = Success(jitsiWidget),

View file

@ -32,7 +32,7 @@ class StickerPickerActionHandler @Inject constructor(private val session: Sessio
return@withContext RoomDetailViewEvents.DisplayEnableIntegrationsWarning return@withContext RoomDetailViewEvents.DisplayEnableIntegrationsWarning
} }
val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive } val stickerWidget = session.widgetService().getUserWidgets(WidgetType.StickerPicker.values()).firstOrNull { it.isActive }
if (stickerWidget == null || stickerWidget.computedUrl.isNullOrBlank()) { if (stickerWidget == null || stickerWidget.widgetContent.url.isNullOrBlank()) {
RoomDetailViewEvents.DisplayPromptForIntegrationManager RoomDetailViewEvents.DisplayPromptForIntegrationManager
} else { } else {
RoomDetailViewEvents.OpenStickerPicker( RoomDetailViewEvents.OpenStickerPicker(

View file

@ -43,7 +43,7 @@ abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() {
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.widgetName.text = widget.name holder.widgetName.text = widget.name
holder.widgetUrl.text = tryOrNull { URL(widget.computedUrl) }?.host ?: widget.computedUrl holder.widgetUrl.text = tryOrNull { URL(widget.widgetContent.url) }?.host ?: widget.widgetContent.url
if (iconRes != null) { if (iconRes != null) {
holder.iconImage.isVisible = true holder.iconImage.isVisible = true
holder.iconImage.setImageResource(iconRes!!) holder.iconImage.setImageResource(iconRes!!)

View file

@ -0,0 +1,29 @@
/*
* 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.themes
import android.content.Context
import javax.inject.Inject
/**
* Injectable class to encapsulate ThemeUtils call...
*/
class ThemeProvider @Inject constructor(
private val context: Context
) {
fun isLightTheme() = ThemeUtils.isLightTheme(context)
}

View file

@ -16,15 +16,14 @@
package im.vector.app.features.widgets package im.vector.app.features.widgets
import android.content.Context
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeProvider
import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.Widget
import javax.inject.Inject import javax.inject.Inject
class WidgetArgsBuilder @Inject constructor( class WidgetArgsBuilder @Inject constructor(
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
private val context: Context private val themeProvider: ThemeProvider
) { ) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -52,7 +51,8 @@ class WidgetArgsBuilder @Inject constructor(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun buildStickerPickerArgs(roomId: String, widget: Widget): WidgetArgs { fun buildStickerPickerArgs(roomId: String, widget: Widget): WidgetArgs {
val widgetId = widget.widgetId val widgetId = widget.widgetId
val baseUrl = widget.computedUrl ?: throw IllegalStateException() val baseUrl = sessionHolder.getActiveSession().widgetService()
.getWidgetComputedUrl(widget, themeProvider.isLightTheme()) ?: throw IllegalStateException()
return WidgetArgs( return WidgetArgs(
baseUrl = baseUrl, baseUrl = baseUrl,
kind = WidgetKind.STICKER_PICKER, kind = WidgetKind.STICKER_PICKER,
@ -68,15 +68,13 @@ class WidgetArgsBuilder @Inject constructor(
fun buildRoomWidgetArgs(roomId: String, widget: Widget): WidgetArgs { fun buildRoomWidgetArgs(roomId: String, widget: Widget): WidgetArgs {
val widgetId = widget.widgetId val widgetId = widget.widgetId
val baseUrl = widget.computedUrl ?: throw IllegalStateException() val baseUrl = sessionHolder.getActiveSession().widgetService()
.getWidgetComputedUrl(widget, themeProvider.isLightTheme()) ?: throw IllegalStateException()
return WidgetArgs( return WidgetArgs(
baseUrl = baseUrl, baseUrl = baseUrl,
kind = WidgetKind.ROOM, kind = WidgetKind.ROOM,
roomId = roomId, roomId = roomId,
widgetId = widgetId, widgetId = widgetId
urlParams = mapOf(
"theme" to getTheme()
).filterNotNull()
) )
} }
@ -86,7 +84,7 @@ class WidgetArgsBuilder @Inject constructor(
} }
private fun getTheme(): String { private fun getTheme(): String {
return if (ThemeUtils.isLightTheme(context)) { return if (themeProvider.isLightTheme()) {
"light" "light"
} else { } else {
"dark" "dark"

View file

@ -27,6 +27,7 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.session.widgets.model.WidgetType
@ -52,11 +53,7 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in
.filter { it.isNotEmpty() } .filter { it.isNotEmpty() }
.map { .map {
val widget = it.first() val widget = it.first()
val domain = try { val domain = tryOrNull { URL(widget.widgetContent.url) }?.host
URL(widget.computedUrl).host
} catch (e: Throwable) {
null
}
// TODO check from widget urls the perms that should be shown? // TODO check from widget urls the perms that should be shown?
// For now put all // For now put all
if (widget.type == WidgetType.Jitsi) { if (widget.type == WidgetType.Jitsi) {