Merge branch 'develop' into feature/bma/url_preview_fixes

This commit is contained in:
Benoit Marty 2021-02-08 14:32:55 +01:00 committed by GitHub
commit d2c912e5c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 122 additions and 74 deletions

View file

@ -1,4 +1,4 @@
Changes in Element 1.X.X (2020-XX-XX) Changes in Element 1.0.17 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
@ -8,10 +8,12 @@ Improvements 🙌:
- Open image from URL Preview (#2705) - Open image from URL Preview (#2705)
Bugfix 🐛: Bugfix 🐛:
- Bug in WidgetContent.computeURL() (#2767)
- Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776) - Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776)
- Join room by alias other federation error (#2778) - Join room by alias other federation error (#2778)
- HTML unescaping for URL preview (#2766) - HTML unescaping for URL preview (#2766)
- URL preview on reply fallback (#2756) - URL preview on reply fallback (#2756)
- RTL: some arrows should be rotated in RTL (#2757)
Translations 🗣: Translations 🗣:
- -
@ -26,7 +28,7 @@ Test:
- -
Other changes: Other changes:
- - Change app name from "Element (Riot.im)" to "Element"
Changes in Element 1.0.16 (2020-02-04) Changes in Element 1.0.16 (2020-02-04)
=================================================== ===================================================

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=3db89524a3981819ff28c3f979236c1274a726e146ced0c8a2020417f9bc0782 distributionSha256Sum=1433372d903ffba27496f8d5af24265310d2da0d78bf6b4e5138831d4fe066e9
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -97,10 +97,11 @@ interface WidgetService {
): LiveData<List<Widget>> ): LiveData<List<Widget>>
/** /**
* Creates a new widget in a room. It makes sure you have the rights to handle this. * Creates and send a new widget in a room. It makes sure you have the rights to handle this.
* *
* @param roomId: the room where you want to deactivate the widget. * @param roomId the room where you want to create the widget.
* @param widgetId: the widget to deactivate. * @param widgetId the widget to create.
* @param content the content of the widget
* @param callback the matrix callback to listen for result. * @param callback the matrix callback to listen for result.
* @return Cancelable * @return Cancelable
*/ */

View file

@ -35,3 +35,10 @@ fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder
return this return this
} }
fun StringBuilder.appendParamsToUrl(params: Map<String, String>): StringBuilder {
params.forEach { (param, value) ->
appendParamToUrl(param, value)
}
return this
}

View file

@ -20,11 +20,12 @@ import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.api.util.appendParamsToUrl
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.widgets.token.GetScalarTokenTask import org.matrix.android.sdk.internal.session.widgets.token.GetScalarTokenTask
import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
@ -90,25 +91,4 @@ internal class DefaultWidgetURLFormatter @Inject constructor(private val integra
} }
return false return false
} }
private fun StringBuilder.appendParamsToUrl(params: Map<String, String>): StringBuilder {
params.forEach { (param, value) ->
appendParamToUrl(param, value)
}
return this
}
private fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
if (contains("?")) {
append("&")
} else {
append("?")
}
append(param)
append("=")
append(URLEncoder.encode(value, "utf-8"))
return this
}
} }

View file

@ -65,23 +65,31 @@ internal class WidgetFactory @Inject constructor(private val userDataSource: Use
) )
} }
// 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? { private fun WidgetContent.computeURL(roomId: String?, widgetId: String): String? {
var computedUrl = url ?: return null var computedUrl = url ?: return null
val myUser = userDataSource.getUser(userId) val myUser = userDataSource.getUser(userId)
computedUrl = computedUrl
.replace("\$matrix_user_id", userId)
.replace("\$matrix_display_name", myUser?.displayName ?: userId)
.replace("\$matrix_avatar_url", myUser?.avatarUrl ?: "")
.replace("\$matrix_widget_id", widgetId)
if (roomId != null) { val keyValue = data.mapKeys { "\$${it.key}" }.toMutableMap()
computedUrl = computedUrl.replace("\$matrix_room_id", roomId)
} keyValue[WIDGET_PATTERN_MATRIX_USER_ID] = userId
for ((key, value) in data) { keyValue[WIDGET_PATTERN_MATRIX_DISPLAY_NAME] = myUser?.getBestName() ?: userId
if (value is String) { keyValue[WIDGET_PATTERN_MATRIX_AVATAR_URL] = myUser?.avatarUrl ?: ""
computedUrl = computedUrl.replace("$$key", URLEncoder.encode(value, "utf-8")) keyValue[WIDGET_PATTERN_MATRIX_WIDGET_ID] = widgetId
} keyValue[WIDGET_PATTERN_MATRIX_ROOM_ID] = roomId ?: ""
for ((key, value) in keyValue) {
computedUrl = computedUrl.replace(key, URLEncoder.encode(value.toString(), "utf-8"))
} }
return computedUrl return computedUrl
} }
companion object {
// Value to be replaced in URLS
const val WIDGET_PATTERN_MATRIX_USER_ID = "\$matrix_user_id"
const val WIDGET_PATTERN_MATRIX_DISPLAY_NAME = "\$matrix_display_name"
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_ROOM_ID = "\$matrix_room_id"
}
} }

View file

@ -210,7 +210,7 @@ android {
} }
release { release {
resValue "string", "app_name", "Element (Riot.im)" resValue "string", "app_name", "Element"
resValue "bool", "debug_mode", "false" resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"

View file

@ -16,7 +16,6 @@
package im.vector.app.features.call.conference package im.vector.app.features.call.conference
import android.net.Uri
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
@ -64,14 +63,12 @@ class JitsiCallViewModel @AssistedInject constructor(
.subscribe { .subscribe {
val jitsiWidget = it.firstOrNull() val jitsiWidget = it.firstOrNull()
if (jitsiWidget != null) { if (jitsiWidget != null) {
val uri = Uri.parse(jitsiWidget.computedUrl)
val confId = uri.getQueryParameter("confId")
val ppt = jitsiWidget.computedUrl?.let { url -> JitsiWidgetProperties(url, stringProvider) } val ppt = jitsiWidget.computedUrl?.let { url -> JitsiWidgetProperties(url, stringProvider) }
setState { setState {
copy( copy(
widget = Success(jitsiWidget), widget = Success(jitsiWidget),
jitsiUrl = "https://${ppt?.domain}", jitsiUrl = "https://${ppt?.domain}",
confId = confId ?: "", confId = ppt?.confId ?: "",
subject = roomName ?: "" subject = roomName ?: ""
) )
} }

View file

@ -19,9 +19,11 @@ package im.vector.app.features.call.conference
import android.net.Uri import android.net.Uri
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import java.net.URLDecoder
class JitsiWidgetProperties(private val uriString: String, val stringProvider: StringProvider) { class JitsiWidgetProperties(private val uriString: String, val stringProvider: StringProvider) {
val domain: String by lazy { configs["conferenceDomain"] ?: stringProvider.getString(R.string.preferred_jitsi_domain) } val domain: String by lazy { configs["conferenceDomain"] ?: stringProvider.getString(R.string.preferred_jitsi_domain) }
val confId: String? by lazy { configs["conferenceId"] }
val displayName: String? by lazy { configs["displayName"] } val displayName: String? by lazy { configs["displayName"] }
val avatarUrl: String? by lazy { configs["avatarUrl"] } val avatarUrl: String? by lazy { configs["avatarUrl"] }
@ -30,8 +32,9 @@ class JitsiWidgetProperties(private val uriString: String, val stringProvider: S
private val configs: Map<String, String?> by lazy { private val configs: Map<String, String?> by lazy {
configString?.split("&") configString?.split("&")
?.map { it.split("=") } ?.map { it.split("=") }
?.map { (key, value) -> key to value } ?.filter { it.size == 2 }
?.map { (key, value) -> key to URLDecoder.decode(value, "UTF-8") }
?.toMap() ?.toMap()
?: mapOf() .orEmpty()
} }
} }

View file

@ -37,6 +37,7 @@ import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
import org.jitsi.meet.sdk.JitsiMeetView import org.jitsi.meet.sdk.JitsiMeetView
import org.jitsi.meet.sdk.JitsiMeetViewListener import org.jitsi.meet.sdk.JitsiMeetViewListener
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import timber.log.Timber
import java.net.URL import java.net.URL
import javax.inject.Inject import javax.inject.Inject
@ -154,13 +155,19 @@ class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMee
} }
override fun onConferenceTerminated(p0: MutableMap<String, Any>?) { override fun onConferenceTerminated(p0: MutableMap<String, Any>?) {
finish() Timber.v("JitsiMeetViewListener.onConferenceTerminated()")
// Do not finish if there is an error
if (p0?.get("error") == null) {
finish()
}
} }
override fun onConferenceJoined(p0: MutableMap<String, Any>?) { override fun onConferenceJoined(p0: MutableMap<String, Any>?) {
Timber.v("JitsiMeetViewListener.onConferenceJoined()")
} }
override fun onConferenceWillJoin(p0: MutableMap<String, Any>?) { override fun onConferenceWillJoin(p0: MutableMap<String, Any>?) {
Timber.v("JitsiMeetViewListener.onConferenceWillJoin()")
} }
companion object { companion object {

View file

@ -91,6 +91,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
import org.matrix.android.sdk.api.session.widgets.model.Widget 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.appendParamToUrl
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
@ -400,15 +401,19 @@ 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" + // https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/utils/WidgetUtils.ts#L469
"?confId=$confId" + val url = buildString {
"#conferenceDomain=\$domain" + append("https://app.element.io/jitsi.html")
"&conferenceId=\$conferenceId" + appendParamToUrl("confId", confId)
"&isAudioOnly=${!action.withVideo}" + append("#conferenceDomain=\$domain")
"&displayName=\$matrix_display_name" + append("&conferenceId=\$conferenceId")
"&avatarUrl=\$matrix_avatar_url" + append("&isAudioOnly=\$isAudioOnly")
"&userId=\$matrix_user_id" append("&displayName=\$matrix_display_name")
append("&avatarUrl=\$matrix_avatar_url")
append("&userId=\$matrix_user_id")
append("&roomId=\$matrix_room_id")
append("&theme=\$theme")
}
val widgetEventContent = mapOf( val widgetEventContent = mapOf(
"url" to url, "url" to url,
"type" to WidgetType.Jitsi.legacy, "type" to WidgetType.Jitsi.legacy,

View file

@ -16,11 +16,16 @@
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 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(private val sessionHolder: ActiveSessionHolder) { class WidgetArgsBuilder @Inject constructor(
private val sessionHolder: ActiveSessionHolder,
private val context: Context
) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun buildIntegrationManagerArgs(roomId: String, integId: String?, screen: String?): WidgetArgs { fun buildIntegrationManagerArgs(roomId: String, integId: String?, screen: String?): WidgetArgs {
@ -38,7 +43,8 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
urlParams = mapOf( urlParams = mapOf(
"screen" to normalizedScreen, "screen" to normalizedScreen,
"integ_id" to integId, "integ_id" to integId,
"room_id" to roomId "room_id" to roomId,
"theme" to getTheme()
).filterNotNull() ).filterNotNull()
) )
} }
@ -54,7 +60,8 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
widgetId = widgetId, widgetId = widgetId,
urlParams = mapOf( urlParams = mapOf(
"widgetId" to widgetId, "widgetId" to widgetId,
"room_id" to roomId "room_id" to roomId,
"theme" to getTheme()
).filterNotNull() ).filterNotNull()
) )
} }
@ -66,7 +73,10 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
baseUrl = baseUrl, baseUrl = baseUrl,
kind = WidgetKind.ROOM, kind = WidgetKind.ROOM,
roomId = roomId, roomId = roomId,
widgetId = widgetId widgetId = widgetId,
urlParams = mapOf(
"theme" to getTheme()
).filterNotNull()
) )
} }
@ -74,4 +84,12 @@ class WidgetArgsBuilder @Inject constructor(private val sessionHolder: ActiveSes
private fun Map<String, String?>.filterNotNull(): Map<String, String> { private fun Map<String, String?>.filterNotNull(): Map<String, String> {
return filterValues { it != null } as Map<String, String> return filterValues { it != null } as Map<String, String>
} }
private fun getTheme(): String {
return if (ThemeUtils.isLightTheme(context)) {
"light"
} else {
"dark"
}
}
} }

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="7dp"
android:height="12dp"
android:viewportWidth="7"
android:viewportHeight="12">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M6,11l-5,-5 5,-5"
android:strokeWidth="2"
android:strokeColor="#2E2F32"
android:strokeLineCap="round"
android:strokeLineJoin="round"
tools:strokeColor="#FFAF0F" />
</vector>

View file

@ -1,14 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="7dp" android:width="7dp"
android:height="12dp" android:height="12dp"
android:viewportWidth="7" android:viewportWidth="7"
android:viewportHeight="12"> android:viewportHeight="12">
<path <path
android:pathData="M1,11l5,-5 -5,-5" android:fillColor="#00000000"
android:strokeLineJoin="round" android:fillType="evenOdd"
android:strokeWidth="2" android:pathData="M1,11l5,-5 -5,-5"
android:fillColor="#00000000" android:strokeWidth="2"
android:fillType="evenOdd" android:strokeColor="#2E2F32"
android:strokeColor="#2E2F32" android:strokeLineCap="round"
android:strokeLineCap="round"/> android:strokeLineJoin="round"
tools:strokeColor="#FFAF0F" />
</vector> </vector>

View file

@ -84,6 +84,7 @@
android:insetRight="0dp" android:insetRight="0dp"
android:insetBottom="0dp" android:insetBottom="0dp"
android:padding="0dp" android:padding="0dp"
android:rotationY="@integer/rtl_mirror_flip"
app:cornerRadius="17dp" app:cornerRadius="17dp"
app:icon="@drawable/ic_qr_code_add" app:icon="@drawable/ic_qr_code_add"
app:iconGravity="textStart" app:iconGravity="textStart"

View file

@ -44,12 +44,13 @@
android:id="@+id/messageVerificationRequestStub" android:id="@+id/messageVerificationRequestStub"
style="@style/TimelineContentStubBaseParams" style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_verification_stub" android:layout="@layout/item_timeline_event_verification_stub"
tools:visibility="gone" /> tools:visibility="visible" />
<ViewStub <ViewStub
android:id="@+id/messageVerificationDoneStub" android:id="@+id/messageVerificationDoneStub"
style="@style/TimelineContentStubBaseParams" style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_status_tile_stub" android:layout="@layout/item_timeline_event_status_tile_stub"
tools:layout_marginTop="180dp"
tools:visibility="visible" /> tools:visibility="visible" />
</FrameLayout> </FrameLayout>
@ -59,8 +60,8 @@
android:id="@+id/messageE2EDecoration" android:id="@+id/messageE2EDecoration"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:layout_marginTop="4dp"
android:layout_alignTop="@id/viewStubContainer" android:layout_alignTop="@id/viewStubContainer"
android:layout_marginTop="4dp"
android:layout_toStartOf="@id/viewStubContainer" android:layout_toStartOf="@id/viewStubContainer"
android:visibility="gone" android:visibility="gone"
tools:src="@drawable/ic_shield_warning" tools:src="@drawable/ic_shield_warning"
@ -70,11 +71,11 @@
android:id="@+id/messageFailToSendIndicator" android:id="@+id/messageFailToSendIndicator"
android:layout_width="14dp" android:layout_width="14dp"
android:layout_height="14dp" android:layout_height="14dp"
android:layout_alignTop="@+id/viewStubContainer"
android:layout_marginStart="2dp" android:layout_marginStart="2dp"
android:layout_toEndOf="@+id/viewStubContainer"
android:src="@drawable/ic_warning_badge" android:src="@drawable/ic_warning_badge"
android:visibility="gone" android:visibility="gone"
android:layout_toEndOf="@+id/viewStubContainer"
android:layout_alignTop="@+id/viewStubContainer"
tools:visibility="visible" /> tools:visibility="visible" />