Allow reacting direct item with any emoji. Fixes austinhuang0131/barinsta#1137

This commit is contained in:
Ammar Githam 2021-05-09 03:57:47 +09:00
parent 5744a1a687
commit 2f4fe657e9
11 changed files with 172 additions and 204 deletions

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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,17 +128,17 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
binding.image.setImageDrawable(null);
binding.indicator.setVisibility(View.GONE);
itemView.setOnLongClickListener(null);
itemView.post(() -> {
binding.image.setImageDrawable(emoji.getDrawable());
final boolean hasVariants = !parent.getVariants().isEmpty();
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
if (onEmojiClickListener != null) {
itemView.setOnClickListener(v -> onEmojiClickListener.onClick(v, emoji));
}
if (hasVariants && onEmojiLongClickListener != null) {
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
}
});
// itemView.post(() -> {
binding.image.setImageDrawable(emoji.getDrawable());
final boolean hasVariants = !parent.getVariants().isEmpty();
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
if (onEmojiClickListener != null) {
itemView.setOnClickListener(v -> onEmojiClickListener.onClick(v, emoji));
}
if (hasVariants && onEmojiLongClickListener != null) {
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
}
// });
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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));
}
}
}

View File

@ -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);

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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);
}