Skip to content

Commit

Permalink
Merge pull request #4837 from vector-im/feature/bma/safe_epoxy_char_s…
Browse files Browse the repository at this point in the history
…equence

Safe epoxy char sequence
  • Loading branch information
bmarty authored Jan 4, 2022
2 parents 2dc88d1 + e03c806 commit 279f9e0
Show file tree
Hide file tree
Showing 76 changed files with 307 additions and 251 deletions.
1 change: 1 addition & 0 deletions changelog.d/4837.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Stop using CharSequence as EpoxyAttribute because it can lead to crash if the CharSequence mutates during rendering.
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ class SpanUtilsTest : InstrumentedTest {
val string = SpannableString("Text")
val result = spanUtils.getBindingOptions(string)
result.canUseTextFuture shouldBeEqualTo true
result.preventMutation shouldBeEqualTo false
}

@Test
Expand All @@ -117,15 +116,13 @@ class SpanUtilsTest : InstrumentedTest {
string.setSpan(StrikethroughSpan(), 10, 23, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
val result = spanUtils.getBindingOptions(string)
result.canUseTextFuture shouldBeEqualTo false
result.preventMutation shouldBeEqualTo false
}

@Test
fun testGetBindingOptionsMetricAffectingSpan() {
val string = SpannableString("Emoji \uD83D\uDE2E\u200D\uD83D\uDCA8")
val result = spanUtils.getBindingOptions(string)
result.canUseTextFuture shouldBeEqualTo false
result.preventMutation shouldBeEqualTo true
}

private fun trueIfAlwaysAllowed() = Build.VERSION.SDK_INT < Build.VERSION_CODES.P
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.epoxy.util.preventMutation
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.item.BindingOptions
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
import im.vector.app.features.media.ImageContentRenderer
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.util.MatrixItem

/**
Expand All @@ -50,13 +49,13 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
lateinit var matrixItem: MatrixItem

@EpoxyAttribute
lateinit var body: CharSequence
lateinit var body: EpoxyCharSequence

@EpoxyAttribute
var bindingOptions: BindingOptions? = null

@EpoxyAttribute
var bodyDetails: CharSequence? = null
var bodyDetails: EpoxyCharSequence? = null

@EpoxyAttribute
var imageContentRenderer: ImageContentRenderer? = null
Expand All @@ -65,7 +64,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
var data: ImageContentRenderer.Data? = null

@EpoxyAttribute
var time: CharSequence? = null
var time: String? = null

@EpoxyAttribute
var movementMethod: MovementMethod? = null
Expand All @@ -84,13 +83,9 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
}
holder.imagePreview.isVisible = data != null
holder.body.movementMethod = movementMethod
holder.body.text = if (bindingOptions?.preventMutation.orFalse()) {
body.preventMutation()
} else {
body
}
holder.bodyDetails.setTextOrHide(bodyDetails)
body.findPillsAndProcess(coroutineScope) { it.bind(holder.body) }
holder.body.text = body.charSequence
holder.bodyDetails.setTextOrHide(bodyDetails?.charSequence)
body.charSequence.findPillsAndProcess(coroutineScope) { it.bind(holder.body) }
holder.timestamp.setTextOrHide(time)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import im.vector.app.core.extensions.setTextOrHide
abstract class BottomSheetRadioActionItem : VectorEpoxyModel<BottomSheetRadioActionItem.Holder>() {

@EpoxyAttribute
var title: CharSequence? = null
var title: String? = null

@StringRes
@EpoxyAttribute
Expand All @@ -47,7 +47,7 @@ abstract class BottomSheetRadioActionItem : VectorEpoxyModel<BottomSheetRadioAct
var selected = false

@EpoxyAttribute
var description: CharSequence? = null
var description: String? = null

@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var listener: ClickListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ abstract class BottomSheetSendStateItem : VectorEpoxyModel<BottomSheetSendStateI
var showProgress: Boolean = false

@EpoxyAttribute
lateinit var text: CharSequence
lateinit var text: String

@EpoxyAttribute
@DrawableRes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.core.epoxy.charsequence

/**
* Wrapper for a CharSequence, which support mutation of the CharSequence, which can happen during rendering
*/
class EpoxyCharSequence(val charSequence: CharSequence) {
private val hash = charSequence.toString().hashCode()

override fun hashCode() = hash
override fun equals(other: Any?) = other is EpoxyCharSequence && other.hash == hash
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021 New Vector Ltd
* Copyright (c) 2022 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.
Expand All @@ -14,8 +14,9 @@
* limitations under the License.
*/

package im.vector.app.core.epoxy.util
package im.vector.app.core.epoxy.charsequence

import android.text.SpannableString

fun CharSequence?.preventMutation(): CharSequence? = this?.let { SpannableString(it) }
/**
* Extensions to wrap CharSequence to EpoxyCharSequence
*/
fun CharSequence.toEpoxyCharSequence() = EpoxyCharSequence(this)
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import im.vector.app.core.extensions.setAttributeTintedImageResource
abstract class RadioButtonItem : VectorEpoxyModel<RadioButtonItem.Holder>() {

@EpoxyAttribute
var title: CharSequence? = null
var title: String? = null

@StringRes
@EpoxyAttribute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction
* Parent class for a bottom sheet action
*/
open class BottomSheetGenericRadioAction(
open val title: CharSequence?,
open val title: String?,
open val description: String? = null,
open val isSelected: Boolean
) : VectorSharedAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ import im.vector.app.core.extensions.setTextOrHide
abstract class GenericEmptyWithActionItem : VectorEpoxyModel<GenericEmptyWithActionItem.Holder>() {

@EpoxyAttribute
var title: CharSequence? = null
var title: String? = null

@EpoxyAttribute
var description: CharSequence? = null
var description: String? = null

@EpoxyAttribute
@DrawableRes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import im.vector.app.features.themes.ThemeUtils
abstract class GenericFooterItem : VectorEpoxyModel<GenericFooterItem.Holder>() {

@EpoxyAttribute
var text: CharSequence? = null
var text: String? = null

@EpoxyAttribute
var style: ItemStyle = ItemStyle.NORMAL_TEXT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide

Expand All @@ -41,10 +42,10 @@ import im.vector.app.core.extensions.setTextOrHide
abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {

@EpoxyAttribute
var title: CharSequence? = null
var title: EpoxyCharSequence? = null

@EpoxyAttribute
var description: CharSequence? = null
var description: EpoxyCharSequence? = null

@EpoxyAttribute
var style: ItemStyle = ItemStyle.NORMAL_TEXT
Expand All @@ -71,7 +72,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {

override fun bind(holder: Holder) {
super.bind(holder)
holder.titleText.setTextOrHide(title)
holder.titleText.setTextOrHide(title?.charSequence)

if (titleIconResourceId != -1) {
holder.titleIcon.setImageResource(titleIconResourceId)
Expand All @@ -82,7 +83,7 @@ abstract class GenericItem : VectorEpoxyModel<GenericItem.Holder>() {

holder.titleText.textSize = style.toTextSize()

holder.descriptionText.setTextOrHide(description)
holder.descriptionText.setTextOrHide(description?.charSequence)

if (hasIndeterminateProcess) {
holder.progressBar.isVisible = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.themes.ThemeUtils
Expand All @@ -39,7 +40,7 @@ import im.vector.app.features.themes.ThemeUtils
abstract class GenericPillItem : VectorEpoxyModel<GenericPillItem.Holder>() {

@EpoxyAttribute
var text: CharSequence? = null
var text: EpoxyCharSequence? = null

@EpoxyAttribute
var style: ItemStyle = ItemStyle.NORMAL_TEXT
Expand All @@ -60,7 +61,7 @@ abstract class GenericPillItem : VectorEpoxyModel<GenericPillItem.Holder>() {
override fun bind(holder: Holder) {
super.bind(holder)

holder.textView.setTextOrHide(text)
holder.textView.setTextOrHide(text?.charSequence)
holder.textView.typeface = style.toTypeFace()
holder.textView.textSize = style.toTextSize()
holder.textView.gravity = if (centered) Gravity.CENTER_HORIZONTAL else Gravity.START
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.charsequence.EpoxyCharSequence
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.themes.ThemeUtils
Expand All @@ -41,10 +42,10 @@ import im.vector.app.features.themes.ThemeUtils
abstract class GenericWithValueItem : VectorEpoxyModel<GenericWithValueItem.Holder>() {

@EpoxyAttribute
var title: CharSequence? = null
var title: EpoxyCharSequence? = null

@EpoxyAttribute
var value: CharSequence? = null
var value: String? = null

@EpoxyAttribute
@ColorInt
Expand All @@ -62,7 +63,7 @@ abstract class GenericWithValueItem : VectorEpoxyModel<GenericWithValueItem.Hold

override fun bind(holder: Holder) {
super.bind(holder)
holder.titleText.setTextOrHide(title)
holder.titleText.setTextOrHide(title?.charSequence)

if (titleIconResourceId != -1) {
holder.titleIcon.setImageResource(titleIconResourceId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import im.vector.app.core.epoxy.onClick
abstract class AutocompleteCommandItem : VectorEpoxyModel<AutocompleteCommandItem.Holder>() {

@EpoxyAttribute
var name: CharSequence? = null
var name: String? = null

@EpoxyAttribute
var parameters: CharSequence? = null
var parameters: String? = null

@EpoxyAttribute
var description: CharSequence? = null
var description: String? = null

@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: ClickListener? = null
Expand Down
Loading

0 comments on commit 279f9e0

Please sign in to comment.