Allow reacting direct item with any emoji. Fixes austinhuang0131/barinsta#1137
This commit is contained in:
parent
5744a1a687
commit
2f4fe657e9
@ -406,6 +406,8 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
void onReactionClick(DirectItem item, int position);
|
||||
|
||||
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
|
||||
|
||||
void onAddReactionListener(DirectItem item);
|
||||
}
|
||||
|
||||
public interface DirectItemInternalLongClickListener {
|
||||
|
@ -551,6 +551,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
||||
menu.setOnDismissListener(() -> setSelected(false));
|
||||
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
|
||||
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
|
||||
menu.setOnAddReactionListener(() -> {
|
||||
menu.dismiss();
|
||||
itemView.postDelayed(() -> callback.onAddReactionListener(item), 300);
|
||||
});
|
||||
menu.show(itemView, location);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,100 @@
|
||||
package awais.instagrabber.customviews.emoji;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public class EmojiBottomSheetDialog extends BottomSheetDialogFragment {
|
||||
public static final String TAG = EmojiBottomSheetDialog.class.getSimpleName();
|
||||
|
||||
private RecyclerView grid;
|
||||
private EmojiPicker.OnEmojiClickListener callback;
|
||||
|
||||
@NonNull
|
||||
public static EmojiBottomSheetDialog newInstance() {
|
||||
// Bundle args = new Bundle();
|
||||
// fragment.setArguments(args);
|
||||
return new EmojiBottomSheetDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
|
||||
final Context context = getContext();
|
||||
if (context == null) return null;
|
||||
grid = new RecyclerView(context);
|
||||
return grid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
final Dialog dialog = getDialog();
|
||||
if (dialog == null) return;
|
||||
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
|
||||
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
|
||||
if (bottomSheetInternal == null) return;
|
||||
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
bottomSheetInternal.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull final Context context) {
|
||||
super.onAttach(context);
|
||||
final Fragment parentFragment = getParentFragment();
|
||||
if (parentFragment instanceof EmojiPicker.OnEmojiClickListener) {
|
||||
callback = (EmojiPicker.OnEmojiClickListener) parentFragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
grid = null;
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
final GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 9);
|
||||
grid.setLayoutManager(gridLayoutManager);
|
||||
grid.setHasFixedSize(true);
|
||||
grid.setClipToPadding(false);
|
||||
grid.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(8)));
|
||||
final EmojiGridAdapter adapter = new EmojiGridAdapter(null, (view, emoji) -> {
|
||||
if (callback != null) {
|
||||
callback.onClick(view, emoji);
|
||||
}
|
||||
dismiss();
|
||||
}, null);
|
||||
grid.setAdapter(adapter);
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
|
||||
private final EmojiVariantManager emojiVariantManager;
|
||||
private final AppExecutors appExecutors;
|
||||
|
||||
public EmojiGridAdapter(@NonNull final EmojiCategoryType emojiCategoryType,
|
||||
public EmojiGridAdapter(final EmojiCategoryType emojiCategoryType,
|
||||
final OnEmojiClickListener onEmojiClickListener,
|
||||
final OnEmojiLongClickListener onEmojiLongClickListener) {
|
||||
this.onEmojiClickListener = onEmojiClickListener;
|
||||
@ -55,6 +55,11 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
|
||||
emojiVariantManager = EmojiVariantManager.getInstance();
|
||||
appExecutors = AppExecutors.getInstance();
|
||||
setHasStableIds(true);
|
||||
if (emojiCategoryType == null) {
|
||||
// show all if type is null
|
||||
differ.submitList(ImmutableList.copyOf(emojiParser.getAllEmojis().values()));
|
||||
return;
|
||||
}
|
||||
final EmojiCategory emojiCategory = categoryMap.get(emojiCategoryType);
|
||||
if (emojiCategory == null) {
|
||||
differ.submitList(Collections.emptyList());
|
||||
@ -105,7 +110,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
|
||||
}
|
||||
|
||||
public static class EmojiViewHolder extends RecyclerView.ViewHolder {
|
||||
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||
// private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||
private final ItemEmojiGridBinding binding;
|
||||
private final OnEmojiClickListener onEmojiClickListener;
|
||||
private final OnEmojiLongClickListener onEmojiLongClickListener;
|
||||
@ -123,7 +128,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
|
||||
binding.image.setImageDrawable(null);
|
||||
binding.indicator.setVisibility(View.GONE);
|
||||
itemView.setOnLongClickListener(null);
|
||||
itemView.post(() -> {
|
||||
// itemView.post(() -> {
|
||||
binding.image.setImageDrawable(emoji.getDrawable());
|
||||
final boolean hasVariants = !parent.getVariants().isEmpty();
|
||||
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
|
||||
@ -133,7 +138,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
|
||||
if (hasVariants && onEmojiLongClickListener != null) {
|
||||
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
|
||||
}
|
||||
});
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,163 +0,0 @@
|
||||
package awais.instagrabber.customviews.emoji;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.customviews.emoji.EmojiPicker.OnBackspaceClickListener;
|
||||
import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/a/33897583/1436766
|
||||
*/
|
||||
public class EmojiPopupWindow extends PopupWindow {
|
||||
|
||||
private int keyBoardHeight = 0;
|
||||
private Boolean pendingOpen = false;
|
||||
private Boolean isOpened = false;
|
||||
private final View rootView;
|
||||
private final Context context;
|
||||
private final OnEmojiClickListener onEmojiClickListener;
|
||||
private final OnBackspaceClickListener onBackspaceClickListener;
|
||||
|
||||
private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height.
|
||||
*/
|
||||
public EmojiPopupWindow(final View rootView,
|
||||
final OnEmojiClickListener onEmojiClickListener,
|
||||
final OnBackspaceClickListener onBackspaceClickListener) {
|
||||
super(rootView.getContext());
|
||||
this.rootView = rootView;
|
||||
this.context = rootView.getContext();
|
||||
this.onEmojiClickListener = onEmojiClickListener;
|
||||
this.onBackspaceClickListener = onBackspaceClickListener;
|
||||
View customView = createCustomView();
|
||||
setContentView(customView);
|
||||
setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
//default size
|
||||
setSize((int) context.getResources().getDimension(R.dimen.keyboard_height), MATCH_PARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener for the event of keyboard opening or closing.
|
||||
*/
|
||||
public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) {
|
||||
this.onSoftKeyboardOpenCloseListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to show the emoji popup.
|
||||
* NOTE: Since, the soft keyboard sizes are variable on different android devices, the
|
||||
* library needs you to open the soft keyboard atleast once before calling this function.
|
||||
* If that is not possible see showAtBottomPending() function.
|
||||
*/
|
||||
public void showAtBottom() {
|
||||
showAtLocation(rootView, Gravity.BOTTOM, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function when the soft keyboard has not been opened yet. This
|
||||
* will show the emoji popup after the keyboard is up next time.
|
||||
* Generally, you will be calling InputMethodManager.showSoftInput function after
|
||||
* calling this function.
|
||||
*/
|
||||
public void showAtBottomPending() {
|
||||
if (isKeyBoardOpen())
|
||||
showAtBottom();
|
||||
else
|
||||
pendingOpen = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if the soft keyboard is open, false otherwise.
|
||||
*/
|
||||
public Boolean isKeyBoardOpen() {
|
||||
return isOpened;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the popup
|
||||
*/
|
||||
@Override
|
||||
public void dismiss() {
|
||||
super.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this function to resize the emoji popup according to your soft keyboard size
|
||||
*/
|
||||
public void setSizeForSoftKeyboard() {
|
||||
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
Rect r = new Rect();
|
||||
rootView.getWindowVisibleDisplayFrame(r);
|
||||
|
||||
int screenHeight = getUsableScreenHeight();
|
||||
int heightDifference = screenHeight - (r.bottom - r.top);
|
||||
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resourceId > 0) {
|
||||
heightDifference -= context.getResources()
|
||||
.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
if (heightDifference > 100) {
|
||||
keyBoardHeight = heightDifference;
|
||||
setSize(MATCH_PARENT, keyBoardHeight);
|
||||
if (!isOpened) {
|
||||
if (onSoftKeyboardOpenCloseListener != null)
|
||||
onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
|
||||
}
|
||||
isOpened = true;
|
||||
if (pendingOpen) {
|
||||
showAtBottom();
|
||||
pendingOpen = false;
|
||||
}
|
||||
} else {
|
||||
isOpened = false;
|
||||
if (onSoftKeyboardOpenCloseListener != null)
|
||||
onSoftKeyboardOpenCloseListener.onKeyboardClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int getUsableScreenHeight() {
|
||||
return Utils.displayMetrics.heightPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set the popup window size
|
||||
*
|
||||
* @param width Width of the popup
|
||||
* @param height Height of the popup
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
setWidth(width);
|
||||
setHeight(height);
|
||||
}
|
||||
|
||||
private View createCustomView() {
|
||||
final EmojiPicker emojiPicker = new EmojiPicker(context);
|
||||
final LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
|
||||
emojiPicker.setLayoutParams(layoutParams);
|
||||
emojiPicker.init(rootView, onEmojiClickListener, onBackspaceClickListener);
|
||||
return emojiPicker;
|
||||
}
|
||||
|
||||
|
||||
public interface OnSoftKeyboardOpenCloseListener {
|
||||
void onKeyboardOpen(int keyBoardHeight);
|
||||
|
||||
void onKeyboardClose();
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.emoji.text.EmojiCompat;
|
||||
|
@ -15,7 +15,6 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@ -33,7 +32,6 @@ import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@ -75,6 +73,8 @@ import awais.instagrabber.animations.CubicBezierInterpolator;
|
||||
import awais.instagrabber.customviews.RecordView;
|
||||
import awais.instagrabber.customviews.Tooltip;
|
||||
import awais.instagrabber.customviews.emoji.Emoji;
|
||||
import awais.instagrabber.customviews.emoji.EmojiBottomSheetDialog;
|
||||
import awais.instagrabber.customviews.emoji.EmojiPicker;
|
||||
import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
|
||||
import awais.instagrabber.customviews.helpers.HeightProvider;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||
@ -114,7 +114,8 @@ import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory;
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
|
||||
|
||||
public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener {
|
||||
public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener,
|
||||
EmojiPicker.OnEmojiClickListener {
|
||||
private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
|
||||
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
||||
private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000;
|
||||
@ -159,6 +160,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
private LiveData<Integer> pendingRequestsCountLiveData;
|
||||
private LiveData<List<User>> usersLiveData;
|
||||
private boolean autoMarkAsSeen = false;
|
||||
private MenuItem markAsSeenMenuItem;
|
||||
private Media tempMedia;
|
||||
private DirectItem addReactionItem;
|
||||
|
||||
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
|
||||
@ -291,6 +295,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
cb.apply(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddReactionListener(final DirectItem item) {
|
||||
if (item == null) return;
|
||||
addReactionItem = item;
|
||||
final EmojiBottomSheetDialog emojiBottomSheetDialog = EmojiBottomSheetDialog.newInstance();
|
||||
emojiBottomSheetDialog.show(getChildFragmentManager(), EmojiBottomSheetDialog.TAG);
|
||||
}
|
||||
};
|
||||
|
||||
private final DirectItemLongClickListener directItemLongClickListener = position -> {
|
||||
@ -321,8 +333,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
backStackSavedStateResultLiveData.postValue(null);
|
||||
};
|
||||
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
|
||||
private MenuItem markAsSeenMenuItem;
|
||||
private Media tempMedia;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
@ -1461,25 +1471,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction);
|
||||
}
|
||||
|
||||
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> {
|
||||
private User user;
|
||||
private DirectThread thread;
|
||||
|
||||
public ItemsAdapterDataMerger(final LiveData<User> userLiveData,
|
||||
final LiveData<DirectThread> threadLiveData) {
|
||||
addSource(userLiveData, user -> {
|
||||
this.user = user;
|
||||
combine();
|
||||
});
|
||||
addSource(threadLiveData, thread -> {
|
||||
this.thread = thread;
|
||||
combine();
|
||||
});
|
||||
}
|
||||
|
||||
private void combine() {
|
||||
if (user == null || thread == null) return;
|
||||
setValue(new Pair<>(user, thread));
|
||||
@Override
|
||||
public void onClick(final View view, final Emoji emoji) {
|
||||
if (addReactionItem == null) return;
|
||||
final LiveData<Resource<Object>> resourceLiveData = viewModel.sendReaction(addReactionItem, emoji);
|
||||
if (resourceLiveData != null) {
|
||||
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -585,8 +585,7 @@ public final class ThreadManager {
|
||||
if (index < 0) {
|
||||
temp.add(0, reaction);
|
||||
} else if (shouldReplaceIfAlreadyReacted) {
|
||||
temp.add(0, reaction);
|
||||
temp.remove(index);
|
||||
temp.set(index, reaction);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
@ -736,6 +735,7 @@ public final class ThreadManager {
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public LiveData<Resource<Object>> sendReaction(final DirectItem item, final Emoji emoji) {
|
||||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||
final Long userId = getCurrentUserId(data);
|
||||
|
@ -47,4 +47,14 @@ public class DirectItemEmojiReaction implements Serializable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(senderId, timestamp, emoji, superReactType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DirectItemEmojiReaction{" +
|
||||
"senderId=" + senderId +
|
||||
", timestamp=" + timestamp +
|
||||
", emoji='" + emoji + '\'' +
|
||||
", superReactType='" + superReactType + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,12 @@ public class DirectItemReactions implements Cloneable, Serializable {
|
||||
public int hashCode() {
|
||||
return Objects.hash(emojis, likes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DirectItemReactions{" +
|
||||
"emojis=" + emojis +
|
||||
", likes=" + likes +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
@ -77,7 +78,12 @@ public final class EmojiParser {
|
||||
.addAll(emoji.getVariants())
|
||||
.build()
|
||||
.stream())
|
||||
.collect(Collectors.toMap(Emoji::getUnicode, Function.identity()));
|
||||
.collect(Collectors.toMap(
|
||||
Emoji::getUnicode,
|
||||
Function.identity(),
|
||||
(u, v) -> u,
|
||||
LinkedHashMap::new
|
||||
));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "EmojiParser: ", e);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user