Use Font emoji compat for quickReactions and pills

This commit is contained in:
Valere 2019-06-04 17:16:02 +02:00
parent 53c91dc0c2
commit d2f648edec
9 changed files with 149 additions and 59 deletions

View File

@ -0,0 +1,48 @@
package im.vector.riotredesign

import android.graphics.Typeface
import androidx.core.provider.FontsContractCompat
import timber.log.Timber


class EmojiCompatFontProvider : FontsContractCompat.FontRequestCallback() {

var typeface: Typeface? = null
set(value) {
if (value != field) {
field = value
listeners.forEach {
try {
it.compatibilityFontUpdate(value)
} catch (t: Throwable) {
Timber.e(t)
}
}
}
}

private val listeners = ArrayList<FontProviderListener>()

override fun onTypefaceRetrieved(typeface: Typeface) {
this.typeface = typeface
}

override fun onTypefaceRequestFailed(reason: Int) {
Timber.e("Failed to load Emoji Compatible font, reason:$reason")
}

fun addListener(listener: FontProviderListener) {
if (!listeners.contains(listener)) {
listeners.add(listener)
}
}

fun removeListener(listener: FontProviderListener) {
listeners.remove(listener)
}


interface FontProviderListener {
fun compatibilityFontUpdate(typeface: Typeface?)
}
}

View File

@ -18,6 +18,10 @@ package im.vector.riotredesign


import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.HandlerThread
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import android.content.res.Configuration import android.content.res.Configuration
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyAsyncUtil
@ -41,6 +45,10 @@ import timber.log.Timber


class VectorApplication : Application() { class VectorApplication : Application() {


//font thread handler
private var mFontThreadHandler: Handler? = null


val vectorConfiguration: VectorConfiguration by inject() val vectorConfiguration: VectorConfiguration by inject()


override fun onCreate() { override fun onCreate() {
@ -63,10 +71,20 @@ class VectorApplication : Application() {
val appModule = AppModule(applicationContext).definition val appModule = AppModule(applicationContext).definition
val homeModule = HomeModule().definition val homeModule = HomeModule().definition
val roomDirectoryModule = RoomDirectoryModule().definition val roomDirectoryModule = RoomDirectoryModule().definition
startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger()) val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())


Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION) Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)


val fontRequest = FontRequest(
"com.google.android.gms.fonts",
"com.google.android.gms",
"Noto Color Emoji Compat",
R.array.com_google_android_gms_fonts_certs
)

// val efp = koin.koinContext.get<EmojiCompatFontProvider>()
FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler())

vectorConfiguration.initConfiguration() vectorConfiguration.initConfiguration()
} }


@ -81,4 +99,13 @@ class VectorApplication : Application() {
vectorConfiguration.onConfigurationChanged(newConfig) vectorConfiguration.onConfigurationChanged(newConfig)
} }


private fun getFontThreadHandler(): Handler {
if (mFontThreadHandler == null) {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
mFontThreadHandler = Handler(handlerThread.looper)
}
return mFontThreadHandler!!
}

} }

View File

@ -20,6 +20,7 @@ import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringArrayProvider
@ -90,5 +91,9 @@ class AppModule(private val context: Context) {
DefaultNavigator(fragment) as Navigator DefaultNavigator(fragment) as Navigator
} }


single {
EmojiCompatFontProvider()
}

} }
} }

View File

@ -72,7 +72,8 @@ class HomeModule {
val timelineMediaSizeProvider = TimelineMediaSizeProvider() val timelineMediaSizeProvider = TimelineMediaSizeProvider()
val colorProvider = ColorProvider(fragment.requireContext()) val colorProvider = ColorProvider(fragment.requireContext())
val timelineDateFormatter = get<TimelineDateFormatter>() val timelineDateFormatter = get<TimelineDateFormatter>()
val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer, get()) val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider,
timelineDateFormatter, eventHtmlRenderer, get(), get())


val timelineItemFactory = TimelineItemFactory( val timelineItemFactory = TimelineItemFactory(
messageItemFactory = messageItemFactory, messageItemFactory = messageItemFactory,

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline.action package im.vector.riotredesign.features.home.room.detail.timeline.action


import android.graphics.Typeface
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -28,7 +29,9 @@ import com.airbnb.mvrx.BaseMvRxFragment
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.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R import im.vector.riotredesign.R
import org.koin.android.ext.android.inject


/** /**
* Quick Reaction Fragment (agree / like reactions) * Quick Reaction Fragment (agree / like reactions)
@ -54,6 +57,8 @@ class QuickReactionFragment : BaseMvRxFragment() {


var interactionListener: InteractionListener? = null var interactionListener: InteractionListener? = null


val fontProvider by inject<EmojiCompatFontProvider>()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false) val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false)
ButterKnife.bind(this, view) ButterKnife.bind(this, view)
@ -68,6 +73,10 @@ class QuickReactionFragment : BaseMvRxFragment() {
quickReact3Text.text = QuickReactionViewModel.likePositive quickReact3Text.text = QuickReactionViewModel.likePositive
quickReact4Text.text = QuickReactionViewModel.likeNegative quickReact4Text.text = QuickReactionViewModel.likeNegative


listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach {
it.typeface = fontProvider.typeface ?: Typeface.DEFAULT
}

//configure click listeners //configure click listeners
quickReact1Text.setOnClickListener { quickReact1Text.setOnClickListener {
viewModel.toggleAgree(true) viewModel.toggleAgree(true)

View File

@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
@ -55,7 +56,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val timelineDateFormatter: TimelineDateFormatter, private val timelineDateFormatter: TimelineDateFormatter,
private val htmlRenderer: EventHtmlRenderer, private val htmlRenderer: EventHtmlRenderer,
private val stringProvider: StringProvider) { private val stringProvider: StringProvider,
private val emojiCompatFontProvider: EmojiCompatFontProvider) {


fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
@ -144,20 +146,21 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.filename(messageContent.body) .filename(messageContent.body)
.iconRes(R.drawable.filetype_audio) .iconRes(R.drawable.filetype_audio)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener {
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)
})) }))
.memberClickListener( .memberClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener {
callback?.onMemberNameClicked(informationData) callback?.onMemberNameClicked(informationData)
})) }))
.cellClickListener( .cellClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view: View ->
callback?.onEventCellClicked(informationData, messageContent, view) callback?.onEventCellClicked(informationData, messageContent, view)
})) }))
.clickListener( .clickListener(
DebouncedClickListener(View.OnClickListener { _ -> DebouncedClickListener(View.OnClickListener {
callback?.onAudioMessageClicked(messageContent) callback?.onAudioMessageClicked(messageContent)
})) }))
.longClickListener { view -> .longClickListener { view ->
@ -173,6 +176,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.informationData(informationData) .informationData(informationData)
.filename(messageContent.body) .filename(messageContent.body)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.iconRes(R.drawable.filetype_attachment) .iconRes(R.drawable.filetype_attachment)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
@ -221,6 +225,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.informationData(informationData) .informationData(informationData)
.mediaData(data) .mediaData(data)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)
@ -268,6 +273,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.informationData(informationData) .informationData(informationData)
.mediaData(thumbnailData) .mediaData(thumbnailData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)
@ -311,6 +317,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
} }
.informationData(informationData) .informationData(informationData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)
@ -384,6 +391,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
.message(message) .message(message)
.informationData(informationData) .informationData(informationData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)
@ -423,6 +431,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
} }
.informationData(informationData) .informationData(informationData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface)
.avatarClickListener( .avatarClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onAvatarClicked(informationData) callback?.onAvatarClicked(informationData)

View File

@ -16,6 +16,7 @@


package im.vector.riotredesign.features.home.room.detail.timeline.item package im.vector.riotredesign.features.home.room.detail.timeline.item


import android.graphics.Typeface
import android.os.Build import android.os.Build
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -50,6 +51,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
@EpoxyAttribute @EpoxyAttribute
var memberClickListener: View.OnClickListener? = null var memberClickListener: View.OnClickListener? = null


@EpoxyAttribute
var emojiTypeFace: Typeface? = null

@EpoxyAttribute @EpoxyAttribute
var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null


@ -116,6 +120,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.setChecked(reaction.addedByMe) reactionButton.setChecked(reaction.addedByMe)
reactionButton.isEnabled = reaction.synced reactionButton.isEnabled = reaction.synced
} }

View File

@ -19,23 +19,20 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Handler
import android.os.HandlerThread
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.widget.SearchView import android.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.ViewModelProviders
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import im.vector.riotredesign.EmojiCompatFontProvider
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.* import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.*
import timber.log.Timber import org.koin.android.ext.android.inject


/** /**
* *
@ -44,20 +41,21 @@ import timber.log.Timber
* TODO: Finish Refactor to vector base activity * TODO: Finish Refactor to vector base activity
* TODO: Move font request to app * TODO: Move font request to app
*/ */
class EmojiReactionPickerActivity : VectorBaseActivity() { class EmojiReactionPickerActivity : VectorBaseActivity(), EmojiCompatFontProvider.FontProviderListener {



private lateinit var tabLayout: TabLayout private lateinit var tabLayout: TabLayout


lateinit var viewModel: EmojiChooserViewModel lateinit var viewModel: EmojiChooserViewModel


private var mHandler: Handler? = null

override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker


override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker


override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker


val emojiCompatFontProvider by inject<EmojiCompatFontProvider>()

private var tabLayoutSelectionListener = object : TabLayout.BaseOnTabSelectedListener<TabLayout.Tab> { private var tabLayoutSelectionListener = object : TabLayout.BaseOnTabSelectedListener<TabLayout.Tab> {
override fun onTabReselected(p0: TabLayout.Tab) { override fun onTabReselected(p0: TabLayout.Tab) {
} }
@ -71,19 +69,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {


} }


private fun getFontThreadHandler(): Handler {
if (mHandler == null) {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
mHandler = Handler(handlerThread.looper)
}
return mHandler!!
}

override fun initUiAndData() { override fun initUiAndData() {
configureToolbar(emojiPickerToolbar) configureToolbar(emojiPickerToolbar)


requestEmojivUnicode10CompatibleFont() emojiCompatFontProvider.let {
EmojiDrawView.configureTextPaint(this, it.typeface)
it.addListener(this)
}


tabLayout = findViewById(R.id.tabs) tabLayout = findViewById(R.id.tabs)


@ -124,27 +116,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
}) })
} }


private fun requestEmojivUnicode10CompatibleFont() { override fun compatibilityFontUpdate(typeface: Typeface?) {
val fontRequest = FontRequest( EmojiDrawView.configureTextPaint(this, typeface)
"com.google.android.gms.fonts",
"com.google.android.gms",
"Noto Color Emoji Compat",
R.array.com_google_android_gms_fonts_certs
)

EmojiDrawView.configureTextPaint(this, null)
val callback = object : FontsContractCompat.FontRequestCallback() {

override fun onTypefaceRetrieved(typeface: Typeface) {
EmojiDrawView.configureTextPaint(this@EmojiReactionPickerActivity, typeface)
} }


override fun onTypefaceRequestFailed(reason: Int) { override fun onDestroy() {
Timber.e("Failed to load Emoji Compatible font, reason:$reason") emojiCompatFontProvider.removeListener(this)
} super.onDestroy()
}

FontsContractCompat.requestFont(this, fontRequest, callback, getFontThreadHandler())
} }


override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -21,6 +21,7 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.content.Context import android.content.Context
import android.content.res.TypedArray import android.content.res.TypedArray
import android.graphics.Typeface
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
@ -56,6 +57,11 @@ 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
@ -97,6 +103,8 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut


countTextView?.text = reactionCount.toString() countTextView?.text = reactionCount.toString()


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)


onDrawable = ContextCompat.getDrawable(context, R.drawable.rounded_rect_shape) onDrawable = ContextCompat.getDrawable(context, R.drawable.rounded_rect_shape)