mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 18:30:20 +01:00
Add inline emoji search.
This commit is contained in:
committed by
Greyson Parrelli
parent
ba7319e215
commit
19af68a27c
@@ -103,6 +103,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
@@ -137,6 +138,8 @@ import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
|
||||
import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
|
||||
import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem;
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu;
|
||||
import org.thoughtcrime.securesms.components.reminder.BubbleOptOutReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.GroupsV1MigrationSuggestionsReminder;
|
||||
@@ -163,6 +166,11 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationM
|
||||
import org.thoughtcrime.securesms.conversation.drafts.DraftRepository;
|
||||
import org.thoughtcrime.securesms.conversation.drafts.DraftViewModel;
|
||||
import org.thoughtcrime.securesms.conversation.ui.groupcall.GroupCallViewModel;
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQuery;
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryChangedListener;
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResultsController;
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryResultsPopup;
|
||||
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryViewModel;
|
||||
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel;
|
||||
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
@@ -328,8 +336,11 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
|
||||
/**
|
||||
@@ -443,12 +454,14 @@ public class ConversationParentFragment extends Fragment
|
||||
private InviteReminderModel inviteReminderModel;
|
||||
private ConversationGroupViewModel groupViewModel;
|
||||
private MentionsPickerViewModel mentionsViewModel;
|
||||
private InlineQueryViewModel inlineQueryViewModel;
|
||||
private GroupCallViewModel groupCallViewModel;
|
||||
private VoiceRecorderWakeLock voiceRecorderWakeLock;
|
||||
private DraftViewModel draftViewModel;
|
||||
private VoiceNoteMediaController voiceNoteMediaController;
|
||||
private VoiceNotePlayerView voiceNotePlayerView;
|
||||
private Material3OnScrollHelper material3OnScrollHelper;
|
||||
private InlineQueryResultsController inlineQueryResultsController;
|
||||
|
||||
private LiveRecipient recipient;
|
||||
private long threadId;
|
||||
@@ -644,6 +657,10 @@ public class ConversationParentFragment extends Fragment
|
||||
if (reactionDelegate.isShowing()) {
|
||||
reactionDelegate.hide();
|
||||
}
|
||||
|
||||
if (inlineQueryResultsController != null) {
|
||||
inlineQueryResultsController.onOrientationChange(newConfig.orientation == ORIENTATION_LANDSCAPE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2330,7 +2347,17 @@ public class ConversationParentFragment extends Fragment
|
||||
}
|
||||
|
||||
private void initializeMentionsViewModel() {
|
||||
mentionsViewModel = new ViewModelProvider(requireActivity(), new MentionsPickerViewModel.Factory()).get(MentionsPickerViewModel.class);
|
||||
mentionsViewModel = new ViewModelProvider(requireActivity(), new MentionsPickerViewModel.Factory()).get(MentionsPickerViewModel.class);
|
||||
inlineQueryViewModel = new ViewModelProvider(requireActivity()).get(InlineQueryViewModel.class);
|
||||
|
||||
inlineQueryResultsController = new InlineQueryResultsController(
|
||||
requireContext(),
|
||||
inlineQueryViewModel,
|
||||
inputPanel,
|
||||
(ViewGroup) requireView(),
|
||||
composeText,
|
||||
getViewLifecycleOwner()
|
||||
);
|
||||
|
||||
recipient.observe(getViewLifecycleOwner(), r -> {
|
||||
if (r.isPushV2Group() && !mentionsSuggestions.resolved()) {
|
||||
@@ -2339,12 +2366,29 @@ public class ConversationParentFragment extends Fragment
|
||||
mentionsViewModel.onRecipientChange(r);
|
||||
});
|
||||
|
||||
composeText.setMentionQueryChangedListener(query -> {
|
||||
if (getRecipient().isPushV2Group() && getRecipient().isActiveGroup()) {
|
||||
if (!mentionsSuggestions.resolved()) {
|
||||
mentionsSuggestions.get();
|
||||
composeText.setInlineQueryChangedListener(new InlineQueryChangedListener() {
|
||||
@Override
|
||||
public void onQueryChanged(@NonNull InlineQuery inlineQuery) {
|
||||
if (inlineQuery instanceof InlineQuery.Mention) {
|
||||
if (getRecipient().isPushV2Group() && getRecipient().isActiveGroup()) {
|
||||
if (!mentionsSuggestions.resolved()) {
|
||||
mentionsSuggestions.get();
|
||||
}
|
||||
mentionsViewModel.onQueryChange(inlineQuery.getQuery());
|
||||
}
|
||||
inlineQueryViewModel.onQueryChange(inlineQuery);
|
||||
} else if (inlineQuery instanceof InlineQuery.Emoji) {
|
||||
inlineQueryViewModel.onQueryChange(inlineQuery);
|
||||
mentionsViewModel.onQueryChange(null);
|
||||
} else if (inlineQuery instanceof InlineQuery.NoQuery) {
|
||||
mentionsViewModel.onQueryChange(null);
|
||||
inlineQueryViewModel.onQueryChange(inlineQuery);
|
||||
}
|
||||
mentionsViewModel.onQueryChange(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearQuery() {
|
||||
onQueryChanged(InlineQuery.NoQuery.INSTANCE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2365,6 +2409,15 @@ public class ConversationParentFragment extends Fragment
|
||||
mentionsViewModel.getSelectedRecipient().observe(getViewLifecycleOwner(), recipient -> {
|
||||
composeText.replaceTextWithMention(recipient.getDisplayName(requireContext()), recipient.getId());
|
||||
});
|
||||
|
||||
Disposable disposable = inlineQueryViewModel
|
||||
.getSelection()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(r -> {
|
||||
composeText.replaceText(r);
|
||||
});
|
||||
|
||||
disposables.add(disposable);
|
||||
}
|
||||
|
||||
public void initializeGroupCallViewModel() {
|
||||
@@ -3776,6 +3829,7 @@ public class ConversationParentFragment extends Fragment
|
||||
reactionDelegate.setOnActionSelectedListener(onActionSelectedListener);
|
||||
reactionDelegate.setOnHideListener(onHideListener);
|
||||
reactionDelegate.show(requireActivity(), recipient.get(), conversationMessage, groupViewModel.isNonAdminInAnnouncementGroup(), selectedConversationModel);
|
||||
composeText.clearFocus();
|
||||
if (attachmentKeyboardStub.resolved()) {
|
||||
attachmentKeyboardStub.get().hide(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
/**
|
||||
* Represents an inline query via compose text.
|
||||
*/
|
||||
sealed class InlineQuery(val query: String) {
|
||||
object NoQuery : InlineQuery("")
|
||||
class Emoji(query: String, val keywordSearch: Boolean) : InlineQuery(query.replace('_', ' '))
|
||||
class Mention(query: String) : InlineQuery(query)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
||||
class InlineQueryAdapter(listener: (AnyMappingModel) -> Unit) : MappingAdapter() {
|
||||
init {
|
||||
registerFactory(InlineQueryEmojiResult.Model::class.java, { InlineQueryEmojiResult.ViewHolder(it, listener) }, R.layout.inline_query_emoji_result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
/**
|
||||
* Called when a query changes.
|
||||
*/
|
||||
interface InlineQueryChangedListener {
|
||||
fun onQueryChanged(inlineQuery: InlineQuery)
|
||||
fun clearQuery() = onQueryChanged(InlineQuery.NoQuery)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.view.View
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiImageView
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
|
||||
|
||||
/**
|
||||
* Used to render inline emoji search results in a [org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter]
|
||||
*/
|
||||
object InlineQueryEmojiResult {
|
||||
|
||||
class Model(val canonicalEmoji: String, val preferredEmoji: String, val keywordSearch: Boolean) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean {
|
||||
return canonicalEmoji == newItem.canonicalEmoji
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: Model): Boolean {
|
||||
return preferredEmoji == newItem.preferredEmoji
|
||||
}
|
||||
}
|
||||
|
||||
class ViewHolder(itemView: View, private val listener: (AnyMappingModel) -> Unit) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val emoji: EmojiImageView = findViewById(R.id.inline_query_emoji_image)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
itemView.setOnClickListener { listener(model) }
|
||||
emoji.setImageEmoji(model.preferredEmoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* Encapsulate how to replace a query with a user selected result.
|
||||
*/
|
||||
sealed class InlineQueryReplacement(@get:JvmName("isKeywordSearch") val keywordSearch: Boolean = false) {
|
||||
abstract fun toCharSequence(context: Context): CharSequence
|
||||
|
||||
class Emoji(private val emoji: String, keywordSearch: Boolean) : InlineQueryReplacement(keywordSearch) {
|
||||
override fun toCharSequence(context: Context): CharSequence {
|
||||
return "$emoji "
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.components.ComposeText
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
import org.thoughtcrime.securesms.util.doOnEachLayout
|
||||
|
||||
/**
|
||||
* Controller for inline search results.
|
||||
*/
|
||||
class InlineQueryResultsController(
|
||||
private val context: Context,
|
||||
private val viewModel: InlineQueryViewModel,
|
||||
private val anchor: View,
|
||||
private val container: ViewGroup,
|
||||
editText: ComposeText,
|
||||
lifecycleOwner: LifecycleOwner
|
||||
) : InlineQueryResultsPopup.Callback {
|
||||
|
||||
private val lifecycleDisposable: LifecycleDisposable = LifecycleDisposable()
|
||||
private var popup: InlineQueryResultsPopup? = null
|
||||
private var previousResults: List<AnyMappingModel>? = null
|
||||
private var canShow: Boolean = false
|
||||
|
||||
init {
|
||||
lifecycleDisposable.bindTo(lifecycleOwner)
|
||||
|
||||
lifecycleDisposable += viewModel.results
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeBy { updateList(it) }
|
||||
|
||||
lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
|
||||
editText.addOnFocusChangeListener { _, hasFocus ->
|
||||
canShow = hasFocus
|
||||
updateList(previousResults ?: emptyList())
|
||||
}
|
||||
|
||||
anchor.doOnEachLayout { popup?.updateWithAnchor() }
|
||||
}
|
||||
|
||||
override fun onSelection(model: AnyMappingModel) {
|
||||
viewModel.onSelection(model)
|
||||
}
|
||||
|
||||
override fun onDismiss() {
|
||||
popup = null
|
||||
}
|
||||
|
||||
fun onOrientationChange(isLandscape: Boolean) {
|
||||
if (isLandscape) {
|
||||
dismiss()
|
||||
} else {
|
||||
updateList(previousResults ?: emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateList(results: List<AnyMappingModel>) {
|
||||
previousResults = results
|
||||
if (results.isEmpty() || !canShow) {
|
||||
dismiss()
|
||||
} else if (popup != null) {
|
||||
popup?.setResults(results)
|
||||
} else {
|
||||
popup = InlineQueryResultsPopup(
|
||||
anchor = anchor,
|
||||
container = container,
|
||||
results = results,
|
||||
baseOffsetX = DimensionUnit.DP.toPixels(16f).toInt(),
|
||||
callback = this
|
||||
).show()
|
||||
VibrateUtil.vibrateTick(context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismiss() {
|
||||
popup?.dismiss()
|
||||
popup = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupWindow
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
|
||||
class InlineQueryResultsPopup(
|
||||
val anchor: View,
|
||||
val container: ViewGroup,
|
||||
results: List<AnyMappingModel>,
|
||||
val baseOffsetX: Int = 0,
|
||||
val baseOffsetY: Int = 0,
|
||||
var callback: Callback?
|
||||
) : PopupWindow(
|
||||
LayoutInflater.from(anchor.context).inflate(R.layout.inline_query_results_popup, null),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
false
|
||||
) {
|
||||
|
||||
private val context: Context = anchor.context
|
||||
|
||||
private val list: RecyclerView = contentView.findViewById(R.id.inline_query_results_list)
|
||||
private val adapter: MappingAdapter
|
||||
|
||||
init {
|
||||
setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.signal_context_menu_background))
|
||||
inputMethodMode = INPUT_METHOD_NOT_NEEDED
|
||||
|
||||
setOnDismissListener {
|
||||
callback?.onDismiss()
|
||||
callback = null
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
elevation = 20f
|
||||
}
|
||||
|
||||
adapter = InlineQueryAdapter { m -> callback?.onSelection(m) }
|
||||
list.adapter = adapter
|
||||
list.itemAnimator = null
|
||||
|
||||
setResults(results)
|
||||
}
|
||||
|
||||
fun setResults(results: List<AnyMappingModel>) {
|
||||
adapter.submitList(results) { list.scrollToPosition(0) }
|
||||
}
|
||||
|
||||
fun show(): InlineQueryResultsPopup {
|
||||
if (anchor.width == 0 || anchor.height == 0) {
|
||||
anchor.post(this::show)
|
||||
return this
|
||||
}
|
||||
|
||||
val (offsetX, offsetY) = calculateOffsets()
|
||||
|
||||
showAsDropDown(anchor, offsetX, offsetY)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun updateWithAnchor() {
|
||||
val (offsetX, offsetY) = calculateOffsets()
|
||||
update(anchor, offsetX, offsetY, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
private fun calculateOffsets(): Pair<Int, Int> {
|
||||
contentView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
)
|
||||
|
||||
val anchorRect = Rect(anchor.left, anchor.top, anchor.right, anchor.bottom).also {
|
||||
if (anchor.parent != container) {
|
||||
container.offsetDescendantRectToMyCoords(anchor, it)
|
||||
}
|
||||
}
|
||||
|
||||
val menuBottomBound = anchorRect.bottom + contentView.measuredHeight + baseOffsetY
|
||||
val menuTopBound = anchorRect.top - contentView.measuredHeight - baseOffsetY
|
||||
|
||||
val screenBottomBound = container.height
|
||||
val screenTopBound = container.y
|
||||
|
||||
val offsetY: Int = when {
|
||||
menuTopBound > screenTopBound -> -(anchorRect.height() + contentView.measuredHeight + baseOffsetY)
|
||||
menuBottomBound < screenBottomBound -> baseOffsetY
|
||||
menuTopBound > screenTopBound -> -(anchorRect.height() + contentView.measuredHeight + baseOffsetY)
|
||||
else -> -((anchorRect.height() / 2) + (contentView.measuredHeight / 2) + baseOffsetY)
|
||||
}
|
||||
|
||||
val offsetX: Int = if (ViewUtil.isLtr(context)) {
|
||||
baseOffsetX
|
||||
} else {
|
||||
-(baseOffsetX + contentView.measuredWidth)
|
||||
}
|
||||
|
||||
return offsetX to offsetY
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onSelection(model: AnyMappingModel)
|
||||
fun onDismiss()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.thoughtcrime.securesms.conversation.ui.inlinequery
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchRepository
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.AnyMappingModel
|
||||
|
||||
/**
|
||||
* Activity (at least) scope view model for managing inline queries. The view model needs to be larger scope so it can
|
||||
* be shared between the fragment requesting the search and the instace of [InlineQueryResultsFragment] used for displaying
|
||||
* the results.
|
||||
*/
|
||||
class InlineQueryViewModel(private val emojiSearchRepository: EmojiSearchRepository = EmojiSearchRepository(ApplicationDependencies.getApplication())) : ViewModel() {
|
||||
|
||||
private val querySubject: PublishSubject<InlineQuery> = PublishSubject.create()
|
||||
private val selectionSubject: PublishSubject<InlineQueryReplacement> = PublishSubject.create()
|
||||
|
||||
val results: Observable<List<AnyMappingModel>>
|
||||
val selection: Observable<InlineQueryReplacement> = selectionSubject
|
||||
|
||||
init {
|
||||
results = querySubject.switchMap { query ->
|
||||
when (query) {
|
||||
is InlineQuery.Emoji -> queryEmoji(query)
|
||||
is InlineQuery.Mention -> Observable.just(emptyList())
|
||||
InlineQuery.NoQuery -> Observable.just(emptyList())
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun onQueryChange(inlineQuery: InlineQuery) {
|
||||
querySubject.onNext(inlineQuery)
|
||||
}
|
||||
|
||||
private fun queryEmoji(query: InlineQuery.Emoji): Observable<List<AnyMappingModel>> {
|
||||
return emojiSearchRepository
|
||||
.submitQuery(query.query)
|
||||
.map { r -> toMappingModels(r, query.keywordSearch) }
|
||||
.toObservable()
|
||||
}
|
||||
|
||||
fun onSelection(model: AnyMappingModel) {
|
||||
when (model) {
|
||||
is InlineQueryEmojiResult.Model -> {
|
||||
selectionSubject.onNext(InlineQueryReplacement.Emoji(model.preferredEmoji, model.keywordSearch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun toMappingModels(emojiWithLabels: List<String>, keywordSearch: Boolean): List<AnyMappingModel> {
|
||||
val emojiValues = SignalStore.emojiValues()
|
||||
return emojiWithLabels
|
||||
.distinct()
|
||||
.map { emoji ->
|
||||
InlineQueryEmojiResult.Model(
|
||||
canonicalEmoji = emoji,
|
||||
preferredEmoji = emojiValues.getPreferredVariation(emoji),
|
||||
keywordSearch = keywordSearch
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ public class MentionsPickerFragment extends LoggingFragment {
|
||||
|
||||
private MentionsPickerAdapter adapter;
|
||||
private RecyclerView list;
|
||||
private View topDivider;
|
||||
private View bottomDivider;
|
||||
private BottomSheetBehavior<View> behavior;
|
||||
private MentionsPickerViewModel viewModel;
|
||||
private final Runnable lockSheetAfterListUpdate = () -> behavior.setHideable(false);
|
||||
@@ -40,8 +38,6 @@ public class MentionsPickerFragment extends LoggingFragment {
|
||||
View view = inflater.inflate(R.layout.mentions_picker_fragment, container, false);
|
||||
|
||||
list = view.findViewById(R.id.mentions_picker_list);
|
||||
topDivider = view.findViewById(R.id.mentions_picker_top_divider);
|
||||
bottomDivider = view.findViewById(R.id.mentions_picker_bottom_divider);
|
||||
behavior = BottomSheetBehavior.from(view.findViewById(R.id.mentions_picker_bottom_sheet));
|
||||
|
||||
initializeBehavior();
|
||||
@@ -74,15 +70,12 @@ public class MentionsPickerFragment extends LoggingFragment {
|
||||
public void onStateChanged(@NonNull View bottomSheet, int newState) {
|
||||
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
||||
adapter.submitList(Collections.emptyList());
|
||||
showDividers(false);
|
||||
} else {
|
||||
showDividers(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
|
||||
showDividers(Float.isNaN(slideOffset) || slideOffset > -0.8f);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -116,16 +109,10 @@ public class MentionsPickerFragment extends LoggingFragment {
|
||||
list.scrollToPosition(0);
|
||||
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
handler.post(lockSheetAfterListUpdate);
|
||||
showDividers(true);
|
||||
} else {
|
||||
handler.removeCallbacks(lockSheetAfterListUpdate);
|
||||
behavior.setHideable(true);
|
||||
behavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
private void showDividers(boolean showDividers) {
|
||||
topDivider.setVisibility(showDividers ? View.VISIBLE : View.GONE);
|
||||
bottomDivider.setVisibility(showDividers ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user