diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 3143f5145..5786595c8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -20,13 +20,16 @@ import android.text.TextWatcher; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.util.Log; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; +import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; @@ -223,7 +226,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ creatingView=true; emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain); - emojiKeyboard.setListener(this::onCustomEmojiClick); + emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){ + @Override + public void onEmojiSelected(Emoji emoji){ + onCustomEmojiClick(emoji); + } + + @Override + public void onBackspace(){ + getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + } + }); View view=inflater.inflate(R.layout.fragment_compose, container, false); mainLayout=view.findViewById(R.id.compose_main_ll); @@ -269,6 +283,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr @Override public void onIconChanged(int icon){ emojiBtn.setSelected(icon!=PopupKeyboard.ICON_HIDDEN); + updateNavigationBarColor(icon!=PopupKeyboard.ICON_HIDDEN); if(autocompleteViewController.getMode()==ComposeAutocompleteViewController.Mode.EMOJIS){ contentView.layout(contentView.getLeft(), contentView.getTop(), contentView.getRight(), contentView.getBottom()); if(icon==PopupKeyboard.ICON_HIDDEN) @@ -281,7 +296,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr contentView=(SizeListenerLinearLayout) view; contentView.addView(emojiKeyboard.getView()); - emojiKeyboard.getView().setElevation(V.dp(2)); spoilerEdit=view.findViewById(R.id.content_warning); spoilerWrap=view.findViewById(R.id.content_warning_wrap); @@ -608,8 +622,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Background, R.attr.colorM3Primary, 0.11f); getToolbar().setBackgroundColor(color); setStatusBarColor(color); - setNavigationBarColor(color); bottomBar.setBackgroundColor(color); + updateNavigationBarColor(emojiKeyboard.isVisible()); + } + + private void updateNavigationBarColor(boolean emojiKeyboardVisible){ + int color=UiUtils.alphaBlendThemeColors(getActivity(), R.attr.colorM3Background, R.attr.colorM3Primary, emojiKeyboardVisible ? 0.08f : 0.11f); + setNavigationBarColor(color); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java b/mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java index 995a1c924..b6ee27989 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java @@ -2,14 +2,19 @@ package org.joinmastodon.android.ui; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.res.TypedArray; +import android.content.res.ColorStateList; import android.graphics.Rect; import android.graphics.drawable.Animatable; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.squareup.otto.Subscribe; @@ -22,7 +27,6 @@ import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.ui.utils.UiUtils; import java.util.List; -import java.util.function.Consumer; import java.util.stream.Collectors; import androidx.annotation.NonNull; @@ -45,9 +49,8 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ private ListImageLoaderWrapper imgLoader; private MergeRecyclerAdapter adapter=new MergeRecyclerAdapter(); private String domain; - private int gridGap; private int spanCount=6; - private Consumer listener; + private Listener listener; public CustomEmojiPopupKeyboard(Activity activity, List emojis, String domain){ super(activity); @@ -62,11 +65,8 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ @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)); + spanCount=Math.round((MeasureSpec.getSize(widthSpec)-V.dp(32-8))/(float)V.dp(48+8)); 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); } @@ -80,6 +80,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ } }); list.setLayoutManager(lm); + list.setPadding(V.dp(16), 0, V.dp(16), 0); imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null); for(EmojiCategory category:emojis) @@ -88,22 +89,52 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ 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 + outRect.left=outRect.right=V.dp(-16); }else{ - outRect.bottom=gridGap; + EmojiViewHolder evh=(EmojiViewHolder) parent.getChildViewHolder(view); + int col=evh.positionWithinCategory%spanCount; + if(colhide()); + bottomPanel.addView(hideKeyboard, new FrameLayout.LayoutParams(V.dp(36), V.dp(36), Gravity.LEFT)); + + ImageButton backspace=new ImageButton(activity); + backspace.setImageResource(R.drawable.ic_backspace_24px); + backspace.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant))); + backspace.setBackgroundResource(R.drawable.bg_round_ripple); + backspace.setOnClickListener(v->listener.onBackspace()); + bottomPanel.addView(backspace, new FrameLayout.LayoutParams(V.dp(36), V.dp(36), Gravity.RIGHT)); + + return ll; } - public void setListener(Consumer listener){ + public void setListener(Listener listener){ this.listener=listener; } @@ -123,7 +154,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ 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()); + requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.url, V.dp(24), V.dp(24))).collect(Collectors.toList()); } @NonNull @@ -134,11 +165,11 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ @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); + if(holder instanceof EmojiViewHolder evh){ + evh.bind(category.emojis.get(position-1)); + evh.positionWithinCategory=position-1; + }else if(holder instanceof SectionHeaderViewHolder shvh){ + shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title); } super.onBindViewHolder(holder, position); } @@ -164,14 +195,24 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ } } - private class SectionHeaderViewHolder extends BindableViewHolder{ + private class SectionHeaderViewHolder extends BindableViewHolder implements StickyHeadersOverlay.HeaderViewHolder{ + private Drawable background; + public SectionHeaderViewHolder(){ super(activity, R.layout.item_emoji_section, list); + background=new ColorDrawable(UiUtils.alphaBlendThemeColors(activity, R.attr.colorM3Surface, R.attr.colorM3Primary, .08f)); + itemView.setBackground(background); } @Override public void onBind(String item){ ((TextView)itemView).setText(item); + setStickyFactor(0); + } + + @Override + public void setStickyFactor(float factor){ + background.setAlpha(Math.round(255*factor)); } } @@ -180,8 +221,11 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ public EmojiViewHolder(){ super(new ImageView(activity)); ImageView img=(ImageView) itemView; - img.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(44))); + img.setLayoutParams(new RecyclerView.LayoutParams(V.dp(48), V.dp(48))); img.setScaleType(ImageView.ScaleType.FIT_CENTER); + int pad=V.dp(12); + img.setPadding(pad, pad, pad, pad); + img.setBackgroundResource(R.drawable.bg_custom_emoji); } @Override @@ -203,7 +247,12 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{ @Override public void onClick(){ - listener.accept(item); + listener.onEmojiSelected(item); } } + + public interface Listener{ + void onEmojiSelected(Emoji emoji); + void onBackspace(); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/StickyHeadersOverlay.java b/mastodon/src/main/java/org/joinmastodon/android/ui/StickyHeadersOverlay.java new file mode 100644 index 000000000..6f287d23f --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/StickyHeadersOverlay.java @@ -0,0 +1,87 @@ +package org.joinmastodon.android.ui; + +import android.content.Context; +import android.view.View; +import android.widget.FrameLayout; + +import java.util.Objects; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +public class StickyHeadersOverlay{ + private static final String TAG="StickyHeadersOverlay"; + + private FrameLayout headerWrapper; + private Context context; + private RecyclerView parent; + private RecyclerView.ViewHolder currentHeaderHolder; + private int headerViewType; + + public StickyHeadersOverlay(Context context, int headerViewType){ + this.context=context; + this.headerViewType=headerViewType; + headerWrapper=new FrameLayout(context); + } + + public void install(RecyclerView parent){ + if(this.parent!=null) + throw new IllegalStateException(); + this.parent=parent; + parent.getViewTreeObserver().addOnPreDrawListener(()->{ + if(parent.getWidth()!=headerWrapper.getWidth() || parent.getHeight()!=headerWrapper.getHeight()){ + headerWrapper.measure(parent.getWidth() | View.MeasureSpec.EXACTLY, parent.getHeight() | View.MeasureSpec.EXACTLY); + headerWrapper.layout(0, 0, parent.getWidth(), parent.getHeight()); + } + return true; + }); + parent.getOverlay().add(headerWrapper); + + parent.addOnScrollListener(new RecyclerView.OnScrollListener(){ + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ + if(currentHeaderHolder==null){ + currentHeaderHolder=parent.getAdapter().createViewHolder(parent, headerViewType); + headerWrapper.addView(currentHeaderHolder.itemView); + } + int firstVisiblePos=parent.getChildAdapterPosition(parent.getChildAt(0)); + RecyclerView.Adapter adapter=Objects.requireNonNull(parent.getAdapter()); + // Go backwards from the first visible position to find the previous header + for(int i=firstVisiblePos;i>=0;i--){ + if(adapter.getItemViewType(i)==headerViewType){ + if(currentHeaderHolder.getAbsoluteAdapterPosition()!=i){ + adapter.bindViewHolder(currentHeaderHolder, i); + } + break; + } + } + if(currentHeaderHolder instanceof HeaderViewHolder hvh){ + hvh.setStickyFactor(firstVisiblePos==0 && parent.getChildAt(0).getTop()==0 ? 0 : 1); + } + // Now go forward and find the next header view to possibly offset the current one + for(int i=firstVisiblePos+1;i + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_primary_alpha8.xml b/mastodon/src/main/res/color/m3_primary_alpha8.xml new file mode 100644 index 000000000..dbd568a8f --- /dev/null +++ b/mastodon/src/main/res/color/m3_primary_alpha8.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_custom_emoji.xml b/mastodon/src/main/res/drawable/bg_custom_emoji.xml new file mode 100644 index 000000000..026d10a93 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_custom_emoji.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_m3_surface1.xml b/mastodon/src/main/res/drawable/bg_m3_surface1.xml new file mode 100644 index 000000000..03721a480 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_m3_surface1.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_m3_surface2.xml b/mastodon/src/main/res/drawable/bg_m3_surface2.xml new file mode 100644 index 000000000..e9c73c376 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_m3_surface2.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/ic_backspace_24px.xml b/mastodon/src/main/res/drawable/ic_backspace_24px.xml new file mode 100644 index 000000000..fd76984d2 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_backspace_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_keyboard_hide_24px.xml b/mastodon/src/main/res/drawable/ic_keyboard_hide_24px.xml new file mode 100644 index 000000000..d9e619add --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_keyboard_hide_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/layout/item_emoji_section.xml b/mastodon/src/main/res/layout/item_emoji_section.xml index c99999753..a5b8c3d50 100644 --- a/mastodon/src/main/res/layout/item_emoji_section.xml +++ b/mastodon/src/main/res/layout/item_emoji_section.xml @@ -2,13 +2,11 @@ \ No newline at end of file