Better char counter and custom emoji in compose
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||
private List<EmojiCategory> emojis;
|
||||
private UsableRecyclerView list;
|
||||
private ListImageLoaderWrapper imgLoader;
|
||||
private MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||
private String domain;
|
||||
private int gridGap;
|
||||
private int spanCount=6;
|
||||
private Consumer<Emoji> listener;
|
||||
|
||||
public CustomEmojiPopupKeyboard(Activity activity, List<EmojiCategory> emojis, String domain){
|
||||
super(activity);
|
||||
this.emojis=emojis;
|
||||
this.domain=domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View onCreateView(){
|
||||
GridLayoutManager lm=new GridLayoutManager(activity, spanCount);
|
||||
list=new UsableRecyclerView(activity){
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec){
|
||||
// it's important to do this in onMeasure so the child views will be measured with correct paddings already set
|
||||
spanCount=Math.round(MeasureSpec.getSize(widthSpec)/(float)V.dp(44+20));
|
||||
lm.setSpanCount(spanCount);
|
||||
int pad=V.dp(16);
|
||||
gridGap=(MeasureSpec.getSize(widthSpec)-pad*2-V.dp(44)*spanCount)/(spanCount-1);
|
||||
setPadding(pad, 0, pad-gridGap, 0);
|
||||
invalidateItemDecorations();
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
}
|
||||
};
|
||||
lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){
|
||||
@Override
|
||||
public int getSpanSize(int position){
|
||||
if(adapter.getItemViewType(position)==0)
|
||||
return lm.getSpanCount();
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
list.setLayoutManager(lm);
|
||||
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
|
||||
|
||||
for(EmojiCategory category:emojis)
|
||||
adapter.addAdapter(new SingleCategoryAdapter(category));
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
outRect.right=gridGap;
|
||||
if(view instanceof TextView){ // section header
|
||||
if(parent.getChildAdapterPosition(view)>0)
|
||||
outRect.top=-gridGap; // negate the margin added by the emojis above
|
||||
}else{
|
||||
outRect.bottom=gridGap;
|
||||
}
|
||||
}
|
||||
});
|
||||
list.setBackgroundResource(R.color.gray_100);
|
||||
list.setSelector(null);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setListener(Consumer<Emoji> listener){
|
||||
this.listener=listener;
|
||||
}
|
||||
|
||||
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
private final EmojiCategory category;
|
||||
private final List<ImageLoaderRequest> requests;
|
||||
|
||||
public SingleCategoryAdapter(EmojiCategory category){
|
||||
super(imgLoader);
|
||||
this.category=category;
|
||||
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.url, V.dp(44), V.dp(44))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return viewType==0 ? new SectionHeaderViewHolder() : new EmojiViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
|
||||
if(holder instanceof EmojiViewHolder){
|
||||
((EmojiViewHolder) holder).bind(category.emojis.get(position-1));
|
||||
((EmojiViewHolder) holder).positionWithinCategory=position-1;
|
||||
}else if(holder instanceof SectionHeaderViewHolder){
|
||||
((SectionHeaderViewHolder) holder).bind(TextUtils.isEmpty(category.title) ? domain : category.title);
|
||||
}
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return category.emojis.size()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position==0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return position>0 ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return requests.get(position-1);
|
||||
}
|
||||
}
|
||||
|
||||
private class SectionHeaderViewHolder extends BindableViewHolder<String>{
|
||||
public SectionHeaderViewHolder(){
|
||||
super(activity, R.layout.item_emoji_section, list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(String item){
|
||||
((TextView)itemView).setText(item);
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiViewHolder extends BindableViewHolder<Emoji> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
public int positionWithinCategory;
|
||||
public EmojiViewHolder(){
|
||||
super(new ImageView(activity));
|
||||
ImageView img=(ImageView) itemView;
|
||||
img.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(44)));
|
||||
img.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Emoji item){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
((ImageView)itemView).setImageDrawable(image);
|
||||
if(image instanceof Animatable)
|
||||
((Animatable) image).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
((ImageView)itemView).setImageDrawable(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
listener.accept(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
/**
|
||||
* Created by grishka on 17.08.15.
|
||||
*/
|
||||
public abstract class PopupKeyboard{
|
||||
|
||||
protected View keyboardPopupView;
|
||||
protected Activity activity;
|
||||
private int initialHeight;
|
||||
private int prevWidth;
|
||||
private int keyboardHeight;
|
||||
private boolean needShowOnHide=false;
|
||||
private boolean keyboardWasVisible=false;
|
||||
private OnIconChangeListener iconListener;
|
||||
|
||||
public static final int ICON_HIDDEN=0;
|
||||
public static final int ICON_ARROW=1;
|
||||
public static final int ICON_KEYBOARD=2;
|
||||
|
||||
public PopupKeyboard(Activity activity){
|
||||
this.activity=activity;
|
||||
}
|
||||
|
||||
protected abstract View onCreateView();
|
||||
|
||||
private void ensureView(){
|
||||
if(keyboardPopupView==null){
|
||||
keyboardPopupView=onCreateView();
|
||||
keyboardPopupView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public View getView(){
|
||||
ensureView();
|
||||
return keyboardPopupView;
|
||||
}
|
||||
|
||||
public boolean isVisible(){
|
||||
ensureView();
|
||||
return keyboardPopupView.getVisibility()==View.VISIBLE;
|
||||
}
|
||||
|
||||
public void toggleKeyboardPopup(View textField){
|
||||
ensureView();
|
||||
if(keyboardPopupView.getVisibility()==View.VISIBLE){
|
||||
if(keyboardWasVisible){
|
||||
keyboardWasVisible=false;
|
||||
InputMethodManager imm=(InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(textField, 0);
|
||||
}else{
|
||||
keyboardPopupView.setVisibility(View.GONE);
|
||||
}
|
||||
if(iconListener!=null)
|
||||
iconListener.onIconChanged(ICON_HIDDEN);
|
||||
return;
|
||||
}
|
||||
if(keyboardHeight>0){
|
||||
needShowOnHide=true;
|
||||
keyboardWasVisible=true;
|
||||
InputMethodManager imm=(InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
|
||||
if(iconListener!=null)
|
||||
iconListener.onIconChanged(ICON_KEYBOARD);
|
||||
}else{
|
||||
doShowKeyboardPopup();
|
||||
if(iconListener!=null)
|
||||
iconListener.onIconChanged(ICON_ARROW);
|
||||
}
|
||||
}
|
||||
|
||||
protected Window getWindow(){
|
||||
return activity.getWindow();
|
||||
}
|
||||
|
||||
public void setOnIconChangedListener(OnIconChangeListener l){
|
||||
iconListener=l;
|
||||
}
|
||||
|
||||
public void onContentViewSizeChanged(int w, int h, int oldw, int oldh){
|
||||
if(oldw==0 || w!=prevWidth){
|
||||
initialHeight=h;
|
||||
prevWidth=w;
|
||||
onWidthChanged(w);
|
||||
}
|
||||
if(h>initialHeight){
|
||||
initialHeight=h;
|
||||
}
|
||||
if(initialHeight!=0 && w==oldw){
|
||||
keyboardHeight=initialHeight-h;
|
||||
if(keyboardHeight!=0){
|
||||
DisplayMetrics dm=activity.getResources().getDisplayMetrics();
|
||||
activity.getSharedPreferences("emoji", Context.MODE_PRIVATE).edit().putInt("kb_size"+dm.widthPixels+"_"+dm.heightPixels, keyboardHeight).commit();
|
||||
}
|
||||
if(needShowOnHide && keyboardHeight==0){
|
||||
((View)keyboardPopupView.getParent()).getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
((View)keyboardPopupView.getParent()).getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
doShowKeyboardPopup();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
needShowOnHide=false;
|
||||
}
|
||||
if(keyboardHeight>0 && keyboardPopupView.getVisibility()==View.VISIBLE){
|
||||
if(iconListener!=null)
|
||||
iconListener.onIconChanged(ICON_HIDDEN);
|
||||
((View)keyboardPopupView.getParent()).getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
((View)keyboardPopupView.getParent()).getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
keyboardPopupView.setVisibility(View.GONE);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hide(){
|
||||
ensureView();
|
||||
if(keyboardPopupView.getVisibility()==View.VISIBLE){
|
||||
keyboardPopupView.setVisibility(View.GONE);
|
||||
keyboardWasVisible=false;
|
||||
if(iconListener!=null)
|
||||
iconListener.onIconChanged(ICON_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(){
|
||||
|
||||
}
|
||||
|
||||
protected void onWidthChanged(int w){
|
||||
|
||||
}
|
||||
|
||||
protected boolean needWrapContent(){
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doShowKeyboardPopup(){
|
||||
ensureView();
|
||||
DisplayMetrics dm=activity.getResources().getDisplayMetrics();
|
||||
int height=activity.getSharedPreferences("emoji", Context.MODE_PRIVATE).getInt("kb_size"+dm.widthPixels+"_"+dm.heightPixels, V.dp(200));
|
||||
if(needWrapContent()){
|
||||
keyboardPopupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.AT_MOST | height);
|
||||
height=keyboardPopupView.getMeasuredHeight();
|
||||
}
|
||||
keyboardPopupView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
|
||||
keyboardPopupView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public interface OnIconChangeListener{
|
||||
public void onIconChanged(int icon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class SizeListenerLinearLayout extends LinearLayout{
|
||||
private OnSizeChangedListener sizeListener;
|
||||
|
||||
public SizeListenerLinearLayout(Context context){
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SizeListenerLinearLayout(Context context, @Nullable AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SizeListenerLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh){
|
||||
if(sizeListener!=null)
|
||||
sizeListener.onSizeChanged(w, h, oldw, oldh);
|
||||
}
|
||||
|
||||
public void setSizeListener(OnSizeChangedListener sizeListener){
|
||||
this.sizeListener=sizeListener;
|
||||
}
|
||||
//
|
||||
// @Override
|
||||
// public View findFocus(){
|
||||
// View v=super.findFocus();
|
||||
// Log.w("11", "findFocus() "+v);
|
||||
// return v;
|
||||
// }
|
||||
|
||||
@FunctionalInterface
|
||||
public interface OnSizeChangedListener{
|
||||
void onSizeChanged(int w, int h, int oldw, int oldh);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user