Merge pull request #220 from vector-im/feature/restrict_reaction_emoji
Only show reactions with an emoji key
This commit is contained in:
commit
17a4a86ad1
|
@ -54,6 +54,7 @@ import im.vector.riotredesign.push.fcm.FcmHelper
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import im.vector.riotredesign.core.utils.initKnownEmojiHashSet
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
||||||
|
@ -124,6 +125,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
FcmHelper.onEnterBackground(appContext, activeSessionHolder)
|
FcmHelper.onEnterBackground(appContext, activeSessionHolder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
//This should be done as early as possible
|
||||||
|
initKnownEmojiHashSet(appContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
package im.vector.riotredesign.core.utils
|
package im.vector.riotredesign.core.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.features.reactions.EmojiDataSource
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
||||||
|
@ -26,6 +33,34 @@ private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
|
||||||
"|\uD83C\uDCCF\uFE0F?" +
|
"|\uD83C\uDCCF\uFE0F?" +
|
||||||
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
|
"|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
|
||||||
|
|
||||||
|
//A hashset from all supported emoji
|
||||||
|
private var knownEmojiSet: HashSet<String>? = null
|
||||||
|
|
||||||
|
fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) {
|
||||||
|
GlobalScope.launch {
|
||||||
|
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
|
||||||
|
val moshi = Moshi.Builder().build()
|
||||||
|
val jsonAdapter = moshi.adapter(EmojiDataSource.EmojiData::class.java)
|
||||||
|
val inputAsString = input.bufferedReader().use { it.readText() }
|
||||||
|
val source = jsonAdapter.fromJson(inputAsString)
|
||||||
|
knownEmojiSet = HashSet<String>()
|
||||||
|
source?.emojis?.values?.forEach {
|
||||||
|
knownEmojiSet?.add(it.emojiString())
|
||||||
|
}
|
||||||
|
done?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSingleEmoji(string: String): Boolean {
|
||||||
|
if (knownEmojiSet == null) {
|
||||||
|
Timber.e("Known Emoji Hashset not initialized")
|
||||||
|
//use fallback regexp
|
||||||
|
return containsOnlyEmojis(string)
|
||||||
|
}
|
||||||
|
return knownEmojiSet?.contains(string) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a string contains emojis.
|
* Test if a string contains emojis.
|
||||||
* It seems that the regex [emoji_regex]+ does not work.
|
* It seems that the regex [emoji_regex]+ does not work.
|
||||||
|
@ -66,4 +101,3 @@ fun containsOnlyEmojis(str: String?): Boolean {
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,9 @@ import im.vector.riotredesign.core.resources.StringProvider
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
import im.vector.riotredesign.core.utils.isSingleEmoji
|
||||||
|
|
||||||
|
|
||||||
data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
|
data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
|
||||||
|
|
||||||
data class MessageMenuState(
|
data class MessageMenuState(
|
||||||
|
@ -92,7 +95,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||||
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state
|
val event = session.getRoom(state.roomId)?.getTimeLineEvent(state.eventId) ?: return state
|
||||||
|
|
||||||
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
|
||||||
?: event.root.content.toModel()
|
?: event.root.content.toModel()
|
||||||
val type = messageContent?.type
|
val type = messageContent?.type
|
||||||
|
|
||||||
val actions = if (!event.sendState.isSent()) {
|
val actions = if (!event.sendState.isSent()) {
|
||||||
|
@ -143,8 +146,8 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||||
if (messageContent is MessageImageContent) {
|
if (messageContent is MessageImageContent) {
|
||||||
this.add(
|
this.add(
|
||||||
SimpleAction(ACTION_SHARE,
|
SimpleAction(ACTION_SHARE,
|
||||||
R.string.share, R.drawable.ic_share,
|
R.string.share, R.drawable.ic_share,
|
||||||
session.contentUrlResolver().resolveFullSize(messageContent.url))
|
session.contentUrlResolver().resolveFullSize(messageContent.url))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//TODO
|
//TODO
|
||||||
|
@ -212,31 +215,31 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canRedact(event: TimelineEvent, myUserId: String): Boolean {
|
private fun canRedact(event: TimelineEvent, myUserId: String): Boolean {
|
||||||
//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.root.senderId == myUserId
|
return event.root.senderId == myUserId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun canViewReactions(event: TimelineEvent): Boolean {
|
private fun canViewReactions(event: TimelineEvent): Boolean {
|
||||||
//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?.isNotEmpty() ?: false
|
return event.annotations?.reactionsSummary?.any { isSingleEmoji(it.key) } ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
|
private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
|
||||||
//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
|
||||||
val messageContent = event.root.content.toModel<MessageContent>()
|
val messageContent = event.root.content.toModel<MessageContent>()
|
||||||
return event.root.senderId == myUserId && (
|
return event.root.senderId == myUserId && (
|
||||||
messageContent?.type == MessageType.MSGTYPE_TEXT
|
messageContent?.type == MessageType.MSGTYPE_TEXT
|
||||||
|| messageContent?.type == MessageType.MSGTYPE_EMOTE
|
|| messageContent?.type == MessageType.MSGTYPE_EMOTE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun canCopy(type: String?): Boolean {
|
private fun canCopy(type: String?): Boolean {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
||||||
import im.vector.matrix.rx.RxRoom
|
import im.vector.matrix.rx.RxRoom
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.core.platform.VectorViewModel
|
import im.vector.riotredesign.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotredesign.core.utils.isSingleEmoji
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
@ -69,7 +70,9 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
||||||
.flatMapSingle { summaries ->
|
.flatMapSingle { summaries ->
|
||||||
Observable
|
Observable
|
||||||
.fromIterable(summaries)
|
.fromIterable(summaries)
|
||||||
.flatMapIterable {it.reactionsSummary}
|
.flatMapIterable { it.reactionsSummary
|
||||||
|
.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
|
||||||
|
}
|
||||||
.toReactionInfoList()
|
.toReactionInfoList()
|
||||||
}
|
}
|
||||||
.execute {
|
.execute {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.core.resources.ColorProvider
|
import im.vector.riotredesign.core.resources.ColorProvider
|
||||||
|
import im.vector.riotredesign.core.utils.isSingleEmoji
|
||||||
import im.vector.riotredesign.features.home.getColorFromUserId
|
import im.vector.riotredesign.features.home.getColorFromUserId
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
|
@ -68,9 +69,11 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate
|
||||||
avatarUrl = avatarUrl,
|
avatarUrl = avatarUrl,
|
||||||
memberName = formattedMemberName,
|
memberName = formattedMemberName,
|
||||||
showInformation = showInformation,
|
showInformation = showInformation,
|
||||||
orderedReactionList = event.annotations?.reactionsSummary?.map {
|
orderedReactionList = event.annotations?.reactionsSummary
|
||||||
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
?.filter { isSingleEmoji(it.key) }
|
||||||
},
|
?.map {
|
||||||
|
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
||||||
|
},
|
||||||
hasBeenEdited = hasBeenEdited
|
hasBeenEdited = hasBeenEdited
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue