Merge pull request #325 from vector-im/feature/non_unicode_reaction

Accept non unicode reactions
This commit is contained in:
Valere 2019-08-27 08:10:51 -04:00 committed by GitHub
commit a51d96bf00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 45 additions and 30 deletions

View File

@ -5,10 +5,10 @@ Features:
- Display read receipts in timeline (#81) - Display read receipts in timeline (#81)


Improvements: Improvements:
- - Reactions: Reinstate the ability to react with non-unicode keys (#307)


Other changes: Other changes:
- -


Bugfix: Bugfix:
- Fix text diff linebreak display (#441) - Fix text diff linebreak display (#441)

View File

@ -318,6 +318,8 @@ dependencies {


implementation 'diff_match_patch:diff_match_patch:current' implementation 'diff_match_patch:diff_match_patch:current'


implementation "androidx.emoji:emoji-appcompat:1.0.0"

// TESTS // TESTS
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'

View File

@ -19,10 +19,13 @@ package im.vector.riotx
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import androidx.core.provider.FontRequest import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat import androidx.core.provider.FontsContractCompat
import androidx.emoji.text.EmojiCompat
import androidx.emoji.text.FontRequestEmojiCompatConfig
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
@ -105,6 +108,23 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
) )
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler()) FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration() vectorConfiguration.initConfiguration()

//Use emoji compat for the benefit of emoji spans
val config = FontRequestEmojiCompatConfig(this, fontRequest)
.setReplaceAll(true) // we want to replace all emojis with selected font
// .setEmojiSpanIndicatorEnabled(true)
// .setEmojiSpanIndicatorColor(Color.GREEN)
EmojiCompat.init(config)
.registerInitCallback(object : EmojiCompat.InitCallback() {
override fun onInitialized() {
Timber.v("Emoji compat onInitialized success ")
}

override fun onFailed(throwable: Throwable?) {
Timber.e(throwable,"Failed to init EmojiCompat")
}
})

NotificationUtils.createNotificationChannels(applicationContext) NotificationUtils.createNotificationChannels(applicationContext)
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!

View File

@ -35,7 +35,6 @@ import im.vector.riotx.R
import im.vector.riotx.core.extensions.canReact import im.vector.riotx.core.extensions.canReact
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.isSingleEmoji
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData




@ -244,7 +243,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
if (event.root.getClearType() != EventType.MESSAGE) return false if (event.root.getClearType() != EventType.MESSAGE) return false
//TODO if user is admin or moderator //TODO if user is admin or moderator
return event.annotations?.reactionsSummary?.any { isSingleEmoji(it.key) } ?: false return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
} }





View File

@ -38,12 +38,8 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
@EpoxyAttribute @EpoxyAttribute
var timeStamp: CharSequence? = null var timeStamp: CharSequence? = null


@EpoxyAttribute
var emojiTypeFace: Typeface? = null

override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.emojiReactionView.text = reactionKey holder.emojiReactionView.text = reactionKey
holder.emojiReactionView.typeface = emojiTypeFace ?: Typeface.DEFAULT
holder.displayNameView.text = authorDisplayName holder.displayNameView.text = authorDisplayName
timeStamp?.let { timeStamp?.let {
holder.timeStampView.text = it holder.timeStampView.text = it

View File

@ -28,7 +28,6 @@ import com.airbnb.epoxy.EpoxyRecyclerView
import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
@ -43,13 +42,12 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class) private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class)


@Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory @Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider


@BindView(R.id.bottom_sheet_display_reactions_list) @BindView(R.id.bottom_sheet_display_reactions_list)
lateinit var epoxyRecyclerView: EpoxyRecyclerView lateinit var epoxyRecyclerView: EpoxyRecyclerView


private val epoxyController by lazy { private val epoxyController by lazy {
ViewReactionsEpoxyController(requireContext(), emojiCompatFontProvider.typeface) ViewReactionsEpoxyController(requireContext())
} }


override fun injectWith(screenComponent: ScreenComponent) { override fun injectWith(screenComponent: ScreenComponent) {

View File

@ -90,7 +90,7 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
.flatMapSingle { summaries -> .flatMapSingle { summaries ->
Observable Observable
.fromIterable(summaries.reactionsSummary) .fromIterable(summaries.reactionsSummary)
.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) } //.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
.toReactionInfoList() .toReactionInfoList()
} }
.execute { .execute {

View File

@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.detail.timeline.action
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.emoji.text.EmojiCompat
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
@ -30,7 +31,7 @@ import im.vector.riotx.core.ui.list.genericLoaderItem
/** /**
* Epoxy controller for reaction event list * Epoxy controller for reaction event list
*/ */
class ViewReactionsEpoxyController(private val context: Context, private val emojiCompatTypeface: Typeface?) class ViewReactionsEpoxyController(private val context: Context)
: TypedEpoxyController<DisplayReactionsViewState>() { : TypedEpoxyController<DisplayReactionsViewState>() {


override fun buildModels(state: DisplayReactionsViewState) { override fun buildModels(state: DisplayReactionsViewState) {
@ -50,9 +51,8 @@ class ViewReactionsEpoxyController(private val context: Context, private val emo
state.mapReactionKeyToMemberList()?.forEach { state.mapReactionKeyToMemberList()?.forEach {
reactionInfoSimpleItem { reactionInfoSimpleItem {
id(it.eventId) id(it.eventId)
emojiTypeFace(emojiCompatTypeface)
timeStamp(it.timestamp) timeStamp(it.timestamp)
reactionKey(it.reactionKey) reactionKey(EmojiCompat.get().process(it.reactionKey))
authorDisplayName(it.authorName ?: it.authorId) authorDisplayName(it.authorName ?: it.authorId)
} }
} }

View File

@ -151,7 +151,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
idToRefInFlow.add(reactionButton.id) idToRefInFlow.add(reactionButton.id)
reactionButton.reactionString = reaction.key reactionButton.reactionString = reaction.key
reactionButton.reactionCount = reaction.count reactionButton.reactionCount = reaction.count
reactionButton.emojiTypeFace = emojiTypeFace //reactionButton.emojiTypeFace = emojiTypeFace
reactionButton.setChecked(reaction.addedByMe) reactionButton.setChecked(reaction.addedByMe)
reactionButton.isEnabled = reaction.synced reactionButton.isEnabled = reaction.synced
} }

View File

@ -71,7 +71,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
memberName = formattedMemberName, memberName = formattedMemberName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary orderedReactionList = event.annotations?.reactionsSummary
?.filter { isSingleEmoji(it.key) } //?.filter { isSingleEmoji(it.key) }
?.map { ?.map {
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}, },

View File

@ -35,6 +35,7 @@ import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.emoji.text.EmojiCompat
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.utils.TextUtils import im.vector.riotx.core.utils.TextUtils


@ -58,12 +59,6 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut


private var reactionSelector: View? = null private var reactionSelector: View? = null


var emojiTypeFace: Typeface? = null
set(value) {
field = value
emojiView?.typeface = value ?: Typeface.DEFAULT
}

private var dotsView: DotsView private var dotsView: DotsView
private var circleView: CircleView private var circleView: CircleView
var reactedListener: ReactedListener? = null var reactedListener: ReactedListener? = null
@ -82,7 +77,9 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
var reactionString = "😀" var reactionString = "😀"
set(value) { set(value) {
field = value field = value
emojiView?.text = field //maybe cache this for performances?
val emojiSpanned = EmojiCompat.get().process(value)
emojiView?.text = emojiSpanned
} }


private var animationScaleFactor: Float = 0.toFloat() private var animationScaleFactor: Float = 0.toFloat()
@ -104,7 +101,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut


countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount) countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount)


emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT // emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT


val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0) val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0)



View File

@ -37,20 +37,23 @@


<TextView <TextView
android:id="@+id/reactionText" android:id="@+id/reactionText"
android:layout_width="20dp" android:layout_width="wrap_content"
android:layout_height="20dp" android:layout_height="20dp"
android:layout_gravity="center" android:minWidth="20dp"
android:layout_marginStart="6dp" android:layout_marginStart="6dp"
android:layout_marginLeft="6dp" android:layout_marginLeft="6dp"
android:gravity="center" android:gravity="center"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="13sp" android:textSize="13sp"
android:maxEms="10"
android:ellipsize="middle"
android:singleLine="true"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/reactionCount" app:layout_constraintEnd_toStartOf="@id/reactionCount"
tools:text="👍" /> tools:text="* Party Parrot Again * 👀" />


<TextView <TextView
android:id="@+id/reactionCount" android:id="@+id/reactionCount"
@ -58,8 +61,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintBaseline_toBaselineOf="@id/reactionText" app:layout_constraintBaseline_toBaselineOf="@id/reactionText"
android:layout_marginStart="-4dp" android:layout_marginStart="2dp"
android:layout_marginLeft="-4dp" android:layout_marginLeft="2dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:gravity="center" android:gravity="center"