Refactoring / revert reaction module to package in main module

This commit is contained in:
Valere
2019-05-09 18:26:32 +02:00
parent 64216f74ae
commit dcc430f91b
31 changed files with 65 additions and 216353 deletions

View File

@ -39,6 +39,10 @@
android:windowSoftInputMode="adjustResize" />
<activity android:name=".features.media.VideoMediaViewerActivity" />
<activity
android:name="im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity"
android:label="@string/title_activity_emoji_reaction_picker" />
<service
android:name=".core.services.CallService"
android:exported="false" />

View File

@ -199,7 +199,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
* MENU MANAGEMENT
* ========================================================================================== */
final override fun onCreateOptionsMenu(menu: Menu): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val menuRes = getMenuRes()
if (menuRes != -1) {

View File

@ -52,7 +52,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.user.model.User
import im.vector.reactions.EmojiReactionPickerActivity
import im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity
import im.vector.riotredesign.R
import im.vector.riotredesign.core.dialogs.DialogListItem
import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer

View File

@ -0,0 +1,61 @@
/*
* Copyright 2019 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.riotredesign.features.reactions
import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import im.vector.riotredesign.R
class EmojiChooserFragment : Fragment() {
companion object {
fun newInstance() = EmojiChooserFragment()
}
private lateinit var viewModel: EmojiChooserViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.emoji_chooser_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = activity?.run {
ViewModelProviders.of(this).get(EmojiChooserViewModel::class.java)
} ?: throw Exception("Invalid Activity")
viewModel.initWithContect(context!!)
(view as? RecyclerView)?.let {
it.adapter = viewModel.adapter
it.adapter?.notifyDataSetChanged()
}
// val ds = EmojiDataSource(this.context!!)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019 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.riotredesign.features.reactions
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class EmojiChooserViewModel : ViewModel() {
var adapter: EmojiRecyclerAdapter? = null
val emojiSourceLiveData: MutableLiveData<EmojiDataSource> = MutableLiveData()
val currentSection: MutableLiveData<Int> = MutableLiveData()
fun initWithContect(context: Context) {
//TODO load async
val emojiDataSource = EmojiDataSource(context)
emojiSourceLiveData.value = emojiDataSource
adapter = EmojiRecyclerAdapter(emojiDataSource)
adapter?.interactionListener = object : EmojiRecyclerAdapter.InteractionListener {
override fun firstVisibleSectionChange(section: Int) {
currentSection.value = section
}
}
}
fun scrollToSection(sectionIndex: Int) {
adapter?.scrollToSection(sectionIndex)
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2019 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.riotredesign.features.reactions
import android.content.Context
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import im.vector.riotredesign.R
import java.io.InputStreamReader
import com.squareup.moshi.JsonAdapter
class EmojiDataSource(val context: Context) {
var rawData: EmojiData? = null
init {
context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input ->
val moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(EmojiData::class.java)
val inputAsString = input.bufferedReader().use { it.readText() }
this.rawData = jsonAdapter.fromJson(inputAsString)
// this.rawData = mb.fr(InputStreamReader(it), EmojiData::class.java)
}
}
@JsonClass(generateAdapter = true)
data class EmojiData(val categories: List<EmojiCategory>,
val emojis: Map<String, EmojiItem>,
val aliases: Map<String, String>)
@JsonClass(generateAdapter = true)
data class EmojiCategory(val id: String, val name: String, val emojis: List<String>)
@JsonClass(generateAdapter = true)
data class EmojiItem(
@Json(name = "a") val name: String,
@Json(name = "b") val unicode: String,
@Json(name = "j") val keywords: List<String>?,
val k: List<String>?) {
var _emojiText: String? = null
fun emojiString() : String {
if (_emojiText == null) {
val utf8Text = unicode.split("-").joinToString("") { "\\u${it}" }//"\u0048\u0065\u006C\u006C\u006F World"
_emojiText = fromUnicode(utf8Text)
}
return _emojiText!!
}
}
companion object {
fun fromUnicode(unicode: String): String {
val str = unicode.replace("\\", "")
val arr = str.split("u".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val text = StringBuffer()
for (i in 1 until arr.size) {
val hexVal = Integer.parseInt(arr[i], 16)
text.append(Character.toChars(hexVal))
}
return text.toString()
}
}
// name: 'a',
// unified: 'b',
// non_qualified: 'c',
// has_img_apple: 'd',
// has_img_google: 'e',
// has_img_twitter: 'f',
// has_img_emojione: 'g',
// has_img_facebook: 'h',
// has_img_messenger: 'i',
// keywords: 'j',
// sheet: 'k',
// emoticons: 'l',
// text: 'm',
// short_names: 'n',
// added_in: 'o',
}

View File

@ -0,0 +1,76 @@
package im.vector.riotredesign.features.reactions
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Typeface
import android.text.StaticLayout
import android.text.TextPaint
import android.util.AttributeSet
import android.view.View
import kotlin.math.abs
/**
* We want to use a custom view for rendering an emoji.
* With generic textview, the performance in the recycler view are very bad
*/
class EmojiDrawView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
var mLayout: StaticLayout? = null
set(value) {
field = value
invalidate()
}
// var _mySpacing = 0f
var emoji: String? = null
// set(value) {
// if (value != null) {
// EmojiRecyclerAdapter.beginTraceSession("EmojiDrawView.TextStaticLayout")
// mLayout = StaticLayout(value, tPaint, emojiSize, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
// if (value != field) invalidate()
// EmojiRecyclerAdapter.endTraceSession()
// } else {
// mLayout = null
//// if (value != field) invalidate()
// }
// field = value
// }
override fun onDraw(canvas: Canvas?) {
EmojiRecyclerAdapter.beginTraceSession("EmojiDrawView.onDraw")
super.onDraw(canvas)
canvas?.save()
val space = abs((width - emojiSize) / 2f)
if (mLayout == null) {
// canvas?.drawCircle(width / 2f ,width / 2f, emojiSize / 2f,tPaint)
} else {
canvas?.translate(space, space)
mLayout!!.draw(canvas)
}
canvas?.restore()
EmojiRecyclerAdapter.endTraceSession()
}
companion object {
val tPaint = TextPaint()
var emojiSize = 40
fun configureTextPaint(context: Context, typeface: Typeface?) {
tPaint.isAntiAlias = true;
tPaint.textSize = 24 * context.resources.displayMetrics.density
tPaint.color = Color.LTGRAY
typeface?.let {
tPaint.typeface = it
}
emojiSize = tPaint.measureText("😅").toInt()
}
}
}

View File

@ -0,0 +1,181 @@
/*
* Copyright 2019 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.riotredesign.features.reactions
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.os.Handler
import android.os.HandlerThread
import android.util.TypedValue
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.core.provider.FontRequest
import androidx.core.provider.FontsContractCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.tabs.TabLayout
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity
import timber.log.Timber
/**
*
* TODO: Loading indicator while getting emoji data source?
* TODO: migrate to maverick
* TODO: Finish Refactor to vector base activity
* TODO: Move font request to app
*/
class EmojiReactionPickerActivity : VectorBaseActivity() {
private lateinit var tabLayout: TabLayout
lateinit var viewModel: EmojiChooserViewModel
private var mHandler: Handler? = null
override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker
override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker
override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker
private var tabLayoutSelectionListener = object : TabLayout.BaseOnTabSelectedListener<TabLayout.Tab> {
override fun onTabReselected(p0: TabLayout.Tab) {
}
override fun onTabUnselected(p0: TabLayout.Tab) {
}
override fun onTabSelected(p0: TabLayout.Tab) {
viewModel.scrollToSection(p0.position)
}
}
private fun getFontThreadHandler(): Handler {
if (mHandler == null) {
val handlerThread = HandlerThread("fonts")
handlerThread.start()
mHandler = Handler(handlerThread.looper)
}
return mHandler!!
}
override fun initUiAndData() {
configureToolbar()
requestEmojivUnicode10CompatibleFont()
tabLayout = findViewById(R.id.tabs)
viewModel = ViewModelProviders.of(this).get(EmojiChooserViewModel::class.java)
viewModel.emojiSourceLiveData.observe(this, Observer {
it.rawData?.categories?.let { categories ->
for (category in categories) {
val s = category.emojis[0]
tabLayout.addTab(tabLayout.newTab().setText(it.rawData!!.emojis[s]!!.emojiString()))
}
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
}
})
viewModel.currentSection.observe(this, Observer { section ->
section?.let {
tabLayout.removeOnTabSelectedListener(tabLayoutSelectionListener)
tabLayout.getTabAt(it)?.select()
tabLayout.addOnTabSelectedListener(tabLayoutSelectionListener)
}
})
}
private fun requestEmojivUnicode10CompatibleFont() {
val fontRequest = FontRequest(
"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) {
Timber.e("Failed to load Emoji Compatible font, reason:$reason")
}
}
FontsContractCompat.requestFont(this, fontRequest, callback, getFontThreadHandler())
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater: MenuInflater = menuInflater
inflater.inflate(getMenuRes(), menu)
val searchItem = menu.findItem(R.id.search)
(searchItem.actionView as? SearchView)?.let {
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
it.isIconified = false
it.requestFocusFromTouch()
//we want to force the tool bar as visible even if hidden with scroll flags
findViewById<Toolbar>(R.id.toolbar)?.minimumHeight = getActionBarSize()
return true
}
override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
// when back, clear all search
findViewById<Toolbar>(R.id.toolbar)?.minimumHeight = 0
it.setQuery("", true)
return true
}
})
}
return true
}
//TODO move to ThemeUtils when core module is created
private fun getActionBarSize(): Int {
return try {
val typedValue = TypedValue()
theme.resolveAttribute(R.attr.actionBarSize, typedValue, true)
TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics)
} catch (e: Exception) {
//Timber.e(e, "Unable to get color")
TypedValue.complexToDimensionPixelSize(56, resources.displayMetrics)
}
}
companion object {
fun intent(context: Context): Intent {
val intent = Intent(context, EmojiReactionPickerActivity::class.java)
// intent.putExtra(EXTRA_MATRIX_ID, matrixID)
return intent
}
}
}

View File

@ -0,0 +1,347 @@
/*
* Copyright 2019 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.riotredesign.features.reactions
import android.os.Build
import android.os.Trace
import android.text.Layout
import android.text.StaticLayout
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import im.vector.riotredesign.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.math.abs
/**
*
* TODO: Configure Span using available width and emoji size
* TODO: Search
* TODO: Performances
* TODO: Scroll to section - Find a way to snap section to the top
*/
class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null) :
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
var interactionListener: InteractionListener? = null
var mRecyclerView: RecyclerView? = null
var currentFirstVisibleSection = 0
enum class ScrollState {
IDLE,
DRAGGING,
SETTLING,
UNKNWON
}
private var scrollState = ScrollState.UNKNWON
private var isFastScroll = false
val toUpdateWhenNotBusy = ArrayList<Pair<String, EmojiViewHolder>>()
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
this.mRecyclerView = recyclerView
val gridLayoutManager = GridLayoutManager(recyclerView.context, 8)
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (isSection(position)) gridLayoutManager.spanCount else 1
}
}.apply {
isSpanIndexCacheEnabled = true
}
recyclerView.layoutManager = gridLayoutManager
recyclerView.itemAnimator = DefaultItemAnimator().apply {
supportsChangeAnimations = false
}
recyclerView.setHasFixedSize(true)
//Default is 5 but we have lots of views for emojis
recyclerView.recycledViewPool
.setMaxRecycledViews(R.layout.grid_item_emoji, 300)
recyclerView.addOnScrollListener(scrollListener)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
this.mRecyclerView = null
recyclerView.removeOnScrollListener(scrollListener)
staticLayoutCache.clear()
super.onDetachedFromRecyclerView(recyclerView)
}
fun scrollToSection(section: Int) {
if (section < 0 || section >= dataSource?.rawData?.categories?.size ?: 0) {
//ignore
return
}
//mRecyclerView?.smoothScrollToPosition(getSectionOffset(section) - 1)
//TODO Snap section header to top
mRecyclerView?.scrollToPosition(getSectionOffset(section) - 1)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
beginTraceSession("MyAdapter.onCreateViewHolder")
val inflater = LayoutInflater.from(parent.context)
val itemView = inflater.inflate(viewType, parent, false)
val viewHolder = when (viewType) {
R.layout.grid_section_header -> SectionViewHolder(itemView)
else -> EmojiViewHolder(itemView)
}
endTraceSession()
return viewHolder
}
override fun getItemViewType(position: Int): Int {
beginTraceSession("MyAdapter.getItemViewType")
if (isSection(position)) {
return R.layout.grid_section_header
}
endTraceSession()
return R.layout.grid_item_emoji
}
private fun isSection(position: Int): Boolean {
dataSource?.rawData?.categories?.let { categories ->
var sectionOffset = 1
var lastItemInSection = 0
for (category in categories) {
lastItemInSection = sectionOffset + category.emojis.size - 1
if (position == sectionOffset - 1) return true
sectionOffset = lastItemInSection + 2
}
}
return false
}
private fun getSectionForAbsoluteIndex(position: Int): Int {
var sectionOffset = 1
var lastItemInSection = 0
var index = 0
dataSource?.rawData?.categories?.let {
for (category in it) {
lastItemInSection = sectionOffset + category.emojis.size - 1
if (position <= lastItemInSection) return index
sectionOffset = lastItemInSection + 2
index++
}
}
return index
}
private fun getSectionOffset(section: Int): Int {
//Todo cache this for fast access
var sectionOffset = 1
var lastItemInSection = 0
dataSource?.rawData?.categories?.let {
for ((index, category) in it.withIndex()) {
lastItemInSection = sectionOffset + category.emojis.size - 1
if (section == index) return sectionOffset
sectionOffset = lastItemInSection + 2
}
}
return sectionOffset
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
beginTraceSession("MyAdapter.onBindViewHolder")
dataSource?.rawData?.categories?.let { categories ->
val sectionNumber = getSectionForAbsoluteIndex(position)
if (isSection(position)) {
holder.bind(categories[sectionNumber].name)
} else {
val sectionMojis = categories[sectionNumber].emojis
val sectionOffset = getSectionOffset(sectionNumber)
val emoji = sectionMojis[position - sectionOffset]
val item = dataSource!!.rawData!!.emojis[emoji]!!.emojiString()
(holder as EmojiViewHolder).data = item
if (scrollState != ScrollState.SETTLING || !isFastScroll) {
// Log.i("PERF","Bind with draw at position:$position")
holder.bind(item)
} else {
// Log.i("PERF","Bind without draw at position:$position")
toUpdateWhenNotBusy.add(item to holder)
holder.bind(null)
}
}
}
endTraceSession()
}
override fun onViewRecycled(holder: ViewHolder) {
if (holder is EmojiViewHolder) {
holder.data = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
toUpdateWhenNotBusy.removeIf { it.second == holder }
} else {
val index = toUpdateWhenNotBusy.indexOfFirst { it.second == holder }
if (index != -1) {
toUpdateWhenNotBusy.removeAt(index)
}
}
}
super.onViewRecycled(holder)
}
override fun getItemCount(): Int {
dataSource?.rawData?.categories?.let {
var count = /*number of sections*/ it.size
for (ad in it) {
count += ad.emojis.size
}
return count
} ?: kotlin.run { return 0 }
}
abstract class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(s: String?)
}
class EmojiViewHolder(itemView: View) : ViewHolder(itemView) {
var emojiView: EmojiDrawView = itemView.findViewById(R.id.grid_item_emoji_text)
val placeHolder: View = itemView.findViewById(R.id.grid_item_place_holder)
var data: String? = null
override fun bind(s: String?) {
emojiView.emoji = s
if (s != null) {
emojiView.mLayout = getStaticLayoutForEmoji(s)
placeHolder.visibility = View.GONE
// emojiView.visibility = View.VISIBLE
} else {
emojiView.mLayout = null
placeHolder.visibility = View.VISIBLE
// emojiView.visibility = View.GONE
}
}
}
class SectionViewHolder(itemView: View) : ViewHolder(itemView) {
var textView: TextView = itemView.findViewById(R.id.section_header_textview)
override fun bind(s: String?) {
textView.text = s
}
}
companion object {
fun endTraceSession() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.endSection()
}
}
fun beginTraceSession(sectionName: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
Trace.beginSection(sectionName)
}
}
val staticLayoutCache = HashMap<String, StaticLayout>()
fun getStaticLayoutForEmoji(emoji: String): StaticLayout {
var cachedLayout = staticLayoutCache[emoji]
if (cachedLayout == null) {
cachedLayout = StaticLayout(emoji, EmojiDrawView.tPaint, EmojiDrawView.emojiSize, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
staticLayoutCache[emoji] = cachedLayout!!
}
return cachedLayout!!
}
}
interface InteractionListener {
fun firstVisibleSectionChange(section: Int)
}
//privates
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
scrollState = when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> ScrollState.IDLE
RecyclerView.SCROLL_STATE_SETTLING -> ScrollState.SETTLING
RecyclerView.SCROLL_STATE_DRAGGING -> ScrollState.DRAGGING
else -> ScrollState.UNKNWON
}
//TODO better
if (scrollState == ScrollState.IDLE) {
//
val toUpdate = toUpdateWhenNotBusy.clone() as ArrayList<Pair<String, EmojiViewHolder>>
toUpdateWhenNotBusy.clear()
toUpdate.chunked(8).forEach {
recyclerView.post {
val transition = AutoTransition().apply {
duration = 150
}
for (pair in it) {
val holder = pair.second
if (pair.first == holder.data) {
TransitionManager.beginDelayedTransition(holder.itemView as FrameLayout, transition)
val data = holder.data
holder.bind(data)
}
}
toUpdateWhenNotBusy.clear()
}
}
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//Log.i("SCROLL SPEED","scroll speed $dy")
isFastScroll = abs(dy) > 50
val visible = (recyclerView.layoutManager as GridLayoutManager).findFirstCompletelyVisibleItemPosition()
GlobalScope.launch {
val section = getSectionForAbsoluteIndex(visible)
if (section != currentFirstVisibleSection) {
currentFirstVisibleSection = section
GlobalScope.launch(Dispatchers.Main) {
interactionListener?.firstVisibleSectionChange(currentFirstVisibleSection)
}
}
}
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/pale_grey" />
</shape>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="im.vector.riotredesign.features.reactions.EmojiReactionPickerActivity">
<fragment
android:id="@+id/fragment"
android:name="im.vector.riotredesign.features.reactions.EmojiChooserFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:layout="@layout/emoji_chooser_fragment" />
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:minHeight="0dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="?attr/colorPrimary"
android:elevation="4dp" />
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/emoji_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
tools:context="im.vector.riotredesign.features.reactions.EmojiChooserFragment"
tools:itemCount="100"
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/grid_item_emoji"
tools:spanCount="10">
</androidx.recyclerview.widget.RecyclerView>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="40dp"
android:layout_height="40dp"
tools:showIn="@layout/activity_emoji_reaction_picker">
<View
android:id="@+id/grid_item_place_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="4dp"
android:visibility="gone"
tools:visibility="visible"
android:background="@drawable/circle" />
<im.vector.riotredesign.features.reactions.EmojiDrawView
android:id="@+id/grid_item_emoji_text"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center" />
<!--<TextView-->
<!--android:layout_gravity="center"-->
<!--android:id="@+id/grid_item_emoji_text"-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--tools:text="😀"-->
<!--android:textSize="24sp"-->
<!--/>-->
</FrameLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/section_header_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textStyle="bold"
tools:text="Smiley &amp; Peolple" />
</LinearLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/search"
android:icon="@drawable/ic_search_white"
android:title="@string/search"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="collapseActionView|ifRoom" />
</menu>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View File

@ -1402,6 +1402,8 @@ Why choose Riot.im?
<string name="autodiscover_well_known_autofill_dialog_message">Riot detected a custom server configuration for your userId domain \"%s\":\n%s</string>
<string name="autodiscover_well_known_autofill_confirm">Use Config</string>
<string name="title_activity_emoji_reaction_picker">Reactions</string>
<string name="reactions_agree">Agree</string>
<string name="reactions_like">Like</string>
<string name="message_add_reaction">Add Reaction</string>