Feature: Support filtering custom emoji in reaction view (#836)

* Support filtering custom emojis in reaction keyboard.

* Move creation of EditText to conditional block.

* Clear unused comment

* Update requests variable when publishing filter results so the images displayed will be correct.

* Combine text fields in emoji reaction keyboard, create new initializer for EmojiCategory so it can be copied properly.

* Performance optimization and fixed a typo in filter.

* improve layout

---------

Co-authored-by: sk <sk22@mailbox.org>
This commit is contained in:
Tyler Baker
2023-09-30 13:21:36 -04:00
committed by GitHub
parent cbee0fe72e
commit 95685d4de8
3 changed files with 88 additions and 30 deletions

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.model; package org.joinmastodon.android.model;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class EmojiCategory{ public class EmojiCategory{
@@ -10,4 +11,8 @@ public class EmojiCategory{
this.title=title; this.title=title;
this.emojis=emojis; this.emojis=emojis;
} }
public EmojiCategory(EmojiCategory category){
this.title = category.title;
this.emojis = new ArrayList<>(category.emojis);
}
} }

View File

@@ -17,6 +17,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -139,43 +142,52 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
ll.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); ll.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
FrameLayout bottomPanel=new FrameLayout(activity);
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
if(forReaction){ if(forReaction){
InputMethodManager imm=(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); FrameLayout topPanel=new FrameLayout(activity);
topPanel.setPadding(V.dp(16), V.dp(12), V.dp(16), V.dp(12));
topPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(topPanel, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
// TODO: support filtering custom emoji InputMethodManager imm=(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
EditText input=new EditText(activity); EditText input=new EditText(activity);
input.setHint(R.string.sk_enter_emoji_hint); input.setHint(R.string.sk_enter_emoji_hint);
input.addTextChangedListener(new TextWatcher() { input.addTextChangedListener(new TextWatcher() {
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count){
if (!s.toString().isEmpty()) { // Only check the emoji regex if the text field was empty before
if (emojiRegex.matcher(s.toString()).find()) { if(start == 0){
if(emojiRegex.matcher(s.toString()).find()){
imm.hideSoftInputFromWindow(input.getWindowToken(), 0); imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
listener.onEmojiSelected(s.toString().substring(before)); listener.onEmojiSelected(s.toString().substring(before));
input.getText().clear(); input.getText().clear();
} else {
Toast.makeText(activity, R.string.sk_enter_emoji_toast, Toast.LENGTH_SHORT).show();
input.getText().clear();
} }
} }
for(int i=0; i<adapter.getAdapterCount(); i++){
SingleCategoryAdapter currentAdapter=(SingleCategoryAdapter) adapter.getAdapterAt(i);
currentAdapter.getFilter().filter(s.toString());
}
} }
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void afterTextChanged(Editable s) {} @Override public void afterTextChanged(Editable s) {}
}); });
input.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){
@Override
public void onViewAttachedToWindow(@NonNull View view){}
FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START); @Override
int pad=forReaction ? 0 : V.dp(36 + 16); public void onViewDetachedFromWindow(@NonNull View view){
params.setMargins(pad, V.dp(8), pad, V.dp(8)); input.getText().clear();
bottomPanel.addView(input, params);
} }
});
topPanel.addView(input);
}else{ // in compose fragment
FrameLayout bottomPanel=new FrameLayout(activity);
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
if(!forReaction){
ImageButton hideKeyboard=new ImageButton(activity); ImageButton hideKeyboard=new ImageButton(activity);
hideKeyboard.setImageResource(R.drawable.ic_fluent_keyboard_dock_24_regular); hideKeyboard.setImageResource(R.drawable.ic_fluent_keyboard_dock_24_regular);
hideKeyboard.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant))); hideKeyboard.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant)));
@@ -207,13 +219,16 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
} }
} }
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{ private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter, Filterable{
private final EmojiCategory category; private EmojiCategory category;
private final List<ImageLoaderRequest> requests;
private final EmojiCategory originalCategory;
private List<ImageLoaderRequest> requests;
public SingleCategoryAdapter(EmojiCategory category){ public SingleCategoryAdapter(EmojiCategory category){
super(imgLoader); super(imgLoader);
this.category=category; this.category=category;
this.originalCategory=new EmojiCategory(category);
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList()); requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
} }
@@ -225,17 +240,22 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
@Override @Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
if(category.emojis.size() == 0) {
holder.itemView.setVisibility(View.GONE);
}
if(holder instanceof EmojiViewHolder evh){ if(holder instanceof EmojiViewHolder evh){
evh.bind(category.emojis.get(position-1)); evh.bind(category.emojis.get(position-1));
evh.positionWithinCategory=position-1; evh.positionWithinCategory=position-1;
}else if(holder instanceof SectionHeaderViewHolder shvh){ }else if(holder instanceof SectionHeaderViewHolder shvh){
shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title); shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title);
} }
super.onBindViewHolder(holder, position); super.onBindViewHolder(holder, position);
} }
@Override @Override
public int getItemCount(){ public int getItemCount(){
if(category.emojis.size() == 0) return 0;
return category.emojis.size()+1; return category.emojis.size()+1;
} }
@@ -253,6 +273,39 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
public ImageLoaderRequest getImageRequest(int position, int image){ public ImageLoaderRequest getImageRequest(int position, int image){
return requests.get(position-1); return requests.get(position-1);
} }
@Override
public Filter getFilter(){
return emojiFilter;
}
private final Filter emojiFilter = new Filter(){
@Override
protected FilterResults performFiltering(CharSequence charSequence){
List<Emoji> filteredEmoji=new ArrayList<>();
String search=charSequence.toString().toLowerCase().trim();
if(charSequence==null || charSequence.length()==0){
filteredEmoji.addAll(originalCategory.emojis);
}else{
for(Emoji emoji : originalCategory.emojis){
if(emoji.shortcode.toLowerCase().contains(search)){
filteredEmoji.add(emoji);
}
}
}
FilterResults results=new FilterResults();
results.values=filteredEmoji;
return results;
}
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults){
category.emojis.clear();
category.emojis.addAll((List) filterResults.values);
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
notifyDataSetChanged();
}
};
} }
private class SectionHeaderViewHolder extends BindableViewHolder<String> implements StickyHeadersOverlay.HeaderViewHolder{ private class SectionHeaderViewHolder extends BindableViewHolder<String> implements StickyHeadersOverlay.HeaderViewHolder{

View File

@@ -364,7 +364,7 @@
</plurals> </plurals>
<string name="sk_button_react">React with emoji</string> <string name="sk_button_react">React with emoji</string>
<string name="sk_enter_emoji_toast">Please type an emoji</string> <string name="sk_enter_emoji_toast">Please type an emoji</string>
<string name="sk_enter_emoji_hint">Type to react with an emoji</string> <string name="sk_enter_emoji_hint">Type an emoji or search custom emoji</string>
<string name="sk_mute_label">Duration</string> <string name="sk_mute_label">Duration</string>
<string name="sk_duration_indefinite">Indefinite</string> <string name="sk_duration_indefinite">Indefinite</string>
<string name="sk_duration_minutes_5">5 minutes</string> <string name="sk_duration_minutes_5">5 minutes</string>