Add vertical scroll to Emoji Keyboard.
This commit is contained in:
parent
a71fe0fd75
commit
ed4bab1b8b
10 changed files with 334 additions and 121 deletions
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.emoji
|
||||
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.StringRes
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
|
@ -17,8 +18,30 @@ enum class EmojiCategory(val priority: Int, val key: String, @AttrRes val icon:
|
|||
FLAGS(7, "Flags", R.attr.emoji_category_flags),
|
||||
EMOTICONS(8, "Emoticons", R.attr.emoji_category_emoticons);
|
||||
|
||||
@StringRes
|
||||
fun getCategoryLabel(): Int {
|
||||
return getCategoryLabel(icon)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun forKey(key: String) = values().first { it.key == key }
|
||||
|
||||
@JvmStatic
|
||||
@StringRes
|
||||
fun getCategoryLabel(@AttrRes iconAttr: Int): Int {
|
||||
return when (iconAttr) {
|
||||
R.attr.emoji_category_people -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people
|
||||
R.attr.emoji_category_nature -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__nature
|
||||
R.attr.emoji_category_foods -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__food
|
||||
R.attr.emoji_category_activity -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__activities
|
||||
R.attr.emoji_category_places -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__places
|
||||
R.attr.emoji_category_objects -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__objects
|
||||
R.attr.emoji_category_symbols -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__symbols
|
||||
R.attr.emoji_category_flags -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__flags
|
||||
R.attr.emoji_category_emoticons -> R.string.ReactWithAnyEmojiBottomSheetDialogFragment__emoticons
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package org.thoughtcrime.securesms.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class BottomShadowBehavior extends CoordinatorLayout.Behavior<View> {
|
||||
|
||||
private int bottomBarId;
|
||||
private boolean shown = true;
|
||||
|
||||
public BottomShadowBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (attrs != null) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BottomShadowBehavior);
|
||||
bottomBarId = a.getResourceId(R.styleable.BottomShadowBehavior_bottom_bar_id, 0);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
if (bottomBarId == 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
||||
return dependency.getId() == bottomBarId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
||||
float alpha = (dependency.getHeight() - (int) dependency.getTranslationY()) / (float) dependency.getHeight();
|
||||
child.setAlpha(alpha);
|
||||
|
||||
float y = dependency.getY() - child.getHeight();
|
||||
if (y != child.getY()) {
|
||||
child.setY(y);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.thoughtcrime.securesms.keyboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class TopShadowBehavior extends CoordinatorLayout.Behavior<View> {
|
||||
|
||||
private int targetId;
|
||||
private boolean shown = true;
|
||||
|
||||
public TopShadowBehavior(int targetId) {
|
||||
super();
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
public TopShadowBehavior(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (attrs != null) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopShadowBehavior);
|
||||
targetId = a.getResourceId(R.styleable.TopShadowBehavior_app_bar_layout_id, 0);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
if (targetId == 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
||||
return dependency instanceof RecyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
|
||||
boolean shouldShow = dependency.getY() != parent.findViewById(targetId).getHeight();
|
||||
|
||||
if (shouldShow != shown) {
|
||||
if (shouldShow) {
|
||||
show(child);
|
||||
} else {
|
||||
hide(child);
|
||||
}
|
||||
shown = shouldShow;
|
||||
}
|
||||
|
||||
if (child.getY() != 0) {
|
||||
child.setY(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void show(View child) {
|
||||
child.animate()
|
||||
.setDuration(250)
|
||||
.alpha(1f);
|
||||
}
|
||||
|
||||
private void hide(View child) {
|
||||
child.animate()
|
||||
.setDuration(250)
|
||||
.alpha(0f);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package org.thoughtcrime.securesms.keyboard.emoji
|
||||
|
||||
import android.view.ViewGroup
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageView
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
|
||||
class EmojiKeyboardPageAdapter(
|
||||
private val emojiSelectionListener: EmojiKeyboardProvider.EmojiEventListener,
|
||||
private val variationSelectorListener: EmojiPageViewGridAdapter.VariationSelectorListener
|
||||
) : MappingAdapter() {
|
||||
|
||||
init {
|
||||
registerFactory(EmojiPageMappingModel::class.java) { parent ->
|
||||
val pageView = EmojiPageView(parent.context, emojiSelectionListener, variationSelectorListener, true)
|
||||
|
||||
val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
pageView.layoutParams = layoutParams
|
||||
pageView.presentForEmojiKeyboard()
|
||||
|
||||
ViewHolder(pageView)
|
||||
}
|
||||
}
|
||||
|
||||
private class ViewHolder(
|
||||
private val emojiPageView: EmojiPageView,
|
||||
) : MappingViewHolder<EmojiPageMappingModel>(emojiPageView) {
|
||||
|
||||
override fun bind(model: EmojiPageMappingModel) {
|
||||
emojiPageView.bindSearchableAdapter(model.emojiPageModel)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,28 +6,36 @@ import android.view.KeyEvent
|
|||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageView
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.EmojiHeader
|
||||
import org.thoughtcrime.securesms.keyboard.findListener
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import java.util.Optional
|
||||
|
||||
private val DELETE_KEY_EVENT: KeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)
|
||||
|
||||
class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fragment), EmojiKeyboardProvider.EmojiEventListener, EmojiPageViewGridAdapter.VariationSelectorListener {
|
||||
|
||||
private lateinit var viewModel: EmojiKeyboardPageViewModel
|
||||
private lateinit var emojiPager: ViewPager2
|
||||
private lateinit var emojiPageView: EmojiPageView
|
||||
private lateinit var searchView: View
|
||||
private lateinit var emojiCategoriesRecycler: RecyclerView
|
||||
private lateinit var backspaceView: View
|
||||
private lateinit var eventListener: EmojiKeyboardProvider.EmojiEventListener
|
||||
private lateinit var callback: Callback
|
||||
private lateinit var pagesAdapter: EmojiKeyboardPageAdapter
|
||||
private lateinit var categoriesAdapter: EmojiKeyboardPageCategoriesAdapter
|
||||
private lateinit var searchBar: KeyboardPageSearchView
|
||||
private lateinit var appBarLayout: AppBarLayout
|
||||
|
||||
private val categoryUpdateOnScroll = UpdateCategorySelectionOnScroll()
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
@ -37,33 +45,28 @@ class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fr
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
emojiPager = view.findViewById(R.id.emoji_pager)
|
||||
emojiPageView = view.findViewById(R.id.emoji_page_view)
|
||||
emojiPageView.initialize(this, this, true)
|
||||
emojiPageView.addOnScrollListener(categoryUpdateOnScroll)
|
||||
|
||||
searchView = view.findViewById(R.id.emoji_search)
|
||||
searchBar = view.findViewById(R.id.emoji_keyboard_search_text)
|
||||
emojiCategoriesRecycler = view.findViewById(R.id.emoji_categories_recycler)
|
||||
backspaceView = view.findViewById(R.id.emoji_backspace)
|
||||
appBarLayout = view.findViewById(R.id.emoji_keyboard_search_appbar)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(requireActivity()).get(EmojiKeyboardPageViewModel::class.java)
|
||||
|
||||
pagesAdapter = EmojiKeyboardPageAdapter(this, this)
|
||||
viewModel = ViewModelProviders.of(requireActivity(), EmojiKeyboardPageViewModel.Factory(requireContext()))
|
||||
.get(EmojiKeyboardPageViewModel::class.java)
|
||||
|
||||
categoriesAdapter = EmojiKeyboardPageCategoriesAdapter { key ->
|
||||
scrollTo(key)
|
||||
viewModel.onKeySelected(key)
|
||||
|
||||
val page = pagesAdapter.currentList.indexOfFirst {
|
||||
(it as EmojiPageMappingModel).key == key
|
||||
}
|
||||
|
||||
if (emojiPager.currentItem != page) {
|
||||
emojiPager.currentItem = page
|
||||
}
|
||||
}
|
||||
|
||||
emojiPager.adapter = pagesAdapter
|
||||
emojiCategoriesRecycler.adapter = categoriesAdapter
|
||||
|
||||
searchBar.callbacks = EmojiKeyboardPageSearchViewCallbacks()
|
||||
|
@ -75,22 +78,24 @@ class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fr
|
|||
backspaceView.setOnClickListener { eventListener.onKeyEvent(DELETE_KEY_EVENT) }
|
||||
|
||||
viewModel.categories.observe(viewLifecycleOwner) { categories ->
|
||||
categoriesAdapter.submitList(categories)
|
||||
categoriesAdapter.submitList(categories) {
|
||||
(emojiCategoriesRecycler.parent as View).invalidate()
|
||||
emojiCategoriesRecycler.parent.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.pages.observe(viewLifecycleOwner) { pages ->
|
||||
val registerPageCallback: Boolean = pagesAdapter.currentList.isEmpty() && pages.isNotEmpty()
|
||||
pagesAdapter.submitList(pages) { updatePagerPosition(registerPageCallback) }
|
||||
emojiPageView.setList(pages)
|
||||
}
|
||||
|
||||
viewModel.selectedKey.observe(viewLifecycleOwner) { updateCategoryTab() }
|
||||
viewModel.selectedKey.observe(viewLifecycleOwner) { updateCategoryTab(it) }
|
||||
|
||||
eventListener = findListener() ?: throw AssertionError("No emoji listener found")
|
||||
}
|
||||
|
||||
private fun updateCategoryTab() {
|
||||
private fun updateCategoryTab(key: String) {
|
||||
emojiCategoriesRecycler.post {
|
||||
val index: Int = categoriesAdapter.currentList.indexOfFirst { (it as? EmojiKeyboardPageCategoryMappingModel)?.key == viewModel.selectedKey.value }
|
||||
val index: Int = categoriesAdapter.indexOfFirst(EmojiKeyboardPageCategoryMappingModel::class.java) { it.key == key }
|
||||
|
||||
if (index != -1) {
|
||||
emojiCategoriesRecycler.smoothScrollToPosition(index)
|
||||
|
@ -98,17 +103,14 @@ class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fr
|
|||
}
|
||||
}
|
||||
|
||||
private fun updatePagerPosition(registerPageCallback: Boolean) {
|
||||
val page = pagesAdapter.currentList.indexOfFirst {
|
||||
(it as EmojiPageMappingModel).key == viewModel.selectedKey.value
|
||||
}
|
||||
|
||||
if (emojiPager.currentItem != page && page != -1) {
|
||||
emojiPager.setCurrentItem(page, false)
|
||||
}
|
||||
|
||||
if (registerPageCallback) {
|
||||
emojiPager.registerOnPageChangeCallback(PageChanged(pagesAdapter))
|
||||
private fun scrollTo(key: String) {
|
||||
emojiPageView.adapter?.let { adapter ->
|
||||
val index = adapter.indexOfFirst(EmojiHeader::class.java) { it.key == key }
|
||||
if (index != -1) {
|
||||
appBarLayout.setExpanded(false, true)
|
||||
categoryUpdateOnScroll.startAutoScrolling()
|
||||
emojiPageView.smoothScrollToPositionTop(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,16 +124,7 @@ class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fr
|
|||
eventListener.onKeyEvent(keyEvent)
|
||||
}
|
||||
|
||||
override fun onVariationSelectorStateChanged(open: Boolean) {
|
||||
emojiPager.isUserInputEnabled = !open
|
||||
}
|
||||
|
||||
private inner class PageChanged(private val adapter: EmojiKeyboardPageAdapter) : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
val mappingModel: EmojiPageMappingModel = adapter.currentList[position] as EmojiPageMappingModel
|
||||
viewModel.onKeySelected(mappingModel.key)
|
||||
}
|
||||
}
|
||||
override fun onVariationSelectorStateChanged(open: Boolean) = Unit
|
||||
|
||||
private inner class EmojiKeyboardPageSearchViewCallbacks : KeyboardPageSearchView.Callbacks {
|
||||
override fun onClicked() {
|
||||
|
@ -139,6 +132,38 @@ class EmojiKeyboardPageFragment : Fragment(R.layout.keyboard_pager_emoji_page_fr
|
|||
}
|
||||
}
|
||||
|
||||
private inner class UpdateCategorySelectionOnScroll : RecyclerView.OnScrollListener() {
|
||||
|
||||
private var doneScrolling: Boolean = true
|
||||
|
||||
fun startAutoScrolling() {
|
||||
doneScrolling = false
|
||||
}
|
||||
|
||||
@Override
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
if (newState == SCROLL_STATE_IDLE && !doneScrolling) {
|
||||
doneScrolling = true
|
||||
onScrolled(recyclerView, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
if (recyclerView.layoutManager == null || !doneScrolling) {
|
||||
return
|
||||
}
|
||||
|
||||
emojiPageView.adapter?.let { adapter ->
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
val index = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||
val item: Optional<MappingModel<*>> = adapter.getModel(index)
|
||||
if (item.isPresent && item.get() is EmojiPageViewGridAdapter.HasKey) {
|
||||
viewModel.onKeySelected((item.get() as EmojiPageViewGridAdapter.HasKey).key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun openEmojiSearch()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package org.thoughtcrime.securesms.keyboard.emoji
|
||||
|
||||
import android.content.Context
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource.Companion.latest
|
||||
import java.util.function.Consumer
|
||||
|
||||
class EmojiKeyboardPageRepository(context: Context) {
|
||||
|
||||
private val recentEmojiPageModel: RecentEmojiPageModel = RecentEmojiPageModel(context, EmojiKeyboardProvider.RECENT_STORAGE_KEY)
|
||||
|
||||
fun getEmoji(consumer: Consumer<List<EmojiPageModel>>) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val list = mutableListOf<EmojiPageModel>()
|
||||
list += recentEmojiPageModel
|
||||
list += latest.displayPages
|
||||
consumer.accept(list)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +1,66 @@
|
|||
package org.thoughtcrime.securesms.keyboard.emoji
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.EmojiHeader
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.emoji.EmojiCategory
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageCategoryMappingModel.EmojiCategoryMappingModel
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import org.thoughtcrime.securesms.util.MappingModelList
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
|
||||
|
||||
class EmojiKeyboardPageViewModel : ViewModel() {
|
||||
class EmojiKeyboardPageViewModel(repository: EmojiKeyboardPageRepository) : ViewModel() {
|
||||
|
||||
private val internalSelectedKey = DefaultValueLiveData<String>(getStartingTab())
|
||||
|
||||
val selectedKey: LiveData<String>
|
||||
get() = internalSelectedKey
|
||||
|
||||
val categories: LiveData<MappingModelList> = Transformations.map(internalSelectedKey) { selected ->
|
||||
MappingModelList().apply {
|
||||
add(EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel(selected == RecentEmojiPageModel.KEY))
|
||||
val allEmojiModels: MutableLiveData<List<EmojiPageModel>> = MutableLiveData()
|
||||
val pages: LiveData<MappingModelList>
|
||||
val categories: LiveData<MappingModelList>
|
||||
|
||||
EmojiCategory.values().forEach {
|
||||
add(EmojiKeyboardPageCategoryMappingModel.EmojiCategoryMappingModel(it, it.key == selected))
|
||||
init {
|
||||
repository.getEmoji(allEmojiModels::postValue)
|
||||
|
||||
pages = LiveDataUtil.mapAsync(allEmojiModels) { models ->
|
||||
val list = MappingModelList()
|
||||
models.forEach { pageModel ->
|
||||
list += if (RecentEmojiPageModel.KEY == pageModel.key) {
|
||||
EmojiHeader(pageModel.key, R.string.ReactWithAnyEmojiBottomSheetDialogFragment__recently_used)
|
||||
} else {
|
||||
val category = EmojiCategory.forKey(pageModel.key)
|
||||
EmojiHeader(pageModel.key, category.getCategoryLabel())
|
||||
}
|
||||
|
||||
list += pageModel.toMappingModels()
|
||||
}
|
||||
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
val pages: LiveData<MappingModelList> = Transformations.map(categories) { categories ->
|
||||
MappingModelList().apply {
|
||||
categories.forEach {
|
||||
add(getPageForCategory(it as EmojiKeyboardPageCategoryMappingModel))
|
||||
categories = LiveDataUtil.combineLatest(allEmojiModels, internalSelectedKey) { models, selectedKey ->
|
||||
val list = MappingModelList()
|
||||
list += models.map { m ->
|
||||
if (RecentEmojiPageModel.KEY == m.key) {
|
||||
EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel(m.key == selectedKey)
|
||||
} else {
|
||||
val category = EmojiCategory.forKey(m.key)
|
||||
EmojiCategoryMappingModel(category, category.key == selectedKey)
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,4 +91,21 @@ class EmojiKeyboardPageViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(context: Context) : ViewModelProvider.Factory {
|
||||
|
||||
private val repository = EmojiKeyboardPageRepository(context)
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(EmojiKeyboardPageViewModel(repository)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun EmojiPageModel.toMappingModels(): List<MappingModel<*>> {
|
||||
return if (EmojiCategory.EMOTICONS.key == key) {
|
||||
displayEmoji.map { EmojiPageViewGridAdapter.EmojiTextModel(key, it) }
|
||||
} else {
|
||||
displayEmoji.map { EmojiPageViewGridAdapter.EmojiModel(key, it) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ final class ReactWithAnyEmojiRepository {
|
|||
|
||||
emojiPages.addAll(Stream.of(EmojiSource.getLatest().getDisplayPages())
|
||||
.filterNot(p -> p.getIconAttr() == EmojiCategory.EMOTICONS.getIcon())
|
||||
.map(page -> new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(getCategoryLabel(page.getIconAttr()), page))))
|
||||
.map(page -> new ReactWithAnyEmojiPage(Collections.singletonList(new ReactWithAnyEmojiPageBlock(EmojiCategory.getCategoryLabel(page.getIconAttr()), page))))
|
||||
.toList());
|
||||
}
|
||||
|
||||
|
@ -89,29 +89,4 @@ final class ReactWithAnyEmojiRepository {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @StringRes int getCategoryLabel(@AttrRes int iconAttr) {
|
||||
switch (iconAttr) {
|
||||
case R.attr.emoji_category_people:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__smileys_and_people;
|
||||
case R.attr.emoji_category_nature:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__nature;
|
||||
case R.attr.emoji_category_foods:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__food;
|
||||
case R.attr.emoji_category_activity:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__activities;
|
||||
case R.attr.emoji_category_places:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__places;
|
||||
case R.attr.emoji_category_objects:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__objects;
|
||||
case R.attr.emoji_category_symbols:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__symbols;
|
||||
case R.attr.emoji_category_flags:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__flags;
|
||||
case R.attr.emoji_category_emoticons:
|
||||
return R.string.ReactWithAnyEmojiBottomSheetDialogFragment__emoticons;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,22 +19,28 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:click_only="true"
|
||||
app:layout_scrollFlags="scroll|snap"
|
||||
app:search_icon_tint="@color/signal_icon_tint_tab_unselected"
|
||||
app:search_hint="@string/KeyboardPagerFragment_search_emoji"
|
||||
app:show_always="true" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/emoji_pager"
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiPageView
|
||||
android:id="@+id/emoji_page_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingBottom="?actionBarSize"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/emoji_keyboard_bottom_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
|
@ -57,7 +63,7 @@
|
|||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/emoji_categories_recycler"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
|
@ -76,4 +82,20 @@
|
|||
app:tint="@color/icon_tab_selector" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/emoji_keyboard_top_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:background="@drawable/toolbar_shadow"
|
||||
app:app_bar_layout_id="@+id/emoji_keyboard_search_appbar"
|
||||
app:layout_behavior=".keyboard.TopShadowBehavior" />
|
||||
|
||||
<View
|
||||
android:id="@+id/emoji_keyboard_bottom_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:background="@drawable/bottom_toolbar_shadow"
|
||||
app:bottom_bar_id="@+id/emoji_keyboard_bottom_bar"
|
||||
app:layout_behavior=".keyboard.BottomShadowBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -294,4 +294,12 @@
|
|||
<attr name="search_hint" format="string|reference" />
|
||||
<attr name="click_only" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="TopShadowBehavior">
|
||||
<attr name="app_bar_layout_id" format="reference" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="BottomShadowBehavior">
|
||||
<attr name="bottom_bar_id" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue