forked from GitHub-Mirror/riotX-android
WIP / emoji picker
This commit is contained in:
parent
359cc67fab
commit
a64f509872
216115
mynewtrace.html
Normal file
216115
mynewtrace.html
Normal file
File diff suppressed because one or more lines are too long
1
reactions/.gitignore
vendored
Normal file
1
reactions/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
51
reactions/build.gradle
Normal file
51
reactions/build.gradle
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
def coroutines_version = "1.0.1"
|
||||||
|
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.5'
|
||||||
|
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'com.google.android.material:material:1.0.0-beta01'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0-beta01'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0-beta01'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
21
reactions/proguard-rules.pro
vendored
Normal file
21
reactions/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
|||||||
|
package im.vector.reactions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("im.vector.reactions.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
15
reactions/src/main/AndroidManifest.xml
Normal file
15
reactions/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="im.vector.reactions">
|
||||||
|
|
||||||
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".EmojiReactionPickerActivity"
|
||||||
|
android:label="@string/title_activity_emoji_reaction_picker"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
|
android:exported="true"
|
||||||
|
>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -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.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.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
|
||||||
|
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 = ViewModelProviders.of(this).get(EmojiChooserViewModel::class.java)
|
||||||
|
viewModel.initWithContect(context!!)
|
||||||
|
(view as? RecyclerView)?.let {
|
||||||
|
it.adapter = viewModel.adapter
|
||||||
|
it.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
val ds = EmojiDataSource(this.context!!)
|
||||||
|
|
||||||
|
Log.d("YO","trtrt")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.reactions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class EmojiChooserViewModel : ViewModel() {
|
||||||
|
|
||||||
|
var adapter: EmojiRecyclerAdapter? = null
|
||||||
|
|
||||||
|
fun initWithContect(context: Context) {
|
||||||
|
adapter = EmojiRecyclerAdapter(EmojiDataSource(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.reactions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
|
class EmojiDataSource(val context: Context) {
|
||||||
|
|
||||||
|
var rawData: EmojiData? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
context.resources.openRawResource(R.raw.emoji_picker_datasource).use {
|
||||||
|
var gson = Gson()
|
||||||
|
this.rawData = gson.fromJson(InputStreamReader(it), EmojiData::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EmojiData(val categories: ArrayList<EmojiCategory>,
|
||||||
|
val name: String,
|
||||||
|
val emojis: HashMap<String, EmojiItem>,
|
||||||
|
val aliases: HashMap<String, String>)
|
||||||
|
|
||||||
|
data class EmojiCategory(val id: String, val name: String, val emojis: ArrayList<String>)
|
||||||
|
data class EmojiItem(
|
||||||
|
@SerializedName("a") val name: String,
|
||||||
|
@SerializedName("b") val unicode: String,
|
||||||
|
@SerializedName("j") val keywords: ArrayList<String>?,
|
||||||
|
val k: ArrayList<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',
|
||||||
|
}
|
80
reactions/src/main/java/im/vector/reactions/EmojiDrawView.kt
Normal file
80
reactions/src/main/java/im/vector/reactions/EmojiDrawView.kt
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package im.vector.reactions
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.text.Layout
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
// var _mySpacing = 0f
|
||||||
|
|
||||||
|
var emoji: String? = null
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
if (value != null) {
|
||||||
|
EmojiRecyclerAdapter.beginTraceSession("EmojiDrawView.TextStaticLayout")
|
||||||
|
// GlobalScope.launch {
|
||||||
|
// val sl = StaticLayout(value, tPaint, emojiSize, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
|
||||||
|
// GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
// if (emoji == value) {
|
||||||
|
// mLayout = sl
|
||||||
|
// //invalidate()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
mLayout = StaticLayout(value, tPaint, emojiSize, Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
|
||||||
|
EmojiRecyclerAdapter.endTraceSession()
|
||||||
|
} else {
|
||||||
|
mLayout = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private val tPaint = TextPaint()
|
||||||
|
|
||||||
|
private var emojiSize = 40
|
||||||
|
|
||||||
|
fun configureTextPaint(context: Context) {
|
||||||
|
tPaint.isAntiAlias = true;
|
||||||
|
tPaint.textSize = 24 * context.resources.displayMetrics.density
|
||||||
|
tPaint.color = Color.LTGRAY
|
||||||
|
emojiSize = tPaint.measureText("😅").toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.reactions
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.SearchView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
|
||||||
|
class EmojiReactionPickerActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
lateinit var tabLayout: TabLayout
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_emoji_reaction_picker)
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar))
|
||||||
|
|
||||||
|
tabLayout = findViewById(R.id.tabs)
|
||||||
|
|
||||||
|
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
|
||||||
|
tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));
|
||||||
|
|
||||||
|
|
||||||
|
supportActionBar?.title = getString(R.string.title_activity_emoji_reaction_picker)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
val inflater: MenuInflater = menuInflater
|
||||||
|
inflater.inflate(R.menu.menu_emoji_reaction_picker, 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()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
|
||||||
|
// when back, clear all search
|
||||||
|
it.setQuery("", true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* 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.reactions
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Trace
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
|
||||||
|
class EmojiRecyclerAdapter(val dataSource: EmojiDataSource? = null) :
|
||||||
|
RecyclerView.Adapter<EmojiRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
// data class EmojiInfo(val stringValue: String)
|
||||||
|
// data class SectionInfo(val sectionName: String)
|
||||||
|
|
||||||
|
//val mockData: ArrayList<Pair<SectionInfo, ArrayList<EmojiInfo>>> = ArrayList()
|
||||||
|
|
||||||
|
// val dataSource : EmojiDataSource? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
// val faces = ArrayList<EmojiInfo>()
|
||||||
|
// for (i in 0..50) {
|
||||||
|
// faces.add(EmojiInfo("😅"))
|
||||||
|
// }
|
||||||
|
// val animalsNature = ArrayList<EmojiInfo>()
|
||||||
|
// for (i in 0..160) {
|
||||||
|
// animalsNature.add(EmojiInfo("🐶"))
|
||||||
|
// }
|
||||||
|
// val foods = ArrayList<EmojiInfo>()
|
||||||
|
// for (i in 0..150) {
|
||||||
|
// foods.add(EmojiInfo("🍎"))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// mockData.add(SectionInfo("Smiley & People") to faces)
|
||||||
|
// mockData.add(SectionInfo("Animals & Nature") to animalsNature)
|
||||||
|
// mockData.add(SectionInfo("Food & Drinks") to foods)
|
||||||
|
// dataSource = EMp
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// enum class ScrollState {
|
||||||
|
// IDLE,
|
||||||
|
// DRAGGING,
|
||||||
|
// SETTLING,
|
||||||
|
// UNKNWON
|
||||||
|
// }
|
||||||
|
|
||||||
|
private val scrollListener = object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
|
||||||
|
EmojiDrawView.configureTextPaint(recyclerView.context)
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
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.bind(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endTraceSession()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
override fun bind(s: String) {
|
||||||
|
emojiView.emoji = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// data class SectionsIndex(val dataSource: EmojiDataSource) {
|
||||||
|
// var sectionsIndex: ArrayList<Int> = ArrayList()
|
||||||
|
// var sectionsInfo: ArrayList<Pair<Int, Int>> = ArrayList()
|
||||||
|
// var itemCount = 0
|
||||||
|
//
|
||||||
|
// init {
|
||||||
|
// var sectionOffset = 1
|
||||||
|
// var lastItemInSection = 0
|
||||||
|
// dataSource.rawData?.categories?.let {
|
||||||
|
// for (category in it) {
|
||||||
|
// sectionsIndex.add(sectionOffset - 1)
|
||||||
|
// lastItemInSection = sectionOffset + category.emojis.size - 1
|
||||||
|
// sectionsInfo.add(sectionOffset to lastItemInSection)
|
||||||
|
// sectionOffset = lastItemInSection + 2
|
||||||
|
// itemCount += (1 + category.emojis.size)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun getCount(): Int = this.itemCount
|
||||||
|
//
|
||||||
|
// fun isSection(position: Int): Int? {
|
||||||
|
// return sectionsIndex.indexOf(position)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun getSectionForAbsoluteIndex(position: Int): Int {
|
||||||
|
// for (i in sectionsIndex.size - 1 downTo 0) {
|
||||||
|
// val sectionOffset = sectionsIndex[i]
|
||||||
|
// if (position >= sectionOffset) {
|
||||||
|
// return i
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return 0
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
<?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=".EmojiReactionPickerActivity">
|
||||||
|
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/fragment"
|
||||||
|
android:name="im.vector.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" />
|
||||||
|
<!--<androidx.core.widget.NestedScrollView-->
|
||||||
|
<!--android:id="@+id/nestedScrollView"-->
|
||||||
|
<!--android:layout_width="match_parent"-->
|
||||||
|
<!--android:layout_height="match_parent"-->
|
||||||
|
<!--android:fillViewport="true"-->
|
||||||
|
<!--android:fitsSystemWindows="true"-->
|
||||||
|
<!--app:layout_behavior="@string/appbar_scrolling_view_behavior">-->
|
||||||
|
|
||||||
|
<!--<fragment-->
|
||||||
|
<!--android:id="@+id/fragment"-->
|
||||||
|
<!--android:name="im.vector.reactions.EmojiChooserFragment"-->
|
||||||
|
<!--android:layout_width="match_parent"-->
|
||||||
|
<!--android:layout_height="match_parent"-->
|
||||||
|
<!--tools:layout="@layout/emoji_chooser_fragment" />-->
|
||||||
|
|
||||||
|
<!--</androidx.core.widget.NestedScrollView>-->
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<!--<com.google.android.material.appbar.CollapsingToolbarLayout-->
|
||||||
|
<!--android:id="@+id/collapsingToolbarLayout"-->
|
||||||
|
<!--android:layout_width="match_parent"-->
|
||||||
|
<!--android:layout_height="wrap_content"-->
|
||||||
|
<!--app:contentScrim="?attr/colorPrimary"-->
|
||||||
|
<!--app:expandedTitleGravity="top"-->
|
||||||
|
<!--app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">-->
|
||||||
|
|
||||||
|
<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="30dp"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="4dp" />
|
||||||
|
|
||||||
|
<!--</com.google.android.material.appbar.CollapsingToolbarLayout>-->
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
14
reactions/src/main/res/layout/emoji_chooser_fragment.xml
Normal file
14
reactions/src/main/res/layout/emoji_chooser_fragment.xml
Normal 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=".EmojiChooserFragment"
|
||||||
|
tools:itemCount="100"
|
||||||
|
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
tools:listitem="@layout/grid_item_emoji"
|
||||||
|
tools:spanCount="10">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
21
reactions/src/main/res/layout/grid_item_emoji.xml
Normal file
21
reactions/src/main/res/layout/grid_item_emoji.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<im.vector.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>
|
18
reactions/src/main/res/layout/grid_section_header.xml
Normal file
18
reactions/src/main/res/layout/grid_section_header.xml
Normal 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 & Peolple" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
10
reactions/src/main/res/menu/menu_emoji_reaction_picker.xml
Normal file
10
reactions/src/main/res/menu/menu_emoji_reaction_picker.xml
Normal 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>
|
1
reactions/src/main/res/raw/emoji_picker_datasource.json
Normal file
1
reactions/src/main/res/raw/emoji_picker_datasource.json
Normal file
File diff suppressed because one or more lines are too long
3
reactions/src/main/res/values/dimens.xml
Normal file
3
reactions/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
</resources>
|
4
reactions/src/main/res/values/strings.xml
Normal file
4
reactions/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">reactions</string>
|
||||||
|
<string name="title_activity_emoji_reaction_picker">Reactions</string>
|
||||||
|
</resources>
|
13
reactions/src/main/res/values/styles.xml
Normal file
13
reactions/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="AppTheme" parent="Theme.MaterialComponents.Light" />
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||||
|
</resources>
|
@ -0,0 +1,17 @@
|
|||||||
|
package im.vector.reactions;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx'
|
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':reactions'
|
||||||
|
@ -127,6 +127,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
|
implementation project(":reactions")
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
Loading…
Reference in New Issue
Block a user