Add Emoji Search, Sticker Search, and GIF Keyboard.

Co-authored-by: Alex Hart <alex@signal.org>
Co-authored-by: Cody Henthorne <cody@signal.org>
Co-authored-by: ⁨Greyson Parrelli<greyson@signal.org>
This commit is contained in:
Android Team
2021-05-26 10:47:14 -03:00
committed by Cody Henthorne
parent 66c3b1388a
commit 08e86b8c82
119 changed files with 3545 additions and 721 deletions

View File

@@ -26,8 +26,8 @@ public class InputAwareLayout extends KeyboardAwareLinearLayout implements OnKey
addOnKeyboardShownListener(this);
}
@Override public void onKeyboardShown() {
hideAttachedInput(true);
@Override
public void onKeyboardShown() {
}
public void show(@NonNull final EditText imeTarget, @NonNull final InputView input) {

View File

@@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.conversation.ConversationStickerSuggestionAdapter;
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.database.model.StickerRecord;
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
@@ -279,8 +280,8 @@ public class InputPanel extends LinearLayout
mediaKeyboard.setVisibility(show ? View.VISIBLE : GONE);
}
public void setMediaKeyboardToggleMode(boolean isSticker) {
mediaKeyboard.setStickerMode(isSticker);
public void setMediaKeyboardToggleMode(@NonNull KeyboardPage page) {
mediaKeyboard.setStickerMode(page);
}
public boolean isStickerMode() {
@@ -291,6 +292,10 @@ public class InputPanel extends LinearLayout
return mediaKeyboard;
}
public MediaKeyboard.MediaKeyboardListener getMediaKeyboardListener() {
return mediaKeyboard;
}
public void setWallpaperEnabled(boolean enabled) {
if (enabled) {
setBackground(new ColorDrawable(getContext().getResources().getColor(R.color.wallpaper_compose_background)));

View File

@@ -23,6 +23,8 @@ import java.util.List;
/**
* A provider to select emoji in the {@link org.thoughtcrime.securesms.components.emoji.MediaKeyboard}.
*
* TODO [alex] -- Are we still using any of this?
*/
public class EmojiKeyboardProvider implements MediaKeyboardProvider,
MediaKeyboardProvider.TabIconProvider,
@@ -31,6 +33,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
{
private static final KeyEvent DELETE_KEY_EVENT = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
// TODO [alex] -- We are using this.
public static final String RECENT_STORAGE_KEY = "pref_recent_emoji2";
private final Context context;
@@ -146,7 +149,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
@Override
public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) {
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true);
EmojiPageView page = new EmojiPageView(context, emojiSelectionListener, variationSelectorListener, true, null);
page.setModel(pages.get(position));
container.addView(page);
return page;

View File

@@ -6,58 +6,111 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.components.emoji.EmojiPageViewGridAdapter.VariationSelectorListener;
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
import org.thoughtcrime.securesms.util.MappingModelList;
public class EmojiPageView extends FrameLayout implements VariationSelectorListener {
private static final String TAG = Log.tag(EmojiPageView.class);
private EmojiPageModel model;
private EmojiPageViewGridAdapter adapter;
private AdapterFactory adapterFactory;
private RecyclerView recyclerView;
private GridLayoutManager layoutManager;
private RecyclerView.LayoutManager layoutManager;
private RecyclerView.OnItemTouchListener scrollDisabler;
private VariationSelectorListener variationSelectorListener;
private EmojiVariationSelectorPopup popup;
private boolean searchEnabled;
private SpanSizeLookup spanSizeLookup;
public EmojiPageView(@NonNull Context context,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
boolean allowVariations,
@Nullable KeyboardPageSearchView.Callbacks searchCallbacks)
{
this(context, emojiSelectionListener, variationSelectorListener, allowVariations, searchCallbacks, new GridLayoutManager(context, 8), R.layout.emoji_display_item);
}
public EmojiPageView(@NonNull Context context,
@NonNull EmojiEventListener emojiSelectionListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations,
@Nullable KeyboardPageSearchView.Callbacks searchCallbacks,
@NonNull RecyclerView.LayoutManager layoutManager,
@LayoutRes int displayItemLayoutResId)
{
super(context);
final View view = LayoutInflater.from(getContext()).inflate(R.layout.emoji_grid_layout, this, true);
this.variationSelectorListener = variationSelectorListener;
recyclerView = view.findViewById(R.id.emoji);
layoutManager = new GridLayoutManager(context, 8);
scrollDisabler = new ScrollDisabler();
popup = new EmojiVariationSelectorPopup(context, emojiSelectionListener);
adapter = new EmojiPageViewGridAdapter(popup,
emojiSelectionListener,
this,
allowVariations);
this.recyclerView = view.findViewById(R.id.emoji);
this.layoutManager = layoutManager;
this.scrollDisabler = new ScrollDisabler();
this.popup = new EmojiVariationSelectorPopup(context, emojiSelectionListener);
this.adapterFactory = () -> new EmojiPageViewGridAdapter(popup,
emojiSelectionListener,
this,
allowVariations,
displayItemLayoutResId,
searchCallbacks);
if (layoutManager instanceof GridLayoutManager) {
spanSizeLookup = new SpanSizeLookup();
((GridLayoutManager) layoutManager).setSpanSizeLookup(spanSizeLookup);
}
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setItemAnimator(null);
}
public void onSelected() {
if (model.isDynamic() && adapter != null) {
adapter.notifyDataSetChanged();
if (model.isDynamic() && recyclerView.getAdapter() != null) {
recyclerView.getAdapter().notifyDataSetChanged();
}
}
public void setModel(EmojiPageModel model) {
public void setModel(@Nullable EmojiPageModel model) {
this.model = model;
adapter.setEmoji(model.getDisplayEmoji());
EmojiPageViewGridAdapter adapter = adapterFactory.create();
recyclerView.setAdapter(adapter);
adapter.submitList(getMappingModelList());
}
public void bindSearchableAdapter(@Nullable EmojiPageModel model) {
this.searchEnabled = true;
this.model = model;
EmojiPageViewGridAdapter adapter = adapterFactory.create();
recyclerView.setAdapter(adapter);
adapter.submitList(getMappingModelList(), () -> layoutManager.scrollToPosition(1));
}
private @NonNull MappingModelList getMappingModelList() {
MappingModelList mappingModels = new MappingModelList();
if (searchEnabled) {
mappingModels.add(new EmojiPageViewGridAdapter.SearchModel());
}
if (model != null) {
mappingModels.addAll(Stream.of(model.getDisplayEmoji()).map(EmojiPageViewGridAdapter.EmojiModel::new).toList());
}
return mappingModels;
}
@Override
@@ -69,8 +122,13 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
layoutManager.setSpanCount(Math.max(w / idealWidth, 1));
if (layoutManager instanceof GridLayoutManager) {
int idealWidth = getContext().getResources().getDimensionPixelOffset(R.dimen.emoji_drawer_item_width);
int spanCount = Math.max(w / idealWidth, 1);
spanSizeLookup.setSpansPerRow(spanCount);
((GridLayoutManager) layoutManager).setSpanCount(spanCount);
}
}
@Override
@@ -102,4 +160,22 @@ public class EmojiPageView extends FrameLayout implements VariationSelectorListe
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) { }
}
private class SpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
private int spansPerRow;
public void setSpansPerRow(int spansPerRow) {
this.spansPerRow = spansPerRow;
}
@Override
public int getSpanSize(int position) {
return position == 0 && searchEnabled ? spansPerRow : 1;
}
}
private interface AdapterFactory {
EmojiPageViewGridAdapter create();
}
}

View File

@@ -1,95 +1,42 @@
package org.thoughtcrime.securesms.components.emoji;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupWindow;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider.EmojiEventListener;
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView;
import org.thoughtcrime.securesms.util.MappingAdapter;
import org.thoughtcrime.securesms.util.MappingModel;
import org.thoughtcrime.securesms.util.MappingViewHolder;
import java.util.ArrayList;
import java.util.List;
public class EmojiPageViewGridAdapter extends MappingAdapter implements PopupWindow.OnDismissListener {
public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageViewGridAdapter.EmojiViewHolder> implements PopupWindow.OnDismissListener {
private final List<Emoji> emojiList;
private final EmojiVariationSelectorPopup popup;
private final VariationSelectorListener variationSelectorListener;
private final EmojiEventListener emojiEventListener;
private final boolean allowVariations;
public EmojiPageViewGridAdapter(@NonNull EmojiVariationSelectorPopup popup,
@NonNull EmojiEventListener emojiEventListener,
@NonNull VariationSelectorListener variationSelectorListener,
boolean allowVariations)
boolean allowVariations,
@LayoutRes int displayItemLayoutResId,
@Nullable KeyboardPageSearchView.Callbacks callbacks)
{
this.emojiList = new ArrayList<>();
this.popup = popup;
this.emojiEventListener = emojiEventListener;
this.variationSelectorListener = variationSelectorListener;
this.allowVariations = allowVariations;
popup.setOnDismissListener(this);
}
@NonNull
@Override
public EmojiViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new EmojiViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.emoji_display_item, viewGroup, false));
}
@Override
public void onBindViewHolder(@NonNull EmojiViewHolder viewHolder, int i) {
Emoji emoji = emojiList.get(i);
final Drawable drawable = EmojiProvider.getEmojiDrawable(viewHolder.imageView.getContext(), emoji.getValue());
if (drawable != null) {
viewHolder.textView.setVisibility(View.GONE);
viewHolder.imageView.setVisibility(View.VISIBLE);
viewHolder.imageView.setImageDrawable(drawable);
} else {
viewHolder.textView.setVisibility(View.VISIBLE);
viewHolder.imageView.setVisibility(View.GONE);
viewHolder.textView.setEmoji(emoji.getValue());
}
viewHolder.itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(emoji.getValue());
});
if (allowVariations && emoji.getVariations().size() > 1) {
viewHolder.itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(emoji.getVariations());
popup.showAsDropDown(viewHolder.itemView, 0, -(2 * viewHolder.itemView.getHeight()));
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
viewHolder.hintCorner.setVisibility(View.VISIBLE);
} else {
viewHolder.itemView.setOnLongClickListener(null);
viewHolder.hintCorner.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return emojiList.size();
}
public void setEmoji(@NonNull List<Emoji> emojiList) {
this.emojiList.clear();
this.emojiList.addAll(emojiList);
notifyDataSetChanged();
registerFactory(SearchModel.class, new LayoutFactory<>(v -> {
((KeyboardPageSearchView) v).setCallbacks(callbacks);
return new SearchViewHolder(v);
}, R.layout.emoji_page_view_search));
registerFactory(EmojiModel.class, new LayoutFactory<>(v -> new EmojiViewHolder(v, emojiEventListener, variationSelectorListener, popup, allowVariations), displayItemLayoutResId));
}
@Override
@@ -97,18 +44,110 @@ public class EmojiPageViewGridAdapter extends RecyclerView.Adapter<EmojiPageView
variationSelectorListener.onVariationSelectorStateChanged(false);
}
static class EmojiViewHolder extends RecyclerView.ViewHolder {
static class SearchModel implements MappingModel<SearchModel> {
@Override
public boolean areItemsTheSame(@NonNull @NotNull SearchModel newItem) {
return true;
}
@Override
public boolean areContentsTheSame(@NonNull @NotNull SearchModel newItem) {
return true;
}
}
static class SearchViewHolder extends MappingViewHolder<SearchModel> {
public SearchViewHolder(@NonNull View itemView) {
super(itemView);
}
@Override
public void bind(@NonNull @NotNull SearchModel model) {
}
}
static class EmojiModel implements MappingModel<EmojiModel> {
private final Emoji emoji;
EmojiModel(@NonNull Emoji emoji) {
this.emoji = emoji;
}
@Override
public boolean areItemsTheSame(@NonNull @NotNull EmojiModel newItem) {
return newItem.emoji.getValue().equals(emoji.getValue());
}
@Override
public boolean areContentsTheSame(@NonNull @NotNull EmojiModel newItem) {
return areItemsTheSame(newItem);
}
}
static class EmojiViewHolder extends MappingViewHolder<EmojiModel> {
private final EmojiVariationSelectorPopup popup;
private final VariationSelectorListener variationSelectorListener;
private final EmojiEventListener emojiEventListener;
private final boolean allowVariations;
private final ImageView imageView;
private final AsciiEmojiView textView;
private final ImageView hintCorner;
public EmojiViewHolder(@NonNull View itemView) {
public EmojiViewHolder(@NonNull View itemView,
@NonNull EmojiEventListener emojiEventListener,
@NonNull VariationSelectorListener variationSelectorListener,
@NonNull EmojiVariationSelectorPopup popup,
boolean allowVariations)
{
super(itemView);
this.popup = popup;
this.variationSelectorListener = variationSelectorListener;
this.emojiEventListener = emojiEventListener;
this.allowVariations = allowVariations;
this.imageView = itemView.findViewById(R.id.emoji_image);
this.textView = itemView.findViewById(R.id.emoji_text);
this.hintCorner = itemView.findViewById(R.id.emoji_variation_hint);
}
@Override
public void bind(@NonNull @NotNull EmojiModel model) {
final Drawable drawable = EmojiProvider.getEmojiDrawable(imageView.getContext(), model.emoji.getValue());
if (drawable != null) {
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
imageView.setImageDrawable(drawable);
} else {
textView.setVisibility(View.VISIBLE);
imageView.setVisibility(View.GONE);
textView.setEmoji(model.emoji.getValue());
}
itemView.setOnClickListener(v -> {
emojiEventListener.onEmojiSelected(model.emoji.getValue());
});
if (allowVariations && model.emoji.getVariations().size() > 1) {
itemView.setOnLongClickListener(v -> {
popup.dismiss();
popup.setVariations(model.emoji.getVariations());
popup.showAsDropDown(itemView, 0, -(2 * itemView.getHeight()));
variationSelectorListener.onVariationSelectorStateChanged(true);
return true;
});
hintCorner.setVisibility(View.VISIBLE);
} else {
itemView.setOnLongClickListener(null);
hintCorner.setVisibility(View.GONE);
}
}
}
public interface VariationSelectorListener {

View File

@@ -9,13 +9,16 @@ import androidx.appcompat.widget.AppCompatImageButton;
import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
import org.thoughtcrime.securesms.stickers.StickerKeyboardProvider;
import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.MediaKeyboardListener {
private Drawable emojiToggle;
private Drawable stickerToggle;
private Drawable gifToggle;
private Drawable mediaToggle;
private Drawable imeToggle;
@@ -45,9 +48,10 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
}
private void initialize() {
this.emojiToggle = ContextCompat.getDrawable(getContext(), R.drawable.ic_emoji_smiley_24);
this.stickerToggle = ContextCompat.getDrawable(getContext(), R.drawable.ic_sticker_24);
this.imeToggle = ContextCompat.getDrawable(getContext(), R.drawable.ic_keyboard_24);
this.emojiToggle = ContextUtil.requireDrawable(getContext(), R.drawable.keyboard_pager_fragment_emoji_icon);
this.stickerToggle = ContextUtil.requireDrawable(getContext(), R.drawable.keyboard_pager_fragment_sticker_icon);
this.gifToggle = ContextUtil.requireDrawable(getContext(), R.drawable.keyboard_pager_fragment_gif_icon);
this.imeToggle = ContextUtil.requireDrawable(getContext(), R.drawable.ic_keyboard_24);
this.mediaToggle = emojiToggle;
setToMedia();
@@ -57,8 +61,18 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
drawer.setKeyboardListener(this);
}
public void setStickerMode(boolean stickerMode) {
this.mediaToggle = stickerMode ? stickerToggle : emojiToggle;
public void setStickerMode(@NonNull KeyboardPage page) {
switch (page) {
case EMOJI:
mediaToggle = emojiToggle;
break;
case STICKER:
mediaToggle = stickerToggle;
break;
case GIF:
mediaToggle = gifToggle;
break;
}
if (getDrawable() != imeToggle) {
setToMedia();
@@ -78,9 +92,18 @@ public class EmojiToggle extends AppCompatImageButton implements MediaKeyboard.M
}
@Override
public void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider) {
setStickerMode(provider instanceof StickerKeyboardProvider);
TextSecurePreferences.setMediaKeyboardMode(getContext(), (provider instanceof StickerKeyboardProvider) ? TextSecurePreferences.MediaKeyboardMode.STICKER
: TextSecurePreferences.MediaKeyboardMode.EMOJI);
public void onKeyboardChanged(@NonNull KeyboardPage page) {
setStickerMode(page);
switch (page) {
case EMOJI:
TextSecurePreferences.setMediaKeyboardMode(getContext(), TextSecurePreferences.MediaKeyboardMode.EMOJI);
break;
case STICKER:
TextSecurePreferences.setMediaKeyboardMode(getContext(), TextSecurePreferences.MediaKeyboardMode.STICKER);
break;
case GIF:
TextSecurePreferences.setMediaKeyboardMode(getContext(), TextSecurePreferences.MediaKeyboardMode.GIF);
break;
}
}
}

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -10,18 +9,20 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.RepeatableImageKey;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.keyboard.KeyboardPage;
import org.thoughtcrime.securesms.keyboard.KeyboardPagerFragment;
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment;
import java.util.Arrays;
import java.security.Key;
public class MediaKeyboard extends FrameLayout implements InputView,
MediaKeyboardProvider.Presenter,
@@ -29,22 +30,15 @@ public class MediaKeyboard extends FrameLayout implements InputView,
MediaKeyboardBottomTabAdapter.EventListener
{
private static final String TAG = Log.tag(MediaKeyboard.class);
private static final String TAG = Log.tag(MediaKeyboard.class);
private static final String EMOJI_SEARCH = "emoji_search_fragment";
private RecyclerView categoryTabs;
private ViewPager categoryPager;
private ViewGroup providerTabs;
private RepeatableImageKey backspaceButton;
private RepeatableImageKey backspaceButtonBackup;
private View searchButton;
private View addButton;
@Nullable private MediaKeyboardListener keyboardListener;
private MediaKeyboardProvider[] providers;
private int providerIndex;
private final boolean tabsAtBottom;
private MediaKeyboardBottomTabAdapter categoryTabAdapter;
@Nullable private MediaKeyboardListener keyboardListener;
private boolean isInitialised;
private int latestKeyboardHeight;
private State keyboardState;
private KeyboardPagerFragment keyboardPagerFragment;
private FragmentManager fragmentManager;
public MediaKeyboard(Context context) {
this(context, null);
@@ -52,23 +46,6 @@ public class MediaKeyboard extends FrameLayout implements InputView,
public MediaKeyboard(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaKeyboard, 0, 0);
try {
tabsAtBottom = typedArray.getInt(R.styleable.MediaKeyboard_tabs_gravity, 0) == 0;
} finally {
typedArray.recycle();
}
}
public void setProviders(int startIndex, MediaKeyboardProvider... providers) {
if (!Arrays.equals(this.providers, providers)) {
this.providers = providers;
this.providerIndex = startIndex;
requestPresent(providers, providerIndex);
}
}
public void setKeyboardListener(@Nullable MediaKeyboardListener listener) {
@@ -82,10 +59,12 @@ public class MediaKeyboard extends FrameLayout implements InputView,
@Override
public void show(int height, boolean immediate) {
if (this.categoryPager == null) initView();
if (!isInitialised) initView();
latestKeyboardHeight = height;
ViewGroup.LayoutParams params = getLayoutParams();
params.height = height;
params.height = (keyboardState == State.NORMAL) ? latestKeyboardHeight : ViewGroup.LayoutParams.WRAP_CONTENT;
Log.i(TAG, "showing emoji drawer with height " + params.height);
setLayoutParams(params);
@@ -93,19 +72,20 @@ public class MediaKeyboard extends FrameLayout implements InputView,
}
public void show() {
if (this.categoryPager == null) initView();
if (!isInitialised) initView();
setVisibility(VISIBLE);
if (keyboardListener != null) keyboardListener.onShown();
requestPresent(providers, providerIndex);
keyboardPagerFragment.show();
}
@Override
public void hide(boolean immediate) {
setVisibility(GONE);
onCloseEmojiSearchInternal(false);
if (keyboardListener != null) keyboardListener.onHidden();
Log.i(TAG, "hide()");
keyboardPagerFragment.hide();
}
@Override
@@ -117,30 +97,29 @@ public class MediaKeyboard extends FrameLayout implements InputView,
@Nullable MediaKeyboardProvider.SearchObserver searchObserver,
int startingIndex)
{
if (categoryPager == null) return;
if (!provider.equals(providers[providerIndex])) return;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(provider);
boolean isSolo = providers.length == 1;
presentProviderStrip(isSolo);
presentCategoryPager(pagerAdapter, tabIconProvider, startingIndex);
presentProviderTabs(providers, providerIndex);
presentSearchButton(searchObserver);
presentBackspaceButton(backspaceObserver, isSolo);
presentAddButton(addObserver);
// if (categoryPager == null) return;
// if (!provider.equals(providers[providerIndex])) return;
// if (keyboardListener != null) keyboardListener.onKeyboardChanged(provider);
//
// boolean isSolo = providers.length == 1;
//
// presentProviderStrip(isSolo);
// presentCategoryPager(pagerAdapter, tabIconProvider, startingIndex);
// presentProviderTabs(providers, providerIndex);
// presentSearchButton(searchObserver);
// presentBackspaceButton(backspaceObserver, isSolo);
// presentAddButton(addObserver);
}
@Override
public int getCurrentPosition() {
return categoryPager != null ? categoryPager.getCurrentItem() : 0;
// return categoryPager != null ? categoryPager.getCurrentItem() : 0;
return 0;
}
@Override
public void requestDismissal() {
hide(true);
providerIndex = 0;
if (keyboardListener != null) keyboardListener.onKeyboardProviderChanged(providers[providerIndex]);
}
@Override
@@ -150,148 +129,82 @@ public class MediaKeyboard extends FrameLayout implements InputView,
@Override
public void onTabSelected(int index) {
if (categoryPager != null) {
categoryPager.setCurrentItem(index);
categoryTabs.smoothScrollToPosition(index);
}
// if (categoryPager != null) {
// categoryPager.setCurrentItem(index);
// categoryTabs.smoothScrollToPosition(index);
// }
}
@Override
public void setViewPagerEnabled(boolean enabled) {
if (categoryPager != null) {
categoryPager.setEnabled(enabled);
// if (categoryPager != null) {
// categoryPager.setEnabled(enabled);
// }
}
public void onCloseEmojiSearch() {
onCloseEmojiSearchInternal(true);
}
private void onCloseEmojiSearchInternal(boolean showAfterCommit) {
if (keyboardState == State.NORMAL) {
return;
}
keyboardState = State.NORMAL;
Fragment emojiSearch = fragmentManager.findFragmentByTag(EMOJI_SEARCH);
if (emojiSearch == null) {
return;
}
FragmentTransaction transaction = fragmentManager.beginTransaction()
.remove(emojiSearch)
.show(keyboardPagerFragment)
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
if (showAfterCommit) {
transaction.runOnCommit(() -> show(latestKeyboardHeight, false));
}
transaction.commit();
}
public void onOpenEmojiSearch() {
if (keyboardState == State.EMOJI_SEARCH) {
return;
}
keyboardState = State.EMOJI_SEARCH;
fragmentManager.beginTransaction()
.hide(keyboardPagerFragment)
.add(R.id.media_keyboard_fragment_container, new EmojiSearchFragment(), EMOJI_SEARCH)
.runOnCommit(() -> show(latestKeyboardHeight, true))
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
.commit();
}
private void initView() {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
if (!isInitialised) {
LayoutInflater.from(getContext()).inflate(R.layout.media_keyboard, this, true);
RecyclerView categoryTabsTop = view.findViewById(R.id.media_keyboard_tabs_top);
RecyclerView categoryTabsBottom = view.findViewById(R.id.media_keyboard_tabs);
this.categoryTabs = tabsAtBottom ? categoryTabsBottom : categoryTabsTop;
this.categoryPager = view.findViewById(R.id.media_keyboard_pager);
this.providerTabs = view.findViewById(R.id.media_keyboard_provider_tabs);
this.backspaceButton = view.findViewById(R.id.media_keyboard_backspace);
this.backspaceButtonBackup = view.findViewById(R.id.media_keyboard_backspace_backup);
this.searchButton = view.findViewById(R.id.media_keyboard_search);
this.addButton = view.findViewById(R.id.media_keyboard_add);
this.categoryTabAdapter = new MediaKeyboardBottomTabAdapter(GlideApp.with(this), this, tabsAtBottom);
categoryTabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
categoryTabs.setAdapter(categoryTabAdapter);
categoryTabs.setVisibility(VISIBLE);
}
private void requestPresent(@NonNull MediaKeyboardProvider[] providers, int newIndex) {
providers[providerIndex].setController(null);
providerIndex = newIndex;
providers[providerIndex].setController(this);
providers[providerIndex].requestPresentation(this, providers.length == 1);
}
private void presentCategoryPager(@NonNull PagerAdapter pagerAdapter,
@NonNull MediaKeyboardProvider.TabIconProvider iconProvider,
int startingIndex) {
if (categoryPager.getAdapter() != pagerAdapter) {
categoryPager.setAdapter(pagerAdapter);
keyboardState = State.NORMAL;
latestKeyboardHeight = -1;
isInitialised = true;
fragmentManager = ((FragmentActivity) getContext()).getSupportFragmentManager();
keyboardPagerFragment = (KeyboardPagerFragment) fragmentManager.findFragmentById(R.id.media_keyboard_fragment_container);
}
categoryPager.setCurrentItem(startingIndex);
categoryPager.clearOnPageChangeListeners();
categoryPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {
}
@Override
public void onPageSelected(int i) {
categoryTabAdapter.setActivePosition(i);
categoryTabs.smoothScrollToPosition(i);
providers[providerIndex].setCurrentPosition(i);
}
@Override
public void onPageScrollStateChanged(int i) {
}
});
categoryTabAdapter.setTabIconProvider(iconProvider, pagerAdapter.getCount());
categoryTabAdapter.setActivePosition(startingIndex);
}
private void presentProviderTabs(@NonNull MediaKeyboardProvider[] providers, int selected) {
providerTabs.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(getContext());
for (int i = 0; i < providers.length; i++) {
MediaKeyboardProvider provider = providers[i];
View view = inflater.inflate(provider.getProviderIconView(i == selected), providerTabs, false);
view.setTag(provider);
final int index = i;
view.setOnClickListener(v -> {
requestPresent(providers, index);
});
providerTabs.addView(view);
}
}
private void presentBackspaceButton(@Nullable MediaKeyboardProvider.BackspaceObserver backspaceObserver,
boolean useBackupPosition)
{
if (backspaceObserver != null) {
if (useBackupPosition) {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(VISIBLE);
backspaceButtonBackup.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
} else {
backspaceButton.setVisibility(VISIBLE);
backspaceButton.setOnKeyEventListener(backspaceObserver::onBackspaceClicked);
backspaceButtonBackup.setVisibility(GONE);
backspaceButtonBackup.setOnKeyEventListener(null);
}
} else {
backspaceButton.setVisibility(INVISIBLE);
backspaceButton.setOnKeyEventListener(null);
backspaceButtonBackup.setVisibility(GONE);
backspaceButton.setOnKeyEventListener(null);
}
}
private void presentAddButton(@Nullable MediaKeyboardProvider.AddObserver addObserver) {
if (addObserver != null) {
addButton.setVisibility(VISIBLE);
addButton.setOnClickListener(v -> addObserver.onAddClicked());
} else {
addButton.setVisibility(GONE);
addButton.setOnClickListener(null);
}
}
private void presentSearchButton(@Nullable MediaKeyboardProvider.SearchObserver searchObserver) {
searchButton.setVisibility(searchObserver != null ? VISIBLE : INVISIBLE);
}
private void presentProviderStrip(boolean isSolo) {
int visibility = isSolo ? View.GONE : View.VISIBLE;
searchButton.setVisibility(visibility);
backspaceButton.setVisibility(visibility);
providerTabs.setVisibility(visibility);
}
public interface MediaKeyboardListener {
void onShown();
void onHidden();
void onKeyboardProviderChanged(@NonNull MediaKeyboardProvider provider);
void onKeyboardChanged(@NonNull KeyboardPage page);
}
private enum State {
NORMAL,
EMOJI_SEARCH
}
}

View File

@@ -16,22 +16,19 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
private final GlideRequests glideRequests;
private final EventListener eventListener;
private final boolean highlightTop;
private TabIconProvider tabIconProvider;
private int activePosition;
private int count;
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener, boolean highlightTop) {
public MediaKeyboardBottomTabAdapter(@NonNull GlideRequests glideRequests, @NonNull EventListener eventListener) {
this.glideRequests = glideRequests;
this.eventListener = eventListener;
this.highlightTop = highlightTop;
}
@Override
public @NonNull MediaKeyboardBottomTabViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false),
highlightTop);
return new MediaKeyboardBottomTabViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.media_keyboard_bottom_tab_item, viewGroup, false));
}
@Override
@@ -64,18 +61,12 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
static class MediaKeyboardBottomTabViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
private final View indicator;
private final View imageSelected;
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView, boolean highlightTop) {
public MediaKeyboardBottomTabViewHolder(@NonNull View itemView) {
super(itemView);
View indicatorTop = itemView.findViewById(R.id.media_keyboard_top_tab_indicator);
View indicatorBottom = itemView.findViewById(R.id.media_keyboard_bottom_tab_indicator);
this.image = itemView.findViewById(R.id.media_keyboard_bottom_tab_image);
this.indicator = highlightTop ? indicatorTop : indicatorBottom;
this.indicator.setVisibility(View.VISIBLE);
this.image = itemView.findViewById(R.id.category_icon);
this.imageSelected = itemView.findViewById(R.id.category_icon_selected);
}
void bind(@NonNull GlideRequests glideRequests,
@@ -86,9 +77,7 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
{
tabIconProvider.loadCategoryTabIcon(glideRequests, image, index);
image.setAlpha(selected ? 1 : 0.5f);
image.setSelected(selected);
indicator.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
imageSelected.setSelected(selected);
itemView.setOnClickListener(v -> eventListener.onTabSelected(index));
}
@@ -98,7 +87,7 @@ public class MediaKeyboardBottomTabAdapter extends RecyclerView.Adapter<MediaKey
}
}
interface EventListener {
public interface EventListener {
void onTabSelected(int index);
}
}

View File

@@ -33,6 +33,10 @@ public class RecentEmojiPageModel implements EmojiPageModel {
private final String preferenceName;
private final LinkedHashSet<String> recentlyUsed;
public static boolean hasRecents(Context context, @NonNull String preferenceName) {
return PreferenceManager.getDefaultSharedPreferences(context).contains(preferenceName);
}
public RecentEmojiPageModel(Context context, @NonNull String preferenceName) {
this.prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.preferenceName = preferenceName;

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.appearance
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.livedata.Store
@@ -28,6 +29,7 @@ class AppearanceSettingsViewModel : ViewModel() {
fun setLanguage(language: String) {
store.update { it.copy(language = language) }
SignalStore.settings().language = language
EmojiSearchIndexDownloadJob.scheduleImmediately()
}
fun setMessageFontSize(size: Int) {