diff --git a/emoji/src/main/java/com/vanniktech/emoji/EmojiPopup.java b/emoji/src/main/java/com/vanniktech/emoji/EmojiPopup.java index ce80cfe16c..62a6bf4c7e 100644 --- a/emoji/src/main/java/com/vanniktech/emoji/EmojiPopup.java +++ b/emoji/src/main/java/com/vanniktech/emoji/EmojiPopup.java @@ -32,12 +32,14 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.PopupWindow; + import androidx.annotation.CheckResult; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.viewpager.widget.ViewPager; + import com.vanniktech.emoji.emoji.Emoji; import com.vanniktech.emoji.listeners.OnEmojiBackspaceClickListener; import com.vanniktech.emoji.listeners.OnEmojiClickListener; @@ -53,444 +55,509 @@ import static com.vanniktech.emoji.Utils.backspace; import static com.vanniktech.emoji.Utils.checkNotNull; -@SuppressWarnings("PMD.GodClass") public final class EmojiPopup implements EmojiResultReceiver.Receiver { - static final int MIN_KEYBOARD_HEIGHT = 50; - static final int APPLY_WINDOW_INSETS_DURATION = 250; +@SuppressWarnings("PMD.GodClass") +public final class EmojiPopup implements EmojiResultReceiver.Receiver { + static final int MIN_KEYBOARD_HEIGHT = 50; + static final int APPLY_WINDOW_INSETS_DURATION = 250; - final View rootView; - final Activity context; + final View rootView; + final Activity context; - @NonNull final RecentEmoji recentEmoji; - @NonNull final VariantEmoji variantEmoji; - @NonNull final EmojiVariantPopup variantPopup; + @NonNull + final RecentEmoji recentEmoji; + @NonNull + final VariantEmoji variantEmoji; + @NonNull + final EmojiVariantPopup variantPopup; - final PopupWindow popupWindow; - final EditText editText; + final PopupWindow popupWindow; + final EditText editText; - boolean isPendingOpen; - boolean isKeyboardOpen; + boolean isPendingOpen; + boolean isKeyboardOpen; - private int globalKeyboardHeight; - private int delay; + private int globalKeyboardHeight; + private int delay; - @Nullable OnEmojiPopupShownListener onEmojiPopupShownListener; - @Nullable OnSoftKeyboardCloseListener onSoftKeyboardCloseListener; - @Nullable OnSoftKeyboardOpenListener onSoftKeyboardOpenListener; + @Nullable + OnEmojiPopupShownListener onEmojiPopupShownListener; + @Nullable + OnSoftKeyboardCloseListener onSoftKeyboardCloseListener; + @Nullable + OnSoftKeyboardOpenListener onSoftKeyboardOpenListener; - @Nullable OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; - @Nullable OnEmojiClickListener onEmojiClickListener; - @Nullable OnEmojiPopupDismissListener onEmojiPopupDismissListener; + @Nullable + OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; + @Nullable + OnEmojiClickListener onEmojiClickListener; + @Nullable + OnEmojiPopupDismissListener onEmojiPopupDismissListener; - int popupWindowHeight; - int originalImeOptions = -1; + EmojiView.OnCustomViewListener onCustomViewListener; - final EmojiResultReceiver emojiResultReceiver = new EmojiResultReceiver(new Handler(Looper.getMainLooper())); + int popupWindowHeight; + int originalImeOptions = -1; - final View.OnAttachStateChangeListener onAttachStateChangeListener = new View.OnAttachStateChangeListener() { - @Override public void onViewAttachedToWindow(final View v) { - start(); - } + final EmojiResultReceiver emojiResultReceiver = new EmojiResultReceiver(new Handler(Looper.getMainLooper())); - @Override public void onViewDetachedFromWindow(final View v) { - stop(); + final View.OnAttachStateChangeListener onAttachStateChangeListener = new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(final View v) { + start(); + } - popupWindow.setOnDismissListener(null); - rootView.removeOnAttachStateChangeListener(this); - } - }; + @Override + public void onViewDetachedFromWindow(final View v) { + stop(); - final OnEmojiClickListener internalOnEmojiClickListener = new OnEmojiClickListener() { - @Override public void onEmojiClick(@NonNull final EmojiImageView imageView, @NonNull final Emoji emoji) { - Utils.input(editText, emoji); + popupWindow.setOnDismissListener(null); + rootView.removeOnAttachStateChangeListener(this); + } + }; - recentEmoji.addEmoji(emoji); - variantEmoji.addVariant(emoji); - imageView.updateEmoji(emoji); + final OnEmojiClickListener internalOnEmojiClickListener = new OnEmojiClickListener() { + @Override + public void onEmojiClick(@NonNull final EmojiImageView imageView, @NonNull final Emoji emoji) { + Utils.input(editText, emoji); - if (onEmojiClickListener != null) { - onEmojiClickListener.onEmojiClick(imageView, emoji); - } + recentEmoji.addEmoji(emoji); + variantEmoji.addVariant(emoji); + imageView.updateEmoji(emoji); - variantPopup.dismiss(); - } - }; + if (onEmojiClickListener != null) { + onEmojiClickListener.onEmojiClick(imageView, emoji); + } - final OnEmojiLongClickListener internalOnEmojiLongClickListener = new OnEmojiLongClickListener() { - @Override public void onEmojiLongClick(@NonNull final EmojiImageView view, @NonNull final Emoji emoji) { - variantPopup.show(view, emoji); - } - }; + variantPopup.dismiss(); + } + }; - final OnEmojiBackspaceClickListener internalOnEmojiBackspaceClickListener = new OnEmojiBackspaceClickListener() { - @Override public void onEmojiBackspaceClick(final View v) { - backspace(editText); + final OnEmojiLongClickListener internalOnEmojiLongClickListener = new OnEmojiLongClickListener() { + @Override + public void onEmojiLongClick(@NonNull final EmojiImageView view, @NonNull final Emoji emoji) { + variantPopup.show(view, emoji); + } + }; - if (onEmojiBackspaceClickListener != null) { - onEmojiBackspaceClickListener.onEmojiBackspaceClick(v); - } - } - }; - - final PopupWindow.OnDismissListener onDismissListener = new PopupWindow.OnDismissListener() { - @Override public void onDismiss() { - if (editText instanceof EmojiEditText && ((EmojiEditText) editText).isKeyboardInputDisabled()) { - editText.clearFocus(); - } - if (onEmojiPopupDismissListener != null) { - onEmojiPopupDismissListener.onEmojiPopupDismiss(); - } - } - }; + final OnEmojiBackspaceClickListener internalOnEmojiBackspaceClickListener = new OnEmojiBackspaceClickListener() { + @Override + public void onEmojiBackspaceClick(final View v) { + backspace(editText); - EmojiPopup(@NonNull final EmojiPopup.Builder builder, @NonNull final EditText editText) { - this.context = Utils.asActivity(builder.rootView.getContext()); - this.rootView = builder.rootView.getRootView(); - this.editText = editText; - this.recentEmoji = builder.recentEmoji; - this.variantEmoji = builder.variantEmoji; + if (onEmojiBackspaceClickListener != null) { + onEmojiBackspaceClickListener.onEmojiBackspaceClick(v); + } + } + }; + + final PopupWindow.OnDismissListener onDismissListener = new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + if (editText instanceof EmojiEditText && ((EmojiEditText) editText).isKeyboardInputDisabled()) { + editText.clearFocus(); + } + if (onEmojiPopupDismissListener != null) { + onEmojiPopupDismissListener.onEmojiPopupDismiss(); + } + } + }; + + EmojiPopup(@NonNull final EmojiPopup.Builder builder, @NonNull final EditText editText) { + this.context = Utils.asActivity(builder.rootView.getContext()); + this.rootView = builder.rootView.getRootView(); + this.editText = editText; + this.recentEmoji = builder.recentEmoji; + this.variantEmoji = builder.variantEmoji; + + popupWindow = new PopupWindow(context); + variantPopup = new EmojiVariantPopup(rootView, internalOnEmojiClickListener); - popupWindow = new PopupWindow(context); - variantPopup = new EmojiVariantPopup(rootView, internalOnEmojiClickListener); + final EmojiView emojiView = new EmojiView(context, + internalOnEmojiClickListener, internalOnEmojiLongClickListener, builder); - final EmojiView emojiView = new EmojiView(context, - internalOnEmojiClickListener, internalOnEmojiLongClickListener, builder); + emojiView.setOnEmojiBackspaceClickListener(internalOnEmojiBackspaceClickListener); + emojiView.setOnCustomViewListener(onCustomViewListener); - emojiView.setOnEmojiBackspaceClickListener(internalOnEmojiBackspaceClickListener); + popupWindow.setContentView(emojiView); + popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + popupWindow.setBackgroundDrawable(new BitmapDrawable(context.getResources(), (Bitmap) null)); // To avoid borders and overdraw. + popupWindow.setOnDismissListener(onDismissListener); - popupWindow.setContentView(emojiView); - popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - popupWindow.setBackgroundDrawable(new BitmapDrawable(context.getResources(), (Bitmap) null)); // To avoid borders and overdraw. - popupWindow.setOnDismissListener(onDismissListener); + if (builder.keyboardAnimationStyle != 0) { + popupWindow.setAnimationStyle(builder.keyboardAnimationStyle); + } + + // Root view might already be laid out in which case we need to manually call start() + if (rootView.getParent() != null) { + start(); + } - if (builder.keyboardAnimationStyle != 0) { - popupWindow.setAnimationStyle(builder.keyboardAnimationStyle); + rootView.addOnAttachStateChangeListener(onAttachStateChangeListener); } - // Root view might already be laid out in which case we need to manually call start() - if (rootView.getParent() != null) { - start(); + void start() { + context.getWindow().getDecorView().setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + int previousOffset; + + @Override + public WindowInsets onApplyWindowInsets(final View v, final WindowInsets insets) { + final int offset; + + if (insets.getSystemWindowInsetBottom() < insets.getStableInsetBottom()) { + offset = insets.getSystemWindowInsetBottom(); + } else { + offset = insets.getSystemWindowInsetBottom() - insets.getStableInsetBottom(); + } + + if (offset != previousOffset || offset == 0) { + previousOffset = offset; + + if (offset > Utils.dpToPx(context, MIN_KEYBOARD_HEIGHT)) { + updateKeyboardStateOpened(offset); + } else { + updateKeyboardStateClosed(); + } + } + + return context.getWindow().getDecorView().onApplyWindowInsets(insets); + } + }); } - rootView.addOnAttachStateChangeListener(onAttachStateChangeListener); - } + void stop() { + dismiss(); - void start() { - context.getWindow().getDecorView().setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { - int previousOffset; + context.getWindow().getDecorView().setOnApplyWindowInsetsListener(null); + } - @Override public WindowInsets onApplyWindowInsets(final View v, final WindowInsets insets) { - final int offset; + @SuppressWarnings("PMD.CyclomaticComplexity") + void updateKeyboardStateOpened(final int keyboardHeight) { + if (popupWindowHeight > 0 && popupWindow.getHeight() != popupWindowHeight) { + popupWindow.setHeight(popupWindowHeight); + } else if (popupWindowHeight == 0 && popupWindow.getHeight() != keyboardHeight) { + popupWindow.setHeight(keyboardHeight); + } - if (insets.getSystemWindowInsetBottom() < insets.getStableInsetBottom()) { - offset = insets.getSystemWindowInsetBottom(); + if (globalKeyboardHeight != keyboardHeight) { + globalKeyboardHeight = keyboardHeight; + delay = APPLY_WINDOW_INSETS_DURATION; } else { - offset = insets.getSystemWindowInsetBottom() - insets.getStableInsetBottom(); + delay = 0; } - if (offset != previousOffset || offset == 0) { - previousOffset = offset; + final int properWidth = Utils.getProperWidth(context); - if (offset > Utils.dpToPx(context, MIN_KEYBOARD_HEIGHT)) { - updateKeyboardStateOpened(offset); - } else { - updateKeyboardStateClosed(); - } + if (popupWindow.getWidth() != properWidth) { + popupWindow.setWidth(properWidth); } - return context.getWindow().getDecorView().onApplyWindowInsets(insets); - } - }); - } - - void stop() { - dismiss(); - - context.getWindow().getDecorView().setOnApplyWindowInsetsListener(null); - } + if (!isKeyboardOpen) { + isKeyboardOpen = true; + if (onSoftKeyboardOpenListener != null) { + onSoftKeyboardOpenListener.onKeyboardOpen(keyboardHeight); + } + } - @SuppressWarnings("PMD.CyclomaticComplexity") void updateKeyboardStateOpened(final int keyboardHeight) { - if (popupWindowHeight > 0 && popupWindow.getHeight() != popupWindowHeight) { - popupWindow.setHeight(popupWindowHeight); - } else if (popupWindowHeight == 0 && popupWindow.getHeight() != keyboardHeight) { - popupWindow.setHeight(keyboardHeight); + if (isPendingOpen) { + showAtBottom(); + } } - if (globalKeyboardHeight != keyboardHeight) { - globalKeyboardHeight = keyboardHeight; - delay = APPLY_WINDOW_INSETS_DURATION; - } else { - delay = 0; - } + void updateKeyboardStateClosed() { + isKeyboardOpen = false; - final int properWidth = Utils.getProperWidth(context); + if (onSoftKeyboardCloseListener != null) { + onSoftKeyboardCloseListener.onKeyboardClose(); + } - if (popupWindow.getWidth() != properWidth) { - popupWindow.setWidth(properWidth); + if (isShowing()) { + dismiss(); + } } - if (!isKeyboardOpen) { - isKeyboardOpen = true; - if (onSoftKeyboardOpenListener != null) { - onSoftKeyboardOpenListener.onKeyboardOpen(keyboardHeight); - } + /** + * Set PopUpWindow's height. + * If height is greater than 0 then this value will be used later on. If it is 0 then the + * keyboard height will be dynamically calculated and set as {@link PopupWindow} height. + * + * @param popupWindowHeight - the height of {@link PopupWindow} + */ + public void setPopupWindowHeight(final int popupWindowHeight) { + this.popupWindowHeight = popupWindowHeight >= 0 ? popupWindowHeight : 0; } - if (isPendingOpen) { - showAtBottom(); + public void toggle() { + if (!popupWindow.isShowing()) { + // this is needed because something might have cleared the insets listener + start(); + requestApplyInsets(context.getWindow().getDecorView()); + show(); + } else { + dismiss(); + } } - } - void updateKeyboardStateClosed() { - isKeyboardOpen = false; + public void show() { + if (Utils.shouldOverrideRegularCondition(context, editText) && originalImeOptions == -1) { + originalImeOptions = editText.getImeOptions(); + } - if (onSoftKeyboardCloseListener != null) { - onSoftKeyboardCloseListener.onKeyboardClose(); - } + editText.setFocusableInTouchMode(true); + editText.requestFocus(); - if (isShowing()) { - dismiss(); - } - } - - /** - * Set PopUpWindow's height. - * If height is greater than 0 then this value will be used later on. If it is 0 then the - * keyboard height will be dynamically calculated and set as {@link PopupWindow} height. - * @param popupWindowHeight - the height of {@link PopupWindow} - */ - public void setPopupWindowHeight(final int popupWindowHeight) { - this.popupWindowHeight = popupWindowHeight >= 0 ? popupWindowHeight : 0; - } - - public void toggle() { - if (!popupWindow.isShowing()) { - // this is needed because something might have cleared the insets listener - start(); - requestApplyInsets(context.getWindow().getDecorView()); - show(); - } else { - dismiss(); + showAtBottomPending(); } - } - - public void show() { - if (Utils.shouldOverrideRegularCondition(context, editText) && originalImeOptions == -1) { - originalImeOptions = editText.getImeOptions(); - } - - editText.setFocusableInTouchMode(true); - editText.requestFocus(); - showAtBottomPending(); - } + private void showAtBottomPending() { + isPendingOpen = true; + final InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - private void showAtBottomPending() { - isPendingOpen = true; - final InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (Utils.shouldOverrideRegularCondition(context, editText)) { + editText.setImeOptions(editText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + if (inputMethodManager != null) { + inputMethodManager.restartInput(editText); + } + } - if (Utils.shouldOverrideRegularCondition(context, editText)) { - editText.setImeOptions(editText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - if (inputMethodManager != null) { - inputMethodManager.restartInput(editText); - } + if (inputMethodManager != null) { + emojiResultReceiver.setReceiver(this); + inputMethodManager.showSoftInput(editText, InputMethodManager.RESULT_UNCHANGED_SHOWN, emojiResultReceiver); + } } - if (inputMethodManager != null) { - emojiResultReceiver.setReceiver(this); - inputMethodManager.showSoftInput(editText, InputMethodManager.RESULT_UNCHANGED_SHOWN, emojiResultReceiver); + public boolean isShowing() { + return popupWindow.isShowing(); } - } - - public boolean isShowing() { - return popupWindow.isShowing(); - } - public void dismiss() { - popupWindow.dismiss(); - variantPopup.dismiss(); - recentEmoji.persist(); - variantEmoji.persist(); + public void dismiss() { + popupWindow.dismiss(); + variantPopup.dismiss(); + recentEmoji.persist(); + variantEmoji.persist(); - emojiResultReceiver.setReceiver(null); + emojiResultReceiver.setReceiver(null); - if (originalImeOptions != -1) { - editText.setImeOptions(originalImeOptions); - final InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (originalImeOptions != -1) { + editText.setImeOptions(originalImeOptions); + final InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.restartInput(editText); - } + if (inputMethodManager != null) { + inputMethodManager.restartInput(editText); + } - if (SDK_INT >= O) { - final AutofillManager autofillManager = context.getSystemService(AutofillManager.class); - if (autofillManager != null) { - autofillManager.cancel(); + if (SDK_INT >= O) { + final AutofillManager autofillManager = context.getSystemService(AutofillManager.class); + if (autofillManager != null) { + autofillManager.cancel(); + } + } } - } } - } - - void showAtBottom() { - isPendingOpen = false; - editText.postDelayed(new Runnable() { - @Override public void run() { - popupWindow.showAtLocation(rootView, Gravity.NO_GRAVITY, 0, - Utils.getProperHeight(context) + popupWindowHeight); - } - }, delay); - - if (onEmojiPopupShownListener != null) { - onEmojiPopupShownListener.onEmojiPopupShown(); - } - } - @Override public void onReceiveResult(final int resultCode, final Bundle data) { - if (resultCode == 0 || resultCode == 1) { - showAtBottom(); - } - } - - public static final class Builder { - @NonNull final View rootView; - @StyleRes int keyboardAnimationStyle; - @ColorInt int backgroundColor; - @ColorInt int iconColor; - @ColorInt int selectedIconColor; - @ColorInt int dividerColor; - @Nullable ViewPager.PageTransformer pageTransformer; - @Nullable OnEmojiPopupShownListener onEmojiPopupShownListener; - @Nullable OnSoftKeyboardCloseListener onSoftKeyboardCloseListener; - @Nullable OnSoftKeyboardOpenListener onSoftKeyboardOpenListener; - @Nullable OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; - @Nullable OnEmojiClickListener onEmojiClickListener; - @Nullable OnEmojiPopupDismissListener onEmojiPopupDismissListener; - @NonNull RecentEmoji recentEmoji; - @NonNull VariantEmoji variantEmoji; - int popupWindowHeight; + void showAtBottom() { + isPendingOpen = false; + editText.postDelayed(new Runnable() { + @Override + public void run() { + popupWindow.showAtLocation(rootView, Gravity.NO_GRAVITY, 0, + Utils.getProperHeight(context) + popupWindowHeight); + } + }, delay); - private Builder(final View rootView) { - this.rootView = checkNotNull(rootView, "The root View can't be null"); - this.recentEmoji = new RecentEmojiManager(rootView.getContext()); - this.variantEmoji = new VariantEmojiManager(rootView.getContext()); + if (onEmojiPopupShownListener != null) { + onEmojiPopupShownListener.onEmojiPopupShown(); + } } - /** - * @param rootView The root View of your layout.xml which will be used for calculating the height - * of the keyboard. - * @return builder For building the {@link EmojiPopup}. - */ - @CheckResult public static Builder fromRootView(final View rootView) { - return new Builder(rootView); + @Override + public void onReceiveResult(final int resultCode, final Bundle data) { + if (resultCode == 0 || resultCode == 1) { + showAtBottom(); + } } - @CheckResult public Builder setOnSoftKeyboardCloseListener(@Nullable final OnSoftKeyboardCloseListener listener) { - onSoftKeyboardCloseListener = listener; - return this; - } + public static final class Builder { + @NonNull + final View rootView; + @StyleRes + int keyboardAnimationStyle; + @ColorInt + int backgroundColor; + @ColorInt + int iconColor; + @ColorInt + int selectedIconColor; + @ColorInt + int dividerColor; + @Nullable + ViewPager.PageTransformer pageTransformer; + @Nullable + OnEmojiPopupShownListener onEmojiPopupShownListener; + @Nullable + OnSoftKeyboardCloseListener onSoftKeyboardCloseListener; + @Nullable + OnSoftKeyboardOpenListener onSoftKeyboardOpenListener; + @Nullable + OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; + @Nullable + OnEmojiClickListener onEmojiClickListener; + @Nullable + OnEmojiPopupDismissListener onEmojiPopupDismissListener; + @NonNull + RecentEmoji recentEmoji; + @NonNull + VariantEmoji variantEmoji; + int popupWindowHeight; + + EmojiView.OnCustomViewListener onCustomViewListener; + + private Builder(final View rootView) { + this.rootView = checkNotNull(rootView, "The root View can't be null"); + this.recentEmoji = new RecentEmojiManager(rootView.getContext()); + this.variantEmoji = new VariantEmojiManager(rootView.getContext()); + } - @CheckResult public Builder setOnEmojiClickListener(@Nullable final OnEmojiClickListener listener) { - onEmojiClickListener = listener; - return this; - } + /** + * @param rootView The root View of your layout.xml which will be used for calculating the height + * of the keyboard. + * @return builder For building the {@link EmojiPopup}. + */ + @CheckResult + public static Builder fromRootView(final View rootView) { + return new Builder(rootView); + } - @CheckResult public Builder setOnSoftKeyboardOpenListener(@Nullable final OnSoftKeyboardOpenListener listener) { - onSoftKeyboardOpenListener = listener; - return this; - } + @CheckResult + public Builder setOnSoftKeyboardCloseListener(@Nullable final OnSoftKeyboardCloseListener listener) { + onSoftKeyboardCloseListener = listener; + return this; + } - @CheckResult public Builder setOnEmojiPopupShownListener(@Nullable final OnEmojiPopupShownListener listener) { - onEmojiPopupShownListener = listener; - return this; - } + @CheckResult + public Builder setOnEmojiClickListener(@Nullable final OnEmojiClickListener listener) { + onEmojiClickListener = listener; + return this; + } - @CheckResult public Builder setOnEmojiPopupDismissListener(@Nullable final OnEmojiPopupDismissListener listener) { - onEmojiPopupDismissListener = listener; - return this; - } + @CheckResult + public Builder setOnSoftKeyboardOpenListener(@Nullable final OnSoftKeyboardOpenListener listener) { + onSoftKeyboardOpenListener = listener; + return this; + } - @CheckResult public Builder setOnEmojiBackspaceClickListener(@Nullable final OnEmojiBackspaceClickListener listener) { - onEmojiBackspaceClickListener = listener; - return this; - } + @CheckResult + public Builder setOnEmojiPopupShownListener(@Nullable final OnEmojiPopupShownListener listener) { + onEmojiPopupShownListener = listener; + return this; + } - /** - * Set PopUpWindow's height. - * If height is not 0 then this value will be used later on. If it is 0 then the keyboard height will - * be dynamically calculated and set as {@link PopupWindow} height. - * @param windowHeight - the height of {@link PopupWindow} - * - * @since 0.7.0 - */ - @CheckResult public Builder setPopupWindowHeight(final int windowHeight) { - this.popupWindowHeight = Math.max(windowHeight, 0); - return this; - } + @CheckResult + public Builder setOnEmojiPopupDismissListener(@Nullable final OnEmojiPopupDismissListener listener) { + onEmojiPopupDismissListener = listener; + return this; + } - /** - * Allows you to pass your own implementation of recent emojis. If not provided the default one - * {@link RecentEmojiManager} will be used. - * - * @since 0.2.0 - */ - @CheckResult public Builder setRecentEmoji(@NonNull final RecentEmoji recent) { - recentEmoji = checkNotNull(recent, "recent can't be null"); - return this; - } + @CheckResult + public Builder setOnEmojiBackspaceClickListener(@Nullable final OnEmojiBackspaceClickListener listener) { + onEmojiBackspaceClickListener = listener; + return this; + } - /** - * Allows you to pass your own implementation of variant emojis. If not provided the default one - * {@link VariantEmojiManager} will be used. - * - * @since 0.5.0 - */ - @CheckResult public Builder setVariantEmoji(@NonNull final VariantEmoji variant) { - variantEmoji = checkNotNull(variant, "variant can't be null"); - return this; - } + /** + * Set PopUpWindow's height. + * If height is not 0 then this value will be used later on. If it is 0 then the keyboard height will + * be dynamically calculated and set as {@link PopupWindow} height. + * + * @param windowHeight - the height of {@link PopupWindow} + * @since 0.7.0 + */ + @CheckResult + public Builder setPopupWindowHeight(final int windowHeight) { + this.popupWindowHeight = Math.max(windowHeight, 0); + return this; + } - @CheckResult public Builder setBackgroundColor(@ColorInt final int color) { - backgroundColor = color; - return this; - } + /** + * Allows you to pass your own implementation of recent emojis. If not provided the default one + * {@link RecentEmojiManager} will be used. + * + * @since 0.2.0 + */ + @CheckResult + public Builder setRecentEmoji(@NonNull final RecentEmoji recent) { + recentEmoji = checkNotNull(recent, "recent can't be null"); + return this; + } - @CheckResult public Builder setIconColor(@ColorInt final int color) { - iconColor = color; - return this; - } + /** + * Allows you to pass your own implementation of variant emojis. If not provided the default one + * {@link VariantEmojiManager} will be used. + * + * @since 0.5.0 + */ + @CheckResult + public Builder setVariantEmoji(@NonNull final VariantEmoji variant) { + variantEmoji = checkNotNull(variant, "variant can't be null"); + return this; + } - @CheckResult public Builder setSelectedIconColor(@ColorInt final int color) { - selectedIconColor = color; - return this; - } + @CheckResult + public Builder setBackgroundColor(@ColorInt final int color) { + backgroundColor = color; + return this; + } - @CheckResult public Builder setDividerColor(@ColorInt final int color) { - dividerColor = color; - return this; - } + @CheckResult + public Builder setIconColor(@ColorInt final int color) { + iconColor = color; + return this; + } - @CheckResult public Builder setKeyboardAnimationStyle(@StyleRes final int animation) { - keyboardAnimationStyle = animation; - return this; - } + @CheckResult + public Builder setSelectedIconColor(@ColorInt final int color) { + selectedIconColor = color; + return this; + } - @CheckResult public Builder setPageTransformer(@Nullable final ViewPager.PageTransformer transformer) { - pageTransformer = transformer; - return this; - } + @CheckResult + public Builder setDividerColor(@ColorInt final int color) { + dividerColor = color; + return this; + } + + @CheckResult + public Builder setKeyboardAnimationStyle(@StyleRes final int animation) { + keyboardAnimationStyle = animation; + return this; + } + + @CheckResult + public Builder setPageTransformer(@Nullable final ViewPager.PageTransformer transformer) { + pageTransformer = transformer; + return this; + } - @CheckResult public EmojiPopup build(@NonNull final EditText editText) { - EmojiManager.getInstance().verifyInstalled(); - checkNotNull(editText, "EditText can't be null"); - - final EmojiPopup emojiPopup = new EmojiPopup(this, editText); - emojiPopup.onSoftKeyboardCloseListener = onSoftKeyboardCloseListener; - emojiPopup.onEmojiClickListener = onEmojiClickListener; - emojiPopup.onSoftKeyboardOpenListener = onSoftKeyboardOpenListener; - emojiPopup.onEmojiPopupShownListener = onEmojiPopupShownListener; - emojiPopup.onEmojiPopupDismissListener = onEmojiPopupDismissListener; - emojiPopup.onEmojiBackspaceClickListener = onEmojiBackspaceClickListener; - emojiPopup.popupWindowHeight = Math.max(popupWindowHeight, 0); - return emojiPopup; + @CheckResult + public Builder setCustomContentView(EmojiView.OnCustomViewListener listener) { + onCustomViewListener = listener; + return this; + } + + @CheckResult + public EmojiPopup build(@NonNull final EditText editText) { + EmojiManager.getInstance().verifyInstalled(); + checkNotNull(editText, "EditText can't be null"); + + final EmojiPopup emojiPopup = new EmojiPopup(this, editText); + emojiPopup.onSoftKeyboardCloseListener = onSoftKeyboardCloseListener; + emojiPopup.onEmojiClickListener = onEmojiClickListener; + emojiPopup.onSoftKeyboardOpenListener = onSoftKeyboardOpenListener; + emojiPopup.onEmojiPopupShownListener = onEmojiPopupShownListener; + emojiPopup.onEmojiPopupDismissListener = onEmojiPopupDismissListener; + emojiPopup.onEmojiBackspaceClickListener = onEmojiBackspaceClickListener; + emojiPopup.popupWindowHeight = Math.max(popupWindowHeight, 0); + emojiPopup.onCustomViewListener = onCustomViewListener; + return emojiPopup; + } } - } } diff --git a/emoji/src/main/java/com/vanniktech/emoji/EmojiView.java b/emoji/src/main/java/com/vanniktech/emoji/EmojiView.java index 1442db08c0..c6a25f19a5 100644 --- a/emoji/src/main/java/com/vanniktech/emoji/EmojiView.java +++ b/emoji/src/main/java/com/vanniktech/emoji/EmojiView.java @@ -20,13 +20,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.PorterDuff; -import androidx.annotation.ColorInt; -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.viewpager.widget.ViewPager; -import androidx.appcompat.content.res.AppCompatResources; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +27,14 @@ import android.widget.ImageButton; import android.widget.LinearLayout; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.viewpager.widget.ViewPager; + import com.vanniktech.emoji.emoji.EmojiCategory; import com.vanniktech.emoji.listeners.OnEmojiBackspaceClickListener; import com.vanniktech.emoji.listeners.OnEmojiClickListener; @@ -42,132 +43,156 @@ import static java.util.concurrent.TimeUnit.SECONDS; -@SuppressLint("ViewConstructor") public final class EmojiView extends LinearLayout implements ViewPager.OnPageChangeListener { - private static final long INITIAL_INTERVAL = SECONDS.toMillis(1) / 2; - private static final int NORMAL_INTERVAL = 50; +@SuppressLint("ViewConstructor") +public final class EmojiView extends LinearLayout implements ViewPager.OnPageChangeListener { + private static final long INITIAL_INTERVAL = SECONDS.toMillis(1) / 2; + private static final int NORMAL_INTERVAL = 50; - @ColorInt private final int themeAccentColor; - @ColorInt private final int themeIconColor; + @ColorInt + private final int themeAccentColor; + @ColorInt + private final int themeIconColor; - private final ImageButton[] emojiTabs; - private final EmojiPagerAdapter emojiPagerAdapter; + private final ImageButton[] emojiTabs; + private final EmojiPagerAdapter emojiPagerAdapter; - @Nullable OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; + @Nullable + OnEmojiBackspaceClickListener onEmojiBackspaceClickListener; - private int emojiTabLastSelectedIndex = -1; + OnCustomViewListener onCustomViewListener; - @SuppressWarnings("PMD.CyclomaticComplexity") public EmojiView(final Context context, - final OnEmojiClickListener onEmojiClickListener, - final OnEmojiLongClickListener onEmojiLongClickListener, @NonNull final EmojiPopup.Builder builder) { - super(context); + private int emojiTabLastSelectedIndex = -1; - View.inflate(context, R.layout.emoji_view, this); + @SuppressWarnings("PMD.CyclomaticComplexity") + public EmojiView(final Context context, + final OnEmojiClickListener onEmojiClickListener, + final OnEmojiLongClickListener onEmojiLongClickListener, @NonNull final EmojiPopup.Builder builder) { + super(context); - setOrientation(VERTICAL); - setBackgroundColor(builder.backgroundColor != 0 ? builder.backgroundColor : Utils.resolveColor(context, R.attr.emojiBackground, R.color.emoji_background)); - themeIconColor = builder.iconColor != 0 ? builder.iconColor : Utils.resolveColor(context, R.attr.emojiIcons, R.color.emoji_icons); + View.inflate(context, R.layout.emoji_view, this); - final TypedValue value = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorAccent, value, true); - themeAccentColor = builder.selectedIconColor != 0 ? builder.selectedIconColor : value.data; + if (onCustomViewListener != null) { + onCustomViewListener.onCustomView(this); + } - final ViewPager emojisPager = findViewById(R.id.emojiViewPager); - final View emojiDivider = findViewById(R.id.emojiViewDivider); - emojiDivider.setBackgroundColor(builder.dividerColor != 0 ? builder.dividerColor : Utils.resolveColor(context, R.attr.emojiDivider, R.color.emoji_divider)); + setOrientation(VERTICAL); + setBackgroundColor(builder.backgroundColor != 0 ? builder.backgroundColor : Utils.resolveColor(context, R.attr.emojiBackground, R.color.emoji_background)); + themeIconColor = builder.iconColor != 0 ? builder.iconColor : Utils.resolveColor(context, R.attr.emojiIcons, R.color.emoji_icons); - if (builder.pageTransformer != null) { - emojisPager.setPageTransformer(true, builder.pageTransformer); - } + final TypedValue value = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.colorAccent, value, true); + themeAccentColor = builder.selectedIconColor != 0 ? builder.selectedIconColor : value.data; - final LinearLayout emojisTab = findViewById(R.id.emojiViewTab); - emojisPager.addOnPageChangeListener(this); + final ViewPager emojisPager = findViewById(R.id.emojiViewPager); + final View emojiDivider = findViewById(R.id.emojiViewDivider); + emojiDivider.setBackgroundColor(builder.dividerColor != 0 ? builder.dividerColor : Utils.resolveColor(context, R.attr.emojiDivider, R.color.emoji_divider)); - final EmojiCategory[] categories = EmojiManager.getInstance().getCategories(); + if (builder.pageTransformer != null) { + emojisPager.setPageTransformer(true, builder.pageTransformer); + } - emojiTabs = new ImageButton[categories.length + 2]; - emojiTabs[0] = inflateButton(context, R.drawable.emoji_recent, R.string.emoji_category_recent, emojisTab); - for (int i = 0; i < categories.length; i++) { - emojiTabs[i + 1] = inflateButton(context, categories[i].getIcon(), categories[i].getCategoryName(), emojisTab); - } - emojiTabs[emojiTabs.length - 1] = inflateButton(context, R.drawable.emoji_backspace, R.string.emoji_backspace, emojisTab); + final LinearLayout emojisTab = findViewById(R.id.emojiViewTab); + emojisPager.addOnPageChangeListener(this); - handleOnClicks(emojisPager); + final EmojiCategory[] categories = EmojiManager.getInstance().getCategories(); + + emojiTabs = new ImageButton[categories.length + 2]; + emojiTabs[0] = inflateButton(context, R.drawable.emoji_recent, R.string.emoji_category_recent, emojisTab); + for (int i = 0; i < categories.length; i++) { + emojiTabs[i + 1] = inflateButton(context, categories[i].getIcon(), categories[i].getCategoryName(), emojisTab); + } + emojiTabs[emojiTabs.length - 1] = inflateButton(context, R.drawable.emoji_backspace, R.string.emoji_backspace, emojisTab); - emojiPagerAdapter = new EmojiPagerAdapter(onEmojiClickListener, onEmojiLongClickListener, builder.recentEmoji, builder.variantEmoji); - emojisPager.setAdapter(emojiPagerAdapter); + handleOnClicks(emojisPager); - final int startIndex = emojiPagerAdapter.numberOfRecentEmojis() > 0 ? 0 : 1; - emojisPager.setCurrentItem(startIndex); - onPageSelected(startIndex); - } + emojiPagerAdapter = new EmojiPagerAdapter(onEmojiClickListener, onEmojiLongClickListener, builder.recentEmoji, builder.variantEmoji); + emojisPager.setAdapter(emojiPagerAdapter); - private void handleOnClicks(final ViewPager emojisPager) { - for (int i = 0; i < emojiTabs.length - 1; i++) { - emojiTabs[i].setOnClickListener(new EmojiTabsClickListener(emojisPager, i)); + final int startIndex = emojiPagerAdapter.numberOfRecentEmojis() > 0 ? 0 : 1; + emojisPager.setCurrentItem(startIndex); + onPageSelected(startIndex); } - emojiTabs[emojiTabs.length - 1].setOnTouchListener(new RepeatListener(INITIAL_INTERVAL, NORMAL_INTERVAL, new OnClickListener() { - @Override public void onClick(final View view) { - if (onEmojiBackspaceClickListener != null) { - onEmojiBackspaceClickListener.onEmojiBackspaceClick(view); + private void handleOnClicks(final ViewPager emojisPager) { + for (int i = 0; i < emojiTabs.length - 1; i++) { + emojiTabs[i].setOnClickListener(new EmojiTabsClickListener(emojisPager, i)); } - } - })); - } - public void setOnEmojiBackspaceClickListener(@Nullable final OnEmojiBackspaceClickListener onEmojiBackspaceClickListener) { - this.onEmojiBackspaceClickListener = onEmojiBackspaceClickListener; - } + emojiTabs[emojiTabs.length - 1].setOnTouchListener(new RepeatListener(INITIAL_INTERVAL, NORMAL_INTERVAL, new OnClickListener() { + @Override + public void onClick(final View view) { + if (onEmojiBackspaceClickListener != null) { + onEmojiBackspaceClickListener.onEmojiBackspaceClick(view); + } + } + })); + } + + public void setOnCustomViewListener(OnCustomViewListener listener) { + onCustomViewListener = listener; + } + + public void setOnEmojiBackspaceClickListener(@Nullable final OnEmojiBackspaceClickListener onEmojiBackspaceClickListener) { + this.onEmojiBackspaceClickListener = onEmojiBackspaceClickListener; + } + + private ImageButton inflateButton(final Context context, @DrawableRes final int icon, @StringRes final int categoryName, final ViewGroup parent) { + final ImageButton button = (ImageButton) LayoutInflater.from(context).inflate(R.layout.emoji_view_category, parent, false); - private ImageButton inflateButton(final Context context, @DrawableRes final int icon, @StringRes final int categoryName, final ViewGroup parent) { - final ImageButton button = (ImageButton) LayoutInflater.from(context).inflate(R.layout.emoji_view_category, parent, false); + button.setImageDrawable(AppCompatResources.getDrawable(context, icon)); + button.setColorFilter(themeIconColor, PorterDuff.Mode.SRC_IN); + button.setContentDescription(context.getString(categoryName)); - button.setImageDrawable(AppCompatResources.getDrawable(context, icon)); - button.setColorFilter(themeIconColor, PorterDuff.Mode.SRC_IN); - button.setContentDescription(context.getString(categoryName)); + parent.addView(button); - parent.addView(button); + return button; + } - return button; - } + @Override + public void onPageSelected(final int i) { + if (emojiTabLastSelectedIndex != i) { + if (i == 0) { + emojiPagerAdapter.invalidateRecentEmojis(); + } - @Override public void onPageSelected(final int i) { - if (emojiTabLastSelectedIndex != i) { - if (i == 0) { - emojiPagerAdapter.invalidateRecentEmojis(); - } + if (emojiTabLastSelectedIndex >= 0 && emojiTabLastSelectedIndex < emojiTabs.length) { + emojiTabs[emojiTabLastSelectedIndex].setSelected(false); + emojiTabs[emojiTabLastSelectedIndex].setColorFilter(themeIconColor, PorterDuff.Mode.SRC_IN); + } - if (emojiTabLastSelectedIndex >= 0 && emojiTabLastSelectedIndex < emojiTabs.length) { - emojiTabs[emojiTabLastSelectedIndex].setSelected(false); - emojiTabs[emojiTabLastSelectedIndex].setColorFilter(themeIconColor, PorterDuff.Mode.SRC_IN); - } + emojiTabs[i].setSelected(true); + emojiTabs[i].setColorFilter(themeAccentColor, PorterDuff.Mode.SRC_IN); - emojiTabs[i].setSelected(true); - emojiTabs[i].setColorFilter(themeAccentColor, PorterDuff.Mode.SRC_IN); + emojiTabLastSelectedIndex = i; + } + } - emojiTabLastSelectedIndex = i; + @Override + public void onPageScrolled(final int i, final float v, final int i2) { + // No-op. } - } - @Override public void onPageScrolled(final int i, final float v, final int i2) { - // No-op. - } + @Override + public void onPageScrollStateChanged(final int i) { + // No-op. + } - @Override public void onPageScrollStateChanged(final int i) { - // No-op. - } + static class EmojiTabsClickListener implements OnClickListener { + private final ViewPager emojisPager; + private final int position; - static class EmojiTabsClickListener implements OnClickListener { - private final ViewPager emojisPager; - private final int position; + EmojiTabsClickListener(final ViewPager emojisPager, final int position) { + this.emojisPager = emojisPager; + this.position = position; + } - EmojiTabsClickListener(final ViewPager emojisPager, final int position) { - this.emojisPager = emojisPager; - this.position = position; + @Override + public void onClick(final View v) { + emojisPager.setCurrentItem(position); + } } - @Override public void onClick(final View v) { - emojisPager.setCurrentItem(position); + interface OnCustomViewListener { + public void onCustomView(View v); } - } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac33e9944a..4e1cc9db6b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists