Add vertical scrolling to Sticker Keyboard.

fork-5.53.8
Cody Henthorne 2021-06-28 17:18:04 -04:00 zatwierdzone przez Greyson Parrelli
rodzic aba5774446
commit d4a3b442f4
13 zmienionych plików z 565 dodań i 82 usunięć

Wyświetl plik

@ -10,6 +10,9 @@
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />

Wyświetl plik

@ -76,6 +76,7 @@ public abstract class Database {
}
protected void notifyStickerPackListeners() {
ApplicationDependencies.getDatabaseObserver().notifyStickerPackObservers();
context.getContentResolver().notifyChange(DatabaseContentProviders.StickerPack.CONTENT_URI, null);
}

Wyświetl plik

@ -30,6 +30,7 @@ public final class DatabaseObserver {
private final Map<UUID, Set<Observer>> paymentObservers;
private final Set<Observer> allPaymentsObservers;
private final Set<Observer> chatColorsObservers;
private final Set<Observer> stickerPackObservers;
public DatabaseObserver(Application application) {
this.application = application;
@ -40,6 +41,7 @@ public final class DatabaseObserver {
this.paymentObservers = new HashMap<>();
this.allPaymentsObservers = new HashSet<>();
this.chatColorsObservers = new HashSet<>();
this.stickerPackObservers = new HashSet<>();
}
public void registerConversationListObserver(@NonNull Observer listener) {
@ -78,6 +80,12 @@ public final class DatabaseObserver {
});
}
public void registerStickerPackObserver(@NonNull Observer listener) {
executor.execute(() -> {
stickerPackObservers.add(listener);
});
}
public void unregisterObserver(@NonNull Observer listener) {
executor.execute(() -> {
conversationListObservers.remove(listener);
@ -85,6 +93,7 @@ public final class DatabaseObserver {
unregisterMapped(verboseConversationObservers, listener);
unregisterMapped(paymentObservers, listener);
chatColorsObservers.remove(listener);
stickerPackObservers.remove(listener);
});
}
@ -160,6 +169,12 @@ public final class DatabaseObserver {
});
}
public void notifyStickerPackObservers() {
executor.execute(() -> {
notifySet(stickerPackObservers);
});
}
private <K> void registerMapped(@NonNull Map<K, Set<Observer>> map, @NonNull K key, @NonNull Observer listener) {
Set<Observer> listeners = map.get(key);

Wyświetl plik

@ -0,0 +1,95 @@
package org.thoughtcrime.securesms.keyboard.sticker
import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.model.StickerRecord
import org.thoughtcrime.securesms.glide.cache.ApngOptions
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.MappingViewHolder
class KeyboardStickerListAdapter(
private val glideRequests: GlideRequests,
private val eventListener: EventListener?,
private val allowApngAnimation: Boolean,
) : MappingAdapter() {
init {
registerFactory(Sticker::class.java, LayoutFactory(::StickerViewHolder, R.layout.sticker_keyboard_page_list_item))
registerFactory(StickerHeader::class.java, LayoutFactory(::StickerHeaderViewHolder, R.layout.sticker_grid_header))
}
data class Sticker(override val packId: String, val stickerRecord: StickerRecord) : MappingModel<Sticker>, HasPackId {
val uri: DecryptableUri
get() = DecryptableUri(stickerRecord.uri)
override fun areItemsTheSame(newItem: Sticker): Boolean {
return packId == newItem.packId && stickerRecord.rowId == newItem.stickerRecord.rowId
}
override fun areContentsTheSame(newItem: Sticker): Boolean {
return areItemsTheSame(newItem)
}
}
private inner class StickerViewHolder(itemView: View) : MappingViewHolder<Sticker>(itemView) {
private val image: ImageView = findViewById(R.id.sticker_keyboard_page_image)
override fun bind(model: Sticker) {
glideRequests.load(model.uri)
.set(ApngOptions.ANIMATE, allowApngAnimation)
.transition(DrawableTransitionOptions.withCrossFade())
.into(image)
if (eventListener != null) {
itemView.setOnClickListener { eventListener.onStickerClicked(model) }
itemView.setOnLongClickListener {
eventListener.onStickerLongClicked(model)
true
}
} else {
itemView.setOnClickListener(null)
itemView.setOnLongClickListener(null)
}
}
}
data class StickerHeader(override val packId: String, private val title: String?, private val titleResource: Int?) : MappingModel<StickerHeader>, HasPackId {
fun getTitle(context: Context): String {
return title ?: context.resources.getString(titleResource ?: R.string.StickerManagementAdapter_untitled)
}
override fun areItemsTheSame(newItem: StickerHeader): Boolean {
return title == newItem.title
}
override fun areContentsTheSame(newItem: StickerHeader): Boolean {
return areItemsTheSame(newItem)
}
}
private class StickerHeaderViewHolder(itemView: View) : MappingViewHolder<StickerHeader>(itemView) {
private val title: TextView = findViewById(R.id.sticker_grid_header_title)
override fun bind(model: StickerHeader) {
title.text = model.getTitle(context)
}
}
interface HasPackId {
val packId: String
}
interface EventListener {
fun onStickerClicked(sticker: Sticker)
fun onStickerLongClicked(sticker: Sticker)
}
}

Wyświetl plik

@ -2,38 +2,84 @@ package org.thoughtcrime.securesms.keyboard.sticker
import android.os.Bundle
import android.view.View
import androidx.annotation.Px
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.recyclerview.widget.RecyclerView.SmoothScroller
import com.google.android.material.appbar.AppBarLayout
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardBottomTabAdapter
import org.thoughtcrime.securesms.components.emoji.MediaKeyboardProvider
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
import org.thoughtcrime.securesms.keyboard.findListener
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider.StickerEventListener
import org.thoughtcrime.securesms.stickers.StickerRolloverTouchListener
import org.thoughtcrime.securesms.stickers.StickerRolloverTouchListener.RolloverStickerRetriever
import org.thoughtcrime.securesms.util.DeviceProperties
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.Throttler
import org.whispersystems.libsignal.util.Pair
import java.util.Optional
import kotlin.math.abs
class StickerKeyboardPageFragment : LoggingFragment(R.layout.keyboard_pager_sticker_page_fragment) {
class StickerKeyboardPageFragment :
LoggingFragment(R.layout.keyboard_pager_sticker_page_fragment),
KeyboardStickerListAdapter.EventListener,
StickerRolloverTouchListener.RolloverEventListener,
RolloverStickerRetriever,
DatabaseObserver.Observer,
View.OnLayoutChangeListener {
private val presenter: StickerPresenter = StickerPresenter()
private lateinit var provider: StickerKeyboardProvider
private lateinit var stickerPager: ViewPager
private lateinit var stickerList: RecyclerView
private lateinit var keyboardStickerListAdapter: KeyboardStickerListAdapter
private lateinit var layoutManager: GridLayoutManager
private lateinit var listTouchListener: StickerRolloverTouchListener
private lateinit var stickerPacksRecycler: RecyclerView
private lateinit var manageStickers: View
private lateinit var tabAdapter: MediaKeyboardBottomTabAdapter
private lateinit var appBarLayout: AppBarLayout
private lateinit var stickerPacksAdapter: StickerPackListAdapter
private lateinit var viewModel: StickerKeyboardPageViewModel
private val packIdSelectionOnScroll: UpdatePackSelectionOnScroll = UpdatePackSelectionOnScroll()
private val observerThrottler: Throttler = Throttler(500)
private val stickerThrottler: Throttler = Throttler(100)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
stickerPager = view.findViewById(R.id.sticker_pager)
manageStickers = view.findViewById(R.id.sticker_manage)
val glideRequests = GlideApp.with(this)
keyboardStickerListAdapter = KeyboardStickerListAdapter(glideRequests, this, DeviceProperties.shouldAllowApngStickerAnimation(requireContext()))
layoutManager = GridLayoutManager(requireContext(), 2).apply {
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
val model: Optional<MappingModel<*>> = keyboardStickerListAdapter.getModel(position)
if (model.isPresent && model.get() is KeyboardStickerListAdapter.StickerHeader) {
return spanCount
}
return 1
}
}
}
listTouchListener = StickerRolloverTouchListener(requireContext(), glideRequests, this, this)
stickerList = view.findViewById(R.id.sticker_keyboard_list)
stickerList.layoutManager = layoutManager
stickerList.adapter = keyboardStickerListAdapter
stickerList.addOnItemTouchListener(listTouchListener)
stickerList.addOnScrollListener(packIdSelectionOnScroll)
stickerPacksRecycler = view.findViewById(R.id.sticker_packs_recycler)
stickerPacksAdapter = StickerPackListAdapter(glideRequests, DeviceProperties.shouldAllowApngStickerAnimation(requireContext()), this::onTabSelected)
stickerPacksRecycler.adapter = stickerPacksAdapter
appBarLayout = view.findViewById(R.id.sticker_keyboard_search_appbar)
view.findViewById<KeyboardPageSearchView>(R.id.sticker_keyboard_search_text).callbacks = object : KeyboardPageSearchView.Callbacks {
override fun onClicked() {
StickerSearchDialogFragment.show(requireActivity().supportFragmentManager)
@ -41,70 +87,139 @@ class StickerKeyboardPageFragment : LoggingFragment(R.layout.keyboard_pager_stic
}
view.findViewById<View>(R.id.sticker_search).setOnClickListener { StickerSearchDialogFragment.show(requireActivity().supportFragmentManager) }
view.findViewById<View>(R.id.sticker_manage).setOnClickListener { findListener<StickerEventListener>()?.onStickerManagementClicked() }
view.findViewById<AppBarLayout>(R.id.sticker_appbar).setExpanded(false)
ApplicationDependencies.getDatabaseObserver().registerStickerPackObserver(this)
view.addOnLayoutChangeListener(this)
}
override fun onDestroyView() {
ApplicationDependencies.getDatabaseObserver().unregisterObserver(this)
requireView().removeOnLayoutChangeListener(this)
super.onDestroyView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(requireActivity()).get(StickerKeyboardPageViewModel::class.java)
viewModel = ViewModelProviders.of(requireActivity(), StickerKeyboardPageViewModel.Factory(requireContext()))
.get(StickerKeyboardPageViewModel::class.java)
tabAdapter = MediaKeyboardBottomTabAdapter(GlideApp.with(this), this::onTabSelected)
stickerPacksRecycler.adapter = tabAdapter
viewModel.stickers.observe(viewLifecycleOwner, keyboardStickerListAdapter::submitList)
viewModel.packs.observe(viewLifecycleOwner, stickerPacksAdapter::submitList)
viewModel.getSelectedPack().observe(viewLifecycleOwner, this::updateCategoryTab)
provider = StickerKeyboardProvider(requireActivity(), findListener() ?: throw AssertionError("No sticker listener"))
provider.requestPresentation(presenter, true)
viewModel.refreshStickers()
}
private fun findListener(): StickerEventListener? {
return parentFragment as? StickerEventListener ?: requireActivity() as? StickerEventListener
private fun onTabSelected(stickerPack: StickerPackListAdapter.StickerPack) {
scrollTo(stickerPack.packRecord.packId)
viewModel.selectPack(stickerPack.packRecord.packId)
}
private fun onTabSelected(index: Int) {
stickerPager.currentItem = index
private fun updateCategoryTab(packId: String) {
stickerPacksRecycler.post {
val index: Int = stickerPacksAdapter.indexOfFirst(StickerPackListAdapter.StickerPack::class.java) { it.packRecord.packId == packId }
if (index != -1) {
stickerPacksRecycler.smoothScrollToPosition(index)
viewModel.selectedTab = index
}
private inner class StickerPresenter : MediaKeyboardProvider.Presenter {
override fun present(
provider: MediaKeyboardProvider,
pagerAdapter: PagerAdapter,
iconProvider: MediaKeyboardProvider.TabIconProvider,
backspaceObserver: MediaKeyboardProvider.BackspaceObserver?,
addObserver: MediaKeyboardProvider.AddObserver?,
searchObserver: MediaKeyboardProvider.SearchObserver?,
startingIndex: Int
) {
if (stickerPager.adapter != pagerAdapter) {
stickerPager.adapter = pagerAdapter
}
stickerPager.currentItem = viewModel.selectedTab
stickerPager.clearOnPageChangeListeners()
stickerPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
tabAdapter.setActivePosition(position)
stickerPacksRecycler.smoothScrollToPosition(position)
provider.setCurrentPosition(position)
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
override fun onPageScrollStateChanged(state: Int) = Unit
})
tabAdapter.setTabIconProvider(iconProvider, pagerAdapter.count)
tabAdapter.setActivePosition(stickerPager.currentItem)
manageStickers.setOnClickListener { addObserver?.onAddClicked() }
}
override fun getCurrentPosition(): Int {
return stickerPager.currentItem
}
override fun requestDismissal() = Unit
override fun isVisible(): Boolean = true
}
}
}
private fun scrollTo(packId: String) {
val index = keyboardStickerListAdapter.indexOfFirst(KeyboardStickerListAdapter.StickerHeader::class.java) { it.packId == packId }
if (index != -1) {
appBarLayout.setExpanded(false, true)
packIdSelectionOnScroll.startAutoScrolling()
smoothScrollToPositionTop(index)
}
}
private fun smoothScrollToPositionTop(position: Int) {
val currentPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
val shortTrip = abs(currentPosition - position) < 40
if (shortTrip) {
val smoothScroller: SmoothScroller = object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}
}
smoothScroller.targetPosition = position
layoutManager.startSmoothScroll(smoothScroller)
} else {
layoutManager.scrollToPositionWithOffset(position, 0)
}
}
override fun onStickerClicked(sticker: KeyboardStickerListAdapter.Sticker) {
stickerThrottler.publish { findListener<StickerEventListener>()?.onStickerSelected(sticker.stickerRecord) }
}
override fun onStickerLongClicked(sticker: KeyboardStickerListAdapter.Sticker) {
listTouchListener.enterHoverMode(stickerList, sticker)
}
override fun getStickerDataFromView(view: View): Pair<Any, String>? {
val position: Int = stickerList.getChildAdapterPosition(view)
val model: Optional<MappingModel<*>> = keyboardStickerListAdapter.getModel(position)
if (model.isPresent && model.get() is KeyboardStickerListAdapter.Sticker) {
val sticker = model.get() as KeyboardStickerListAdapter.Sticker
return Pair(sticker.uri, sticker.stickerRecord.emoji)
}
return null
}
override fun onStickerPopupStarted() = Unit
override fun onStickerPopupEnded() = Unit
override fun onChanged() {
observerThrottler.publish(viewModel::refreshStickers)
}
override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
onScreenWidthChanged(view?.width ?: 0)
}
private fun onScreenWidthChanged(@Px newWidth: Int) {
layoutManager.spanCount = calculateColumnCount(newWidth)
}
private fun calculateColumnCount(@Px screenWidth: Int): Int {
val modifier = resources.getDimensionPixelOffset(R.dimen.sticker_page_item_padding).toFloat()
val divisor = resources.getDimensionPixelOffset(R.dimen.sticker_page_item_divisor).toFloat()
return ((screenWidth - modifier) / divisor).toInt()
}
private inner class UpdatePackSelectionOnScroll : RecyclerView.OnScrollListener() {
private var doneScrolling: Boolean = true
fun startAutoScrolling() {
doneScrolling = false
}
@Override
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.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
}
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val index = layoutManager.findFirstCompletelyVisibleItemPosition()
val item: Optional<MappingModel<*>> = keyboardStickerListAdapter.getModel(index)
if (item.isPresent && item.get() is KeyboardStickerListAdapter.HasPackId) {
viewModel.selectPack((item.get() as KeyboardStickerListAdapter.HasPackId).packId)
}
}
}
}

Wyświetl plik

@ -1,7 +1,66 @@
package org.thoughtcrime.securesms.keyboard.sticker
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.keyboard.sticker.StickerPackListAdapter.StickerPack
import org.thoughtcrime.securesms.util.MappingModelList
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil
class StickerKeyboardPageViewModel : ViewModel() {
var selectedTab: Int = 0
private const val NO_SELECTED_PAGE = "no_selected_page"
class StickerKeyboardPageViewModel(private val repository: StickerKeyboardRepository) : ViewModel() {
private val keyboardStickerPacks: MutableLiveData<List<StickerKeyboardRepository.KeyboardStickerPack>> = MutableLiveData()
private val selectedPack: MutableLiveData<String> = MutableLiveData(NO_SELECTED_PAGE)
val packs: LiveData<List<StickerPack>>
val stickers: LiveData<MappingModelList>
init {
val stickerPacks: LiveData<List<StickerPack>> = LiveDataUtil.mapAsync(keyboardStickerPacks) { packs ->
packs.map { StickerPack(it) }
}
packs = LiveDataUtil.combineLatest(selectedPack, stickerPacks) { selected, packs ->
if (packs.isEmpty()) {
packs
} else {
val actualSelected = if (selected == NO_SELECTED_PAGE) packs[0].packRecord.packId else selected
packs.map { it.copy(selected = it.packRecord.packId == actualSelected) }
}
}
stickers = LiveDataUtil.mapAsync(keyboardStickerPacks) { packs ->
val list = MappingModelList()
packs.forEach { pack ->
list += KeyboardStickerListAdapter.StickerHeader(pack.packId, pack.title, pack.titleResource)
list += pack.stickers.map { KeyboardStickerListAdapter.Sticker(pack.packId, it) }
}
list
}
}
fun getSelectedPack(): LiveData<String> = selectedPack
fun selectPack(packId: String) {
selectedPack.value = packId
}
fun refreshStickers() {
repository.getStickerPacks { keyboardStickerPacks.postValue(it) }
}
class Factory(context: Context) : ViewModelProvider.Factory {
private val repository = StickerKeyboardRepository(DatabaseFactory.getStickerDatabase(context))
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return requireNotNull(modelClass.cast(StickerKeyboardPageViewModel(repository)))
}
}
}

Wyświetl plik

@ -0,0 +1,80 @@
package org.thoughtcrime.securesms.keyboard.sticker
import android.net.Uri
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.StickerDatabase
import org.thoughtcrime.securesms.database.StickerDatabase.StickerPackRecordReader
import org.thoughtcrime.securesms.database.StickerDatabase.StickerRecordReader
import org.thoughtcrime.securesms.database.model.StickerPackRecord
import org.thoughtcrime.securesms.database.model.StickerRecord
import java.util.function.Consumer
private const val RECENT_LIMIT = 24
private const val RECENT_PACK_ID = "RECENT"
class StickerKeyboardRepository(private val stickerDatabase: StickerDatabase) {
fun getStickerPacks(consumer: Consumer<List<KeyboardStickerPack>>) {
SignalExecutors.BOUNDED.execute {
val packs: MutableList<KeyboardStickerPack> = mutableListOf()
StickerPackRecordReader(stickerDatabase.installedStickerPacks).use { reader ->
var pack: StickerPackRecord? = reader.next
while (pack != null) {
packs += KeyboardStickerPack(packId = pack.packId, title = pack.title.orNull(), coverUri = pack.cover.uri)
pack = reader.next
}
}
val fullPacks: MutableList<KeyboardStickerPack> = packs.map { p ->
val stickers: MutableList<StickerRecord> = mutableListOf()
StickerRecordReader(stickerDatabase.getStickersForPack(p.packId)).use { reader ->
var sticker: StickerRecord? = reader.next
while (sticker != null) {
stickers.add(sticker)
sticker = reader.next
}
}
p.copy(stickers = stickers)
}.toMutableList()
val recentStickerPack: KeyboardStickerPack = getRecentStickerPack()
if (recentStickerPack.stickers.isNotEmpty()) {
fullPacks.add(0, recentStickerPack)
}
consumer.accept(fullPacks)
}
}
private fun getRecentStickerPack(): KeyboardStickerPack {
val recentStickers: MutableList<StickerRecord> = mutableListOf()
StickerRecordReader(stickerDatabase.getRecentlyUsedStickers(RECENT_LIMIT)).use { reader ->
var recentSticker: StickerRecord? = reader.next
while (recentSticker != null) {
recentStickers.add(recentSticker)
recentSticker = reader.next
}
}
return KeyboardStickerPack(
packId = RECENT_PACK_ID,
title = null,
titleResource = R.string.StickerKeyboard__recently_used,
coverUri = null,
coverResource = R.drawable.ic_recent_20,
stickers = recentStickers
)
}
data class KeyboardStickerPack(
val packId: String,
val title: String?,
val titleResource: Int? = 0,
val coverUri: Uri?,
val coverResource: Int? = null,
val stickers: List<StickerRecord> = emptyList()
)
}

Wyświetl plik

@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.keyboard.sticker
import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView
import androidx.core.widget.ImageViewCompat
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.glide.cache.ApngOptions
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.MappingAdapter
import org.thoughtcrime.securesms.util.MappingModel
import org.thoughtcrime.securesms.util.MappingViewHolder
class StickerPackListAdapter(private val glideRequests: GlideRequests, private val allowApngAnimation: Boolean, private val onTabSelected: (StickerPack) -> Unit) : MappingAdapter() {
init {
registerFactory(StickerPack::class.java, LayoutFactory(::StickerPackViewHolder, R.layout.keyboard_pager_category_icon))
}
data class StickerPack(val packRecord: StickerKeyboardRepository.KeyboardStickerPack, val selected: Boolean = false) : MappingModel<StickerPack> {
val loadImage: Boolean = packRecord.coverResource == null
val uri: DecryptableUri? = packRecord.coverUri?.let { DecryptableUri(packRecord.coverUri) }
val iconResource: Int = packRecord.coverResource ?: 0
override fun areItemsTheSame(newItem: StickerPack): Boolean {
return packRecord.packId == newItem.packRecord.packId
}
override fun areContentsTheSame(newItem: StickerPack): Boolean {
return areItemsTheSame(newItem) && selected == newItem.selected
}
}
private inner class StickerPackViewHolder(itemView: View) : MappingViewHolder<StickerPack>(itemView) {
private val selected: View = findViewById(R.id.category_icon_selected)
private val icon: ImageView = findViewById(R.id.category_icon)
private val defaultTint: ColorStateList? = ImageViewCompat.getImageTintList(icon)
override fun bind(model: StickerPack) {
itemView.setOnClickListener { onTabSelected(model) }
selected.isSelected = model.selected
if (model.loadImage) {
ImageViewCompat.setImageTintList(icon, null)
icon.alpha = if (model.selected) 1f else 0.5f
glideRequests.load(model.uri)
.set(ApngOptions.ANIMATE, allowApngAnimation)
.into(icon)
} else {
ImageViewCompat.setImageTintList(icon, defaultTint)
icon.setImageResource(model.iconResource)
icon.alpha = 1f
icon.isSelected = model.selected
}
}
}
}

Wyświetl plik

@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyboard.sticker.KeyboardStickerListAdapter;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.Pair;
@ -24,7 +25,7 @@ public class StickerRolloverTouchListener implements RecyclerView.OnItemTouchLis
private WeakReference<View> currentView;
private boolean hoverMode;
StickerRolloverTouchListener(@NonNull Context context,
public StickerRolloverTouchListener(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@NonNull RolloverEventListener eventListener,
@NonNull RolloverStickerRetriever stickerRetriever)
@ -72,25 +73,35 @@ public class StickerRolloverTouchListener implements RecyclerView.OnItemTouchLis
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
void enterHoverMode(@NonNull RecyclerView recyclerView, View targetView) {
public void enterHoverMode(@NonNull RecyclerView recyclerView, View targetView) {
this.hoverMode = true;
showStickerForView(recyclerView, targetView);
}
public void enterHoverMode(@NonNull RecyclerView recyclerView, @NonNull KeyboardStickerListAdapter.Sticker sticker) {
this.hoverMode = true;
showSticker(recyclerView, sticker.getUri(), sticker.getStickerRecord().getEmoji());
}
private void showStickerForView(@NonNull RecyclerView recyclerView, @NonNull View view) {
Pair<Object, String> stickerData = stickerRetriever.getStickerDataFromView(view);
if (stickerData != null) {
showSticker(recyclerView, stickerData.first(), stickerData.second());
}
}
private void showSticker(@NonNull RecyclerView recyclerView, @NonNull Object toLoad, @NonNull String emoji) {
if (!popup.isShowing()) {
popup.showAtLocation(recyclerView, Gravity.NO_GRAVITY, 0, 0);
eventListener.onStickerPopupStarted();
}
popup.presentSticker(stickerData.first(), stickerData.second());
}
popup.presentSticker(toLoad, emoji);
}
public interface RolloverEventListener {
void onStickerPopupStarted();
void onStickerPopupEnded();
}

Wyświetl plik

@ -7,19 +7,21 @@
tools:background="@color/signal_background_secondary">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/sticker_appbar"
android:id="@+id/sticker_keyboard_search_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/signal_background_secondary"
app:elevation="0dp">
app:elevation="0dp"
app:expanded="false"
tools:expanded="true">
<org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
android:id="@+id/sticker_keyboard_search_text"
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_hint="@string/StickerSearchDialogFragment_search_stickers"
@ -27,10 +29,14 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/sticker_pager"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sticker_keyboard_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="?actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout
@ -38,7 +44,7 @@
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="bottom"
android:background="@drawable/keyboard_bottom_bar_background"
android:background="@color/signal_background_secondary"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior">
@ -57,7 +63,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sticker_packs_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"
@ -77,4 +83,18 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@drawable/toolbar_shadow"
app:app_bar_layout_id="@+id/sticker_keyboard_search_appbar"
app:layout_behavior=".keyboard.TopShadowBehavior" />
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@drawable/bottom_toolbar_shadow"
app:bottom_bar_id="@+id/sticker_keyboard_packs_background"
app:layout_behavior=".keyboard.BottomShadowBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Wyświetl plik

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/sticker_grid_header_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:minHeight="48dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
android:textColor="@color/signal_text_primary"
tools:text="@string/ReactWithAnyEmojiBottomSheetDialogFragment__activities" />

Wyświetl plik

@ -1,8 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatImageView xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/sticker_keyboard_page_image"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:background="?selectableItemBackgroundBorderless">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/sticker_keyboard_page_image"
android:layout_width="88dp"
android:layout_height="88dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
tools:srcCompat="@tools:sample/avatars" />
</FrameLayout>

Wyświetl plik

@ -3588,6 +3588,9 @@
<string name="SoundsAndNotificationsSettingsFragment__do_not_notify">Do not notify</string>
<string name="SoundsAndNotificationsSettingsFragment__custom_notifications">Custom notifications</string>
<!-- StickerKeyboard -->
<string name="StickerKeyboard__recently_used">Recently used</string>
<!-- EOF -->
</resources>