1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-26 08:37:29 +00:00

Allow removing like/reaction

This commit is contained in:
Ammar Githam 2021-01-15 02:24:12 +09:00
parent 255c3c84b4
commit 6a163454f4
20 changed files with 613 additions and 50 deletions

View File

@ -389,7 +389,9 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
void onStoryClick(DirectItemStoryShare storyShare); void onStoryClick(DirectItemStoryShare storyShare);
void onReaction(final DirectItem item, Emoji emoji); void onReaction(DirectItem item, Emoji emoji);
void onReactionClick(DirectItem item, int position);
} }
public interface DirectItemInternalLongClickListener { public interface DirectItemInternalLongClickListener {

View File

@ -10,7 +10,7 @@ import androidx.recyclerview.widget.ListAdapter;
import java.util.List; import java.util.List;
import awais.instagrabber.adapters.viewholder.DirectInboxItemViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectInboxItemViewHolder;
import awais.instagrabber.databinding.LayoutDmInboxItemBinding; import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;

View File

@ -0,0 +1,81 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import java.util.List;
import awais.instagrabber.adapters.viewholder.directmessages.DirectReactionViewHolder;
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
public final class DirectReactionsAdapter extends ListAdapter<DirectItemEmojiReaction, DirectReactionViewHolder> {
private static final DiffUtil.ItemCallback<DirectItemEmojiReaction> DIFF_CALLBACK = new DiffUtil.ItemCallback<DirectItemEmojiReaction>() {
@Override
public boolean areItemsTheSame(@NonNull final DirectItemEmojiReaction oldItem, @NonNull final DirectItemEmojiReaction newItem) {
return oldItem.getSenderId() == newItem.getSenderId();
}
@Override
public boolean areContentsTheSame(@NonNull final DirectItemEmojiReaction oldItem, @NonNull final DirectItemEmojiReaction newItem) {
return oldItem.getEmoji().equals(newItem.getEmoji());
}
};
private final long viewerId;
private final List<User> users;
private final String itemId;
private final OnReactionClickListener onReactionClickListener;
public DirectReactionsAdapter(final long viewerId,
final List<User> users,
final String itemId,
final OnReactionClickListener onReactionClickListener) {
super(DIFF_CALLBACK);
this.viewerId = viewerId;
this.users = users;
this.itemId = itemId;
this.onReactionClickListener = onReactionClickListener;
setHasStableIds(true);
}
@NonNull
@Override
public DirectReactionViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final LayoutDmUserItemBinding binding = LayoutDmUserItemBinding.inflate(layoutInflater, parent, false);
return new DirectReactionViewHolder(binding, viewerId, itemId, onReactionClickListener);
}
@Override
public void onBindViewHolder(@NonNull final DirectReactionViewHolder holder, final int position) {
final DirectItemEmojiReaction reaction = getItem(position);
if (reaction == null) return;
holder.bind(reaction, getUser(reaction.getSenderId()));
}
@Override
public long getItemId(final int position) {
return getItem(position).getSenderId();
}
@Nullable
private User getUser(final long pk) {
return users.stream()
.filter(user -> user.getPk() == pk)
.findFirst()
.orElse(null);
}
public interface OnReactionClickListener {
void onReactionClick(String itemId, DirectItemEmojiReaction reaction);
}
}

View File

@ -14,7 +14,7 @@ import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DirectUserViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectUserViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.LayoutDmUserItemBinding; import awais.instagrabber.databinding.LayoutDmUserItemBinding;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;

View File

@ -12,7 +12,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener; import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener;
import awais.instagrabber.adapters.viewholder.DirectUserViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectUserViewHolder;
import awais.instagrabber.databinding.LayoutDmUserItemBinding; import awais.instagrabber.databinding.LayoutDmUserItemBinding;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;

View File

@ -1,4 +1,4 @@
package awais.instagrabber.adapters.viewholder; package awais.instagrabber.adapters.viewholder.directmessages;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.view.View; import android.view.View;

View File

@ -24,7 +24,6 @@ import androidx.transition.TransitionManager;
import com.google.android.material.transition.MaterialFade; import com.google.android.material.transition.MaterialFade;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -109,7 +108,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
public void bind(final int position, final DirectItem item) { public void bind(final int position, final DirectItem item) {
this.item = item; this.item = item;
final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING; final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
itemView.post(() -> bindBase(item, messageDirection)); itemView.post(() -> bindBase(item, messageDirection, position));
itemView.post(() -> bindItem(item, messageDirection)); itemView.post(() -> bindItem(item, messageDirection));
itemView.post(() -> setupLongClickListener(position)); itemView.post(() -> setupLongClickListener(position));
// bindBase(item, messageDirection); // bindBase(item, messageDirection);
@ -117,7 +116,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
// setupLongClickListener(position); // setupLongClickListener(position);
} }
private void bindBase(final DirectItem item, final MessageDirection messageDirection) { private void bindBase(final DirectItem item, final MessageDirection messageDirection, final int position) {
final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams(); final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams();
final DirectItemType itemType = item.getItemType(); final DirectItemType itemType = item.getItemType();
setMessageDirectionGravity(messageDirection, containerLayoutParams); setMessageDirectionGravity(messageDirection, containerLayoutParams);
@ -134,7 +133,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
binding.messageInfo.setPadding(0, 0, messageInfoPaddingSmall, dmRadiusSmall); binding.messageInfo.setPadding(0, 0, messageInfoPaddingSmall, dmRadiusSmall);
} }
setupReply(item, messageDirection); setupReply(item, messageDirection);
setReactions(item); setReactions(item, position);
} }
private void setBackground(final MessageDirection messageDirection) { private void setBackground(final MessageDirection messageDirection) {
@ -334,7 +333,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId; replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
} }
private void setReactions(final DirectItem item) { private void setReactions(final DirectItem item, final int position) {
binding.getRoot().post(() -> { binding.getRoot().post(() -> {
MaterialFade materialFade = new MaterialFade(); MaterialFade materialFade = new MaterialFade();
materialFade.addTarget(binding.emojis); materialFade.addTarget(binding.emojis);
@ -343,17 +342,27 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null; final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
if (emojis == null || emojis.isEmpty()) { if (emojis == null || emojis.isEmpty()) {
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall); binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall);
binding.emojis.setVisibility(View.GONE); binding.reactionsWrapper.setVisibility(View.GONE);
return; return;
} }
binding.emojis.setVisibility(View.VISIBLE); binding.reactionsWrapper.setVisibility(View.VISIBLE);
binding.emojis.setTranslationY(getReactionsTranslationY()); binding.reactionsWrapper.setTranslationY(getReactionsTranslationY());
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, reactionAdjustMargin); binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, reactionAdjustMargin);
final String emojisJoined = emojis.stream() binding.emojis.setEmojis(emojis.stream()
.map(DirectItemEmojiReaction::getEmoji) .map(DirectItemEmojiReaction::getEmoji)
.collect(Collectors.joining()); .collect(Collectors.toList()));
final String text = String.format(Locale.ENGLISH, "%s %d", emojisJoined, emojis.size()); // binding.emojis.setEmojis(ImmutableList.of("😣",
binding.emojis.setText(text); // "😖",
// "😫",
// "😩",
// "🥺",
// "😢",
// "😭",
// "😤",
// "😠",
// "😡",
// "🤬"));
binding.emojis.setOnClickListener(v -> callback.onReactionClick(item, position));
// final List<DirectUser> reactedUsers = emojis.stream() // final List<DirectUser> reactedUsers = emojis.stream()
// .map(DirectItemEmojiReaction::getSenderId) // .map(DirectItemEmojiReaction::getSenderId)
// .distinct() // .distinct()

View File

@ -0,0 +1,71 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectReactionsAdapter.OnReactionClickListener;
import awais.instagrabber.customviews.emoji.Emoji;
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
import awais.instagrabber.utils.emoji.EmojiParser;
public class DirectReactionViewHolder extends RecyclerView.ViewHolder {
private final LayoutDmUserItemBinding binding;
private final long viewerId;
private final String itemId;
private final OnReactionClickListener onReactionClickListener;
private final EmojiParser emojiParser;
public DirectReactionViewHolder(final LayoutDmUserItemBinding binding,
final long viewerId,
final String itemId,
final OnReactionClickListener onReactionClickListener) {
super(binding.getRoot());
this.binding = binding;
this.viewerId = viewerId;
this.itemId = itemId;
this.onReactionClickListener = onReactionClickListener;
binding.info.setVisibility(View.GONE);
binding.secondaryImage.setVisibility(View.VISIBLE);
emojiParser = EmojiParser.getInstance();
}
public void bind(final DirectItemEmojiReaction reaction,
@Nullable final User user) {
itemView.setOnClickListener(v -> {
if (onReactionClickListener == null) return;
onReactionClickListener.onReactionClick(itemId, reaction);
});
setUser(user);
setReaction(reaction);
}
private void setReaction(final DirectItemEmojiReaction reaction) {
final Emoji emoji = emojiParser.getEmoji(reaction.getEmoji());
if (emoji == null) {
binding.secondaryImage.setImageDrawable(null);
return;
}
binding.secondaryImage.setImageDrawable(emoji.getDrawable());
}
private void setUser(final User user) {
if (user == null) {
binding.fullName.setText("");
binding.username.setText("");
binding.profilePic.setImageURI((String) null);
return;
}
binding.fullName.setText(user.getFullName());
if (user.getPk() == viewerId) {
binding.username.setText(R.string.tap_to_remove);
} else {
binding.username.setText(user.getUsername());
}
binding.profilePic.setImageURI(user.getProfilePicUrl());
}
}

View File

@ -1,4 +1,4 @@
package awais.instagrabber.adapters.viewholder; package awais.instagrabber.adapters.viewholder.directmessages;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;

View File

@ -0,0 +1,82 @@
package awais.instagrabber.customviews;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.emoji.widget.EmojiAppCompatTextView;
import java.util.List;
import java.util.stream.Collectors;
public class ReactionEmojiTextView extends EmojiAppCompatTextView {
private static final String TAG = ReactionEmojiTextView.class.getSimpleName();
private final SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
private String count = "";
private SpannableString ellipsisSpannable;
private String distinctEmojis;
public ReactionEmojiTextView(final Context context) {
super(context);
init();
}
public ReactionEmojiTextView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init();
}
public ReactionEmojiTextView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
ellipsisSpannable = new SpannableString(count);
}
@SuppressLint("SetTextI18n")
public void setEmojis(@NonNull final List<String> emojis) {
count = String.valueOf(emojis.size());
distinctEmojis = emojis.stream()
.distinct()
.collect(Collectors.joining());
ellipsisSpannable = new SpannableString(count);
setText(distinctEmojis + (emojis.size() > 1 ? count : ""));
}
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final CharSequence text = getText();
if (text == null) return;
final int measuredWidth = getMeasuredWidth();
float availableTextWidth = measuredWidth - getCompoundPaddingLeft() - getCompoundPaddingRight();
CharSequence ellipsizedText = TextUtils.ellipsize(text, getPaint(), availableTextWidth, getEllipsize());
if (!ellipsizedText.toString().equals(text.toString())) {
// If the ellipsizedText is different than the original text, this means that it didn't fit and got indeed ellipsized.
// Calculate the new availableTextWidth by taking into consideration the size of the custom ellipsis, too.
availableTextWidth = (availableTextWidth - getPaint().measureText(count));
ellipsizedText = TextUtils.ellipsize(text, getPaint(), availableTextWidth, getEllipsize());
final int defaultEllipsisStart = ellipsizedText.toString().indexOf(getDefaultEllipsis());
final int defaultEllipsisEnd = defaultEllipsisStart + 1;
spannableStringBuilder.clear();
// Update the text with the ellipsized version and replace the default ellipsis with the custom one.
final SpannableStringBuilder replace = spannableStringBuilder.append(ellipsizedText)
.replace(defaultEllipsisStart, defaultEllipsisEnd, ellipsisSpannable);
setText(replace);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private char getDefaultEllipsis() {
return '…';
}
}

View File

@ -0,0 +1,121 @@
package awais.instagrabber.dialogs;
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.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectReactionsAdapter;
import awais.instagrabber.adapters.DirectReactionsAdapter.OnReactionClickListener;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
import awais.instagrabber.utils.TextUtils;
public class DirectItemReactionDialogFragment extends BottomSheetDialogFragment {
private static final String ARG_VIEWER_ID = "viewerId";
private static final String ARG_ITEM_ID = "itemId";
private static final String ARG_USERS = "users";
private static final String ARG_REACTIONS = "reactions";
private RecyclerView recyclerView;
private OnReactionClickListener onReactionClickListener;
public static DirectItemReactionDialogFragment newInstance(final long viewerId,
@NonNull final ArrayList<User> users,
@NonNull final String itemId,
@NonNull final DirectItemReactions reactions) {
Bundle args = new Bundle();
args.putLong(ARG_VIEWER_ID, viewerId);
args.putSerializable(ARG_USERS, users);
args.putString(ARG_ITEM_ID, itemId);
args.putSerializable(ARG_REACTIONS, reactions);
DirectItemReactionDialogFragment fragment = new DirectItemReactionDialogFragment();
fragment.setArguments(args);
return fragment;
}
public DirectItemReactionDialogFragment() {}
@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;
}
recyclerView = new RecyclerView(context);
return recyclerView;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
try {
onReactionClickListener = (OnReactionClickListener) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException("Calling fragment must implement DirectReactionsAdapter.OnReactionClickListener interface");
}
}
@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();
}
private void init() {
final Context context = getContext();
if (context == null) return;
final Bundle arguments = getArguments();
if (arguments == null) return;
final long viewerId = arguments.getLong(ARG_VIEWER_ID);
final Serializable usersSerializable = arguments.getSerializable(ARG_USERS);
if (!(usersSerializable instanceof ArrayList)) return;
//noinspection unchecked
final List<User> users = (ArrayList<User>) usersSerializable;
final Serializable reactionsSerializable = arguments.getSerializable(ARG_REACTIONS);
if (!(reactionsSerializable instanceof DirectItemReactions)) return;
final DirectItemReactions reactions = (DirectItemReactions) reactionsSerializable;
final String itemId = arguments.getString(ARG_ITEM_ID);
if (TextUtils.isEmpty(itemId)) return;
recyclerView.setLayoutManager(new LinearLayoutManager(context));
final DirectReactionsAdapter adapter = new DirectReactionsAdapter(viewerId, users, itemId, onReactionClickListener);
recyclerView.setAdapter(adapter);
adapter.submitList(reactions.getEmojis());
}
}

View File

@ -50,6 +50,7 @@ import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -60,6 +61,7 @@ import awais.instagrabber.adapters.DirectItemsAdapter;
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemLongClickListener; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemLongClickListener;
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader;
import awais.instagrabber.adapters.DirectReactionsAdapter;
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
import awais.instagrabber.animations.CubicBezierInterpolator; import awais.instagrabber.animations.CubicBezierInterpolator;
import awais.instagrabber.customviews.RecordView; import awais.instagrabber.customviews.RecordView;
@ -70,6 +72,7 @@ import awais.instagrabber.customviews.helpers.HeightProvider;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter; import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment; import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.models.Resource; import awais.instagrabber.models.Resource;
@ -77,6 +80,7 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare; import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
@ -90,7 +94,7 @@ import awais.instagrabber.viewmodels.DirectThreadViewModel;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
public class DirectMessageThreadFragment extends Fragment { public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener {
private static final String TAG = DirectMessageThreadFragment.class.getSimpleName(); private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000; private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000;
@ -145,9 +149,7 @@ public class DirectMessageThreadFragment extends Fragment {
@Override @Override
public void onMentionClick(final String mention) { public void onMentionClick(final String mention) {
final Bundle bundle = new Bundle(); navigateToUser(mention);
bundle.putString("username", "@" + mention);
NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(R.id.action_global_profileFragment, bundle);
} }
@Override @Override
@ -215,10 +217,17 @@ public class DirectMessageThreadFragment extends Fragment {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
} }
} }
@Override
public void onReactionClick(final DirectItem item, final int position) {
showReactionsDialog(item);
}
}; };
private final DirectItemLongClickListener directItemLongClickListener = position -> { private final DirectItemLongClickListener directItemLongClickListener = position -> {
// viewModel.setSelectedPosition(position); // viewModel.setSelectedPosition(position);
}; };
private DirectItemReactionDialogFragment reactionDialogFragment;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -1132,6 +1141,50 @@ public class DirectMessageThreadFragment extends Fragment {
} }
private void showReactionsDialog(final DirectItem item) {
final LiveData<List<User>> users = viewModel.getUsers();
final LiveData<List<User>> leftUsers = viewModel.getLeftUsers();
final ArrayList<User> allUsers = new ArrayList<>();
allUsers.add(viewModel.getCurrentUser());
if (users != null && users.getValue() != null) {
allUsers.addAll(users.getValue());
}
if (leftUsers != null && leftUsers.getValue() != null) {
allUsers.addAll(leftUsers.getValue());
}
reactionDialogFragment = DirectItemReactionDialogFragment
.newInstance(viewModel.getViewerId(),
allUsers,
item.getItemId(),
item.getReactions());
reactionDialogFragment.show(getChildFragmentManager(), "reactions_dialog");
}
@Override
public void onReactionClick(final String itemId, final DirectItemEmojiReaction reaction) {
if (reactionDialogFragment != null) {
reactionDialogFragment.dismiss();
}
if (reaction == null) return;
if (reaction.getSenderId() == viewModel.getViewerId()) {
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendDeleteReaction(itemId);
if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
}
return;
}
// navigate to user
final User user = viewModel.getUser(reaction.getSenderId());
if (user == null) return;
navigateToUser(user.getUsername());
}
private void navigateToUser(@NonNull final String username) {
final Bundle bundle = new Bundle();
bundle.putString("username", "@" + username);
NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(R.id.action_global_profileFragment, bundle);
}
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> { public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> {
private User user; private User user;
private DirectThread thread; private DirectThread thread;

View File

@ -1,8 +1,9 @@
package awais.instagrabber.repositories.responses.directmessages; package awais.instagrabber.repositories.responses.directmessages;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
public class DirectItemEmojiReaction { public class DirectItemEmojiReaction implements Serializable {
private final long senderId; private final long senderId;
private final long timestamp; private final long timestamp;
private final String emoji; private final String emoji;

View File

@ -2,10 +2,11 @@ package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
public class DirectItemReactions implements Cloneable { public class DirectItemReactions implements Cloneable, Serializable {
private List<DirectItemEmojiReaction> emojis; private List<DirectItemEmojiReaction> emojis;
private List<DirectItemEmojiReaction> likes; private List<DirectItemEmojiReaction> likes;

View File

@ -270,6 +270,13 @@ public final class EmojiParser {
return ALL_EMOJIS; return ALL_EMOJIS;
} }
public Emoji getEmoji(final String emoji) {
if (emoji == null) {
return null;
}
return ALL_EMOJIS.get(emoji);
}
// public String getMinorCategory(String emoji) { // public String getMinorCategory(String emoji) {
// String minorCat = emojiToMinorCategory.get(emoji); // String minorCat = emojiToMinorCategory.get(emoji);
// if (minorCat == null) { // if (minorCat == null) {

View File

@ -77,6 +77,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
private final MutableLiveData<String> threadTitle = new MutableLiveData<>(""); private final MutableLiveData<String> threadTitle = new MutableLiveData<>("");
private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false); private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false);
private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<User>> leftUsers = new MutableLiveData<>(new ArrayList<>());
private final DirectMessagesService service; private final DirectMessagesService service;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
@ -92,18 +93,19 @@ public class DirectThreadViewModel extends AndroidViewModel {
private User currentUser; private User currentUser;
private Call<DirectThreadFeedResponse> chatsRequest; private Call<DirectThreadFeedResponse> chatsRequest;
private VoiceRecorder voiceRecorder; private VoiceRecorder voiceRecorder;
private final long viewerId;
public DirectThreadViewModel(@NonNull final Application application) { public DirectThreadViewModel(@NonNull final Application application) {
super(application); super(application);
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
final long userId = CookieUtils.getUserIdFromCookie(cookie); viewerId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) { if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) {
throw new IllegalArgumentException("User is not logged in!"); throw new IllegalArgumentException("User is not logged in!");
} }
service = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid); service = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid);
mediaService = MediaService.getInstance(deviceUuid, csrfToken, userId); mediaService = MediaService.getInstance(deviceUuid, csrfToken, viewerId);
contentResolver = application.getContentResolver(); contentResolver = application.getContentResolver();
recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings"); recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings");
this.application = application; this.application = application;
@ -138,6 +140,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
return items; return items;
} }
public long getViewerId() {
return viewerId;
}
public void setItems(final List<DirectItem> items) { public void setItems(final List<DirectItem> items) {
this.items.postValue(items); this.items.postValue(items);
} }
@ -219,13 +225,53 @@ public class DirectThreadViewModel extends AndroidViewModel {
"none" "none"
); );
if (index < 0) { if (index < 0) {
temp.add(reaction); temp.add(0, reaction);
} else if (shouldReplaceIfAlreadyReacted) { } else if (shouldReplaceIfAlreadyReacted) {
temp.set(index, reaction); temp.add(0, reaction);
temp.remove(index);
} }
return temp; return temp;
} }
private void removeReaction(final DirectItem item) {
try {
final DirectItem itemClone = (DirectItem) item.clone();
final DirectItemReactions reactions = itemClone.getReactions();
final DirectItemReactions reactionsClone = (DirectItemReactions) reactions.clone();
final List<DirectItemEmojiReaction> likes = reactionsClone.getLikes();
if (likes != null) {
final List<DirectItemEmojiReaction> updatedLikes = likes.stream()
.filter(like -> like.getSenderId() != viewerId)
.collect(Collectors.toList());
reactionsClone.setLikes(updatedLikes);
}
final List<DirectItemEmojiReaction> emojis = reactionsClone.getEmojis();
if (emojis != null) {
final List<DirectItemEmojiReaction> updatedEmojis = emojis.stream()
.filter(emoji -> emoji.getSenderId() != viewerId)
.collect(Collectors.toList());
reactionsClone.setEmojis(updatedEmojis);
}
itemClone.setReactions(reactionsClone);
List<DirectItem> list = this.items.getValue();
list = list == null ? new LinkedList<>() : new LinkedList<>(list);
int index = -1;
for (int i = 0; i < list.size(); i++) {
final DirectItem directItem = list.get(i);
if (directItem.getItemId().equals(item.getItemId())) {
index = i;
break;
}
}
if (index >= 0) {
list.set(index, itemClone);
}
this.items.postValue(list);
} catch (Exception e) {
Log.e(TAG, "removeReaction: ", e);
}
}
private void updateItemSent(final String clientContext, final long timestamp) { private void updateItemSent(final String clientContext, final long timestamp) {
if (clientContext == null) return; if (clientContext == null) return;
List<DirectItem> list = this.items.getValue(); List<DirectItem> list = this.items.getValue();
@ -259,6 +305,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
return users; return users;
} }
public LiveData<List<User>> getLeftUsers() {
return leftUsers;
}
public void fetchChats() { public void fetchChats() {
final Boolean isFetching = fetching.getValue(); final Boolean isFetching = fetching.getValue();
if ((isFetching != null && isFetching) || !hasOlder) return; if ((isFetching != null && isFetching) || !hasOlder) return;
@ -319,9 +369,8 @@ public class DirectThreadViewModel extends AndroidViewModel {
threadTitle.postValue(thread.getThreadTitle()); threadTitle.postValue(thread.getThreadTitle());
cursor = thread.getOldestCursor(); cursor = thread.getOldestCursor();
hasOlder = thread.hasOlder(); hasOlder = thread.hasOlder();
if (users.getValue() == null || users.getValue().isEmpty()) { users.postValue(thread.getUsers());
users.postValue(thread.getUsers()); leftUsers.postValue(thread.getLeftUsers());
}
fetching.postValue(false); fetching.postValue(false);
} }
@ -637,6 +686,33 @@ public class DirectThreadViewModel extends AndroidViewModel {
} }
final Call<DirectThreadBroadcastResponse> request = service.broadcastReaction( final Call<DirectThreadBroadcastResponse> request = service.broadcastReaction(
clientContext, threadIdOrUserIds, item.getItemId(), emojiUnicode, false); clientContext, threadIdOrUserIds, item.getItemId(), emojiUnicode, false);
handleBroadcastReactionRequest(data, item, request);
return data;
}
public LiveData<Resource<DirectItem>> sendDeleteReaction(final String itemId) {
final MutableLiveData<Resource<DirectItem>> data = new MutableLiveData<>();
final DirectItem item = getItem(itemId);
if (item == null) {
data.postValue(Resource.error("Invalid item", null));
return data;
}
final DirectItemReactions reactions = item.getReactions();
if (reactions == null) {
// already removed?
data.postValue(Resource.success(item));
return data;
}
removeReaction(item);
final String clientContext = UUID.randomUUID().toString();
final Call<DirectThreadBroadcastResponse> request = service.broadcastReaction(clientContext, threadIdOrUserIds, item.getItemId(), null, true);
handleBroadcastReactionRequest(data, item, request);
return data;
}
private void handleBroadcastReactionRequest(final MutableLiveData<Resource<DirectItem>> data,
final DirectItem item,
@NonNull final Call<DirectThreadBroadcastResponse> request) {
request.enqueue(new Callback<DirectThreadBroadcastResponse>() { request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
@Override @Override
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call, public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call,
@ -662,13 +738,51 @@ public class DirectThreadViewModel extends AndroidViewModel {
Log.e(TAG, "enqueueRequest: onFailure: ", t); Log.e(TAG, "enqueueRequest: onFailure: ", t);
} }
}); });
return data; }
@Nullable
private DirectItem getItem(final String itemId) {
if (itemId == null) return null;
final List<DirectItem> items = this.items.getValue();
if (items == null) return null;
return items.stream()
.filter(directItem -> directItem.getItemId().equals(itemId))
.findFirst()
.orElse(null);
}
public User getCurrentUser() {
return currentUser;
} }
public void setCurrentUser(final User currentUser) { public void setCurrentUser(final User currentUser) {
this.currentUser = currentUser; this.currentUser = currentUser;
} }
@Nullable
public User getUser(final long userId) {
final LiveData<List<User>> users = getUsers();
User match = null;
if (users != null && users.getValue() != null) {
final List<User> userList = users.getValue();
match = userList.stream()
.filter(user -> user.getPk() == userId)
.findFirst()
.orElse(null);
}
if (match == null) {
final LiveData<List<User>> leftUsers = getLeftUsers();
if (leftUsers != null && leftUsers.getValue() != null) {
final List<User> userList = leftUsers.getValue();
match = userList.stream()
.filter(user -> user.getPk() == userId)
.findFirst()
.orElse(null);
}
}
return match;
}
private void enqueueRequest(@NonNull final Call<DirectThreadBroadcastResponse> request, private void enqueueRequest(@NonNull final Call<DirectThreadBroadcastResponse> request,
@NonNull final MutableLiveData<Resource<DirectItem>> data, @NonNull final MutableLiveData<Resource<DirectItem>> data,
@NonNull final DirectItem directItem) { @NonNull final DirectItem directItem) {

View File

@ -173,28 +173,34 @@
</awais.instagrabber.customviews.ChatMessageLayout> </awais.instagrabber.customviews.ChatMessageLayout>
<androidx.emoji.widget.EmojiAppCompatTextView <FrameLayout
android:id="@+id/emojis" android:id="@+id/reactions_wrapper"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:background="@drawable/bg_rounded_corner" android:paddingBottom="2dp"
android:elevation="1dp"
android:maxLines="1"
android:padding="4dp"
android:textColor="?android:textColorPrimary"
android:textSize="18sp"
android:translationY="@dimen/dm_reaction_translation_y_type_1" android:translationY="@dimen/dm_reaction_translation_y_type_1"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@id/chat_message_layout" app:layout_constraintStart_toStartOf="@id/chat_message_layout"
app:layout_constraintTop_toBottomOf="parent" app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintWidth_max="wrap" tools:visibility="visible">
tools:text="😀"
tools:visibility="visible" /> <awais.instagrabber.customviews.ReactionEmojiTextView
android:id="@+id/emojis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded_corner"
android:elevation="1dp"
android:ellipsize="end"
android:padding="4dp"
android:singleLine="true"
android:textColor="?android:textColorPrimary"
android:textSize="18sp"
tools:text="😀😀😀😀😀😀😀" />
</FrameLayout>
<!--<FrameLayout--> <!--<FrameLayout-->
<!-- android:id="@+id/reactions"--> <!-- android:id="@+id/reactions"-->

View File

@ -78,12 +78,26 @@
android:duplicateParentState="true" android:duplicateParentState="true"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/profile_pic" app:layout_constraintBottom_toBottomOf="@id/profile_pic"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/secondary_image"
app:layout_constraintStart_toEndOf="@id/info" app:layout_constraintStart_toEndOf="@id/info"
app:layout_constraintTop_toTopOf="@id/profile_pic" app:layout_constraintTop_toTopOf="@id/profile_pic"
app:srcCompat="@drawable/ic_circle_check" app:srcCompat="@drawable/ic_circle_check"
app:tint="@color/ic_circle_check_tint" app:tint="@color/ic_circle_check_tint"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/secondary_image"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="4dp"
android:duplicateParentState="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/profile_pic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/select"
app:layout_constraintTop_toTopOf="@id/profile_pic"
tools:srcCompat="@mipmap/ic_launcher"
tools:visibility="gone" />
<include <include
layout="@layout/item_pref_divider" layout="@layout/item_pref_divider"

View File

@ -32,7 +32,7 @@
<dimen name="dm_message_item_margin">80dp</dimen> <dimen name="dm_message_item_margin">80dp</dimen>
<dimen name="dm_message_item_avatar_size">48dp</dimen> <dimen name="dm_message_item_avatar_size">48dp</dimen>
<dimen name="dm_message_info_padding_small">4dp</dimen> <dimen name="dm_message_info_padding_small">4dp</dimen>
<dimen name="dm_reaction_adjust_margin">22dp</dimen> <dimen name="dm_reaction_adjust_margin">24dp</dimen>
<dimen name="dm_reaction_translation_y_type_1">6dp</dimen> <dimen name="dm_reaction_translation_y_type_1">6dp</dimen>
<dimen name="dm_reaction_translation_y_type_2">-12dp</dimen> <dimen name="dm_reaction_translation_y_type_2">-12dp</dimen>

View File

@ -397,4 +397,5 @@
<string name="edit_unsuccessful">Edit was unsuccessful</string> <string name="edit_unsuccessful">Edit was unsuccessful</string>
<string name="message">Message</string> <string name="message">Message</string>
<string name="reply">Reply</string> <string name="reply">Reply</string>
<string name="tap_to_remove">Tap to remove</string>
</resources> </resources>