mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-23 05:16:58 +00:00
Add like/reaction (WIP)
This commit is contained in:
parent
02a1a4a5f5
commit
cf62d88531
@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemAnimatedMediaViewHolder;
|
||||
@ -32,6 +33,7 @@ import awais.instagrabber.adapters.viewholder.directmessages.DirectItemTextViewH
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVideoCallEventViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder;
|
||||
import awais.instagrabber.customviews.emoji.Emoji;
|
||||
import awais.instagrabber.databinding.LayoutDmActionLogBinding;
|
||||
import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
@ -57,11 +59,14 @@ import awais.instagrabber.utils.DateUtils;
|
||||
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
|
||||
|
||||
private final User currentUser;
|
||||
private List<DirectItem> items;
|
||||
private DirectThread thread;
|
||||
private DirectItemViewHolder selectedViewHolder;
|
||||
|
||||
private final User currentUser;
|
||||
private final DirectItemCallback callback;
|
||||
private final AsyncListDiffer<DirectItemOrHeader> differ;
|
||||
private List<DirectItem> items;
|
||||
private final DirectItemInternalLongClickListener longClickListener;
|
||||
|
||||
private static final DiffUtil.ItemCallback<DirectItemOrHeader> diffCallback = new DiffUtil.ItemCallback<DirectItemOrHeader>() {
|
||||
@Override
|
||||
@ -96,21 +101,30 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
if (bothHeaders) {
|
||||
return oldItem.date.equals(newItem.date);
|
||||
}
|
||||
return oldItem.item.getTimestamp() == newItem.item.getTimestamp()
|
||||
&& oldItem.item.isPending() == newItem.item.isPending(); // todo need to be more specific
|
||||
final boolean timestampEqual = oldItem.item.getTimestamp() == newItem.item.getTimestamp();
|
||||
final boolean bothPending = oldItem.item.isPending() == newItem.item.isPending();
|
||||
final boolean reactionSame = Objects.equals(oldItem.item.getReactions(), newItem.item.getReactions());
|
||||
return timestampEqual && bothPending && reactionSame;
|
||||
}
|
||||
};
|
||||
|
||||
public DirectItemsAdapter(@NonNull final User currentUser,
|
||||
@NonNull final DirectThread thread,
|
||||
@NonNull final DirectItemCallback callback) {
|
||||
@NonNull final DirectItemCallback callback,
|
||||
@NonNull final DirectItemLongClickListener itemLongClickListener) {
|
||||
this.currentUser = currentUser;
|
||||
this.thread = thread;
|
||||
this.callback = callback;
|
||||
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
|
||||
new AsyncDifferConfig.Builder<>(diffCallback).build());
|
||||
// this.onClickListener = onClickListener;
|
||||
// this.mentionClickListener = mentionClickListener;
|
||||
longClickListener = (position, viewHolder) -> {
|
||||
if (selectedViewHolder != null) {
|
||||
selectedViewHolder.setSelected(false);
|
||||
}
|
||||
selectedViewHolder = viewHolder;
|
||||
viewHolder.setSelected(true);
|
||||
itemLongClickListener.onLongClick(position);
|
||||
};
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -123,7 +137,9 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
}
|
||||
final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
|
||||
final DirectItemType directItemType = DirectItemType.valueOf(type);
|
||||
return getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
||||
final DirectItemViewHolder itemViewHolder = getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
||||
itemViewHolder.setLongClickListener(longClickListener);
|
||||
return itemViewHolder;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -205,7 +221,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
return;
|
||||
}
|
||||
if (thread == null) return;
|
||||
((DirectItemViewHolder) holder).bind(itemOrHeader.item);
|
||||
((DirectItemViewHolder) holder).bind(position, itemOrHeader.item);
|
||||
}
|
||||
|
||||
protected DirectItemOrHeader getItem(int position) {
|
||||
@ -372,5 +388,15 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
void onMediaClick(Media media);
|
||||
|
||||
void onStoryClick(DirectItemStoryShare storyShare);
|
||||
|
||||
void onReaction(final DirectItem item, Emoji emoji);
|
||||
}
|
||||
|
||||
public interface DirectItemInternalLongClickListener {
|
||||
void onLongClick(int position, DirectItemViewHolder viewHolder);
|
||||
}
|
||||
|
||||
public interface DirectItemLongClickListener {
|
||||
void onLongClick(int position);
|
||||
}
|
||||
}
|
@ -176,14 +176,20 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
|
||||
subtitle = getMediaSpecificSubtitle(username, mediaType);
|
||||
break;
|
||||
}
|
||||
case STORY_SHARE:
|
||||
String format = "%s shared a story by @%s";
|
||||
if (item.getStoryShare().getReelType().equals("highlight_reel")) {
|
||||
format = "%s shared a story highlight by @%s";
|
||||
case STORY_SHARE: {
|
||||
final String reelType = item.getStoryShare().getReelType();
|
||||
if (reelType == null) {
|
||||
subtitle = item.getStoryShare().getTitle();
|
||||
} else {
|
||||
String format = "%s shared a story by @%s";
|
||||
if (reelType.equals("highlight_reel")) {
|
||||
format = "%s shared a story highlight by @%s";
|
||||
}
|
||||
subtitle = String.format(format, username != null ? username : "",
|
||||
item.getStoryShare().getMedia().getUser().getUsername());
|
||||
}
|
||||
subtitle = String.format(format, username != null ? username : "",
|
||||
item.getStoryShare().getMedia().getUser().getUsername());
|
||||
break;
|
||||
}
|
||||
case VOICE_MEDIA:
|
||||
subtitle = String.format("%s sent a voice message", username != null ? username : "");
|
||||
break;
|
||||
|
@ -70,6 +70,7 @@ public class DirectItemLinkViewHolder extends DirectItemViewHolder {
|
||||
setupRamboTextListeners(binding.text);
|
||||
final View.OnClickListener onClickListener = v -> openURL(linkContext.getLinkUrl());
|
||||
binding.preview.setOnClickListener(onClickListener);
|
||||
// binding.preview.setOnLongClickListener(v -> itemView.performLongClick());
|
||||
binding.title.setOnClickListener(onClickListener);
|
||||
binding.summary.setOnClickListener(onClickListener);
|
||||
binding.url.setOnClickListener(onClickListener);
|
||||
|
@ -12,6 +12,8 @@ import com.facebook.drawee.drawable.ScalingUtils;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.generic.RoundingParams;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
@ -29,6 +31,7 @@ import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
|
||||
public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
private static final String TAG = DirectItemMediaShareViewHolder.class.getSimpleName();
|
||||
|
||||
private final LayoutDmMediaShareBinding binding;
|
||||
private final RoundingParams incomingRoundingParams;
|
||||
@ -48,11 +51,6 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
|
||||
@Override
|
||||
public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
|
||||
final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING ? incomingRoundingParams : outgoingRoundingParams;
|
||||
binding.mediaPreview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
|
||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||
.setRoundingParams(roundingParams)
|
||||
.build());
|
||||
binding.topBg.setBackgroundResource(messageDirection == MessageDirection.INCOMING
|
||||
? R.drawable.bg_media_share_top_incoming
|
||||
: R.drawable.bg_media_share_top_outgoing);
|
||||
@ -67,10 +65,10 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
final MediaItemType mediaType = media.getMediaType();
|
||||
setupTypeIndicator(mediaType);
|
||||
if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
|
||||
setupPreview(media.getCarouselMedia().get(0));
|
||||
setupPreview(media.getCarouselMedia().get(0), messageDirection);
|
||||
return;
|
||||
}
|
||||
setupPreview(media);
|
||||
setupPreview(media, messageDirection);
|
||||
});
|
||||
itemView.setOnClickListener(v -> openMedia(media));
|
||||
}
|
||||
@ -87,7 +85,17 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPreview(@NonNull final Media media) {
|
||||
private void setupPreview(@NonNull final Media media,
|
||||
final MessageDirection messageDirection) {
|
||||
final String url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
|
||||
if (Objects.equals(url, binding.mediaPreview.getTag())) {
|
||||
return;
|
||||
}
|
||||
final RoundingParams roundingParams = messageDirection == MessageDirection.INCOMING ? incomingRoundingParams : outgoingRoundingParams;
|
||||
binding.mediaPreview.setHierarchy(new GenericDraweeHierarchyBuilder(itemView.getResources())
|
||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||
.setRoundingParams(roundingParams)
|
||||
.build());
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
media.getOriginalHeight(),
|
||||
media.getOriginalWidth(),
|
||||
@ -98,7 +106,7 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
binding.mediaPreview.requestLayout();
|
||||
final String url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
|
||||
binding.mediaPreview.setTag(url);
|
||||
binding.mediaPreview.setImageURI(url);
|
||||
}
|
||||
|
||||
@ -153,4 +161,9 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
}
|
||||
return media;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getReactionsTranslationY() {
|
||||
return reactionTranslationYType2;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,27 @@
|
||||
package awais.instagrabber.adapters.viewholder.directmessages;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.widget.ImageViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import com.google.android.material.transition.MaterialFade;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@ -20,6 +29,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemInternalLongClickListener;
|
||||
import awais.instagrabber.customviews.DirectItemContextMenu;
|
||||
import awais.instagrabber.customviews.DirectItemFrameLayout;
|
||||
import awais.instagrabber.customviews.RamboTextViewV2;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
import awais.instagrabber.models.enums.DirectItemType;
|
||||
@ -43,6 +55,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
private final int groupMessageWidth;
|
||||
private final List<Long> userIds;
|
||||
private final DirectItemCallback callback;
|
||||
private final int reactionAdjustMargin;
|
||||
private final AccelerateDecelerateInterpolator accelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
|
||||
|
||||
protected final int margin;
|
||||
protected final int dmRadius;
|
||||
@ -51,6 +65,14 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
protected final int mediaImageMaxHeight;
|
||||
protected final int windowWidth;
|
||||
protected final int mediaImageMaxWidth;
|
||||
protected final int reactionTranslationYType1;
|
||||
protected final int reactionTranslationYType2;
|
||||
|
||||
private boolean selected = false;
|
||||
private DirectItemInternalLongClickListener longClickListener;
|
||||
private DirectItem item;
|
||||
private ViewPropertyAnimator shrinkGrowAnimator;
|
||||
// private View.OnLayoutChangeListener layoutChangeListener;
|
||||
|
||||
public DirectItemViewHolder(@NonNull final LayoutDmBaseBinding binding,
|
||||
@NonNull final User currentUser,
|
||||
@ -75,16 +97,24 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
messageInfoPaddingSmall = resources.getDimensionPixelSize(R.dimen.dm_message_info_padding_small);
|
||||
windowWidth = resources.getDisplayMetrics().widthPixels;
|
||||
mediaImageMaxHeight = resources.getDimensionPixelSize(R.dimen.dm_media_img_max_height);
|
||||
reactionAdjustMargin = resources.getDimensionPixelSize(R.dimen.dm_reaction_adjust_margin);
|
||||
final int groupWidthCorrection = avatarSize + messageInfoPaddingSmall * 3;
|
||||
mediaImageMaxWidth = windowWidth - margin - (thread.isGroup() ? groupWidthCorrection : 0);
|
||||
mediaImageMaxWidth = windowWidth - margin - (thread.isGroup() ? groupWidthCorrection : messageInfoPaddingSmall * 2);
|
||||
// messageInfoPaddingSmall is used cuz it's also 4dp, 1 avatar margin + 2 paddings = 3
|
||||
groupMessageWidth = windowWidth - margin - groupWidthCorrection;
|
||||
reactionTranslationYType1 = resources.getDimensionPixelSize(R.dimen.dm_reaction_translation_y_type_1);
|
||||
reactionTranslationYType2 = resources.getDimensionPixelSize(R.dimen.dm_reaction_translation_y_type_2);
|
||||
}
|
||||
|
||||
public void bind(final DirectItem item) {
|
||||
public void bind(final int position, final DirectItem item) {
|
||||
this.item = item;
|
||||
final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
||||
itemView.post(() -> bindBase(item, messageDirection));
|
||||
itemView.post(() -> bindItem(item, messageDirection));
|
||||
itemView.post(() -> setupLongClickListener(position));
|
||||
// bindBase(item, messageDirection);
|
||||
// bindItem(item, messageDirection);
|
||||
// setupLongClickListener(position);
|
||||
}
|
||||
|
||||
private void bindBase(final DirectItem item, final MessageDirection messageDirection) {
|
||||
@ -104,7 +134,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
binding.messageInfo.setPadding(0, 0, messageInfoPaddingSmall, dmRadiusSmall);
|
||||
}
|
||||
setupReply(item, messageDirection);
|
||||
setReactions(item, thread.getUsers());
|
||||
setReactions(item);
|
||||
}
|
||||
|
||||
private void setBackground(final MessageDirection messageDirection) {
|
||||
@ -304,31 +334,39 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
|
||||
}
|
||||
|
||||
private void setReactions(final DirectItem item, final List<User> users) {
|
||||
final DirectItemReactions reactions = item.getReactions();
|
||||
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
|
||||
if (emojis == null || emojis.isEmpty()) {
|
||||
binding.reactions.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
binding.reactions.setVisibility(View.VISIBLE);
|
||||
final String emojisJoined = emojis.stream()
|
||||
.map(DirectItemEmojiReaction::getEmoji)
|
||||
.collect(Collectors.joining());
|
||||
final String text = String.format(Locale.ENGLISH, "%s %d", emojisJoined, emojis.size());
|
||||
binding.emojis.setText(text);
|
||||
// final List<DirectUser> reactedUsers = emojis.stream()
|
||||
// .map(DirectItemEmojiReaction::getSenderId)
|
||||
// .distinct()
|
||||
// .map(userId -> getUser(userId, users))
|
||||
// .collect(Collectors.toList());
|
||||
// for (final DirectUser user : reactedUsers) {
|
||||
// if (user == null) continue;
|
||||
// final ProfilePicView profilePicView = new ProfilePicView(itemView.getContext());
|
||||
// profilePicView.setSize(ProfilePicView.Size.TINY);
|
||||
// profilePicView.setImageURI(user.getProfilePicUrl());
|
||||
// binding.reactions.addView(profilePicView);
|
||||
// }
|
||||
private void setReactions(final DirectItem item) {
|
||||
binding.getRoot().post(() -> {
|
||||
MaterialFade materialFade = new MaterialFade();
|
||||
materialFade.addTarget(binding.emojis);
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot(), materialFade);
|
||||
final DirectItemReactions reactions = item.getReactions();
|
||||
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
|
||||
if (emojis == null || emojis.isEmpty()) {
|
||||
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall);
|
||||
binding.emojis.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
binding.emojis.setVisibility(View.VISIBLE);
|
||||
binding.emojis.setTranslationY(getReactionsTranslationY());
|
||||
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, reactionAdjustMargin);
|
||||
final String emojisJoined = emojis.stream()
|
||||
.map(DirectItemEmojiReaction::getEmoji)
|
||||
.collect(Collectors.joining());
|
||||
final String text = String.format(Locale.ENGLISH, "%s %d", emojisJoined, emojis.size());
|
||||
binding.emojis.setText(text);
|
||||
// final List<DirectUser> reactedUsers = emojis.stream()
|
||||
// .map(DirectItemEmojiReaction::getSenderId)
|
||||
// .distinct()
|
||||
// .map(userId -> getUser(userId, users))
|
||||
// .collect(Collectors.toList());
|
||||
// for (final DirectUser user : reactedUsers) {
|
||||
// if (user == null) continue;
|
||||
// final ProfilePicView profilePicView = new ProfilePicView(itemView.getContext());
|
||||
// profilePicView.setSize(ProfilePicView.Size.TINY);
|
||||
// profilePicView.setImageURI(user.getProfilePicUrl());
|
||||
// binding.reactions.addView(profilePicView);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isSelf(final DirectItem directItem) {
|
||||
@ -370,7 +408,28 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cleanup() {}
|
||||
protected boolean allowLongClick() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean allowReaction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected List<DirectItemContextMenu.MenuItem> getLongClickOptions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected int getReactionsTranslationY() {
|
||||
return reactionTranslationYType1;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void cleanup() {
|
||||
// if (layoutChangeListener != null) {
|
||||
// binding.container.removeOnLayoutChangeListener(layoutChangeListener);
|
||||
// }
|
||||
}
|
||||
|
||||
protected void setupRamboTextListeners(@NonNull final RamboTextViewV2 textView) {
|
||||
textView.addOnHashtagListener(autoLinkItem -> callback.onHashtagClick(autoLinkItem.getOriginalText().trim()));
|
||||
@ -410,6 +469,73 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void setupLongClickListener(final int position) {
|
||||
if (!allowLongClick()) return;
|
||||
binding.getRoot().setOnItemLongClickListener(new DirectItemFrameLayout.OnItemLongClickListener() {
|
||||
@Override
|
||||
public void onLongClickStart(final View view) {
|
||||
itemView.post(() -> shrink());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongClickCancel(final View view) {
|
||||
itemView.post(() -> grow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongClick(final View view, final float x, final float y) {
|
||||
// if (longClickListener == null) return false;
|
||||
// longClickListener.onLongClick(position, this);
|
||||
itemView.post(() -> grow());
|
||||
setSelected(true);
|
||||
showLongClickOptions(new Point((int) x, (int) y));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showLongClickOptions(final Point location) {
|
||||
final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), allowReaction(), getLongClickOptions());
|
||||
menu.setOnDismissListener(() -> setSelected(false));
|
||||
menu.setOnReactionClickListener(emoji -> {
|
||||
callback.onReaction(item, emoji);
|
||||
});
|
||||
menu.show(itemView, location);
|
||||
}
|
||||
|
||||
public void setLongClickListener(final DirectItemInternalLongClickListener longClickListener) {
|
||||
this.longClickListener = longClickListener;
|
||||
}
|
||||
|
||||
public void setSelected(final boolean selected) {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
private void shrink() {
|
||||
if (shrinkGrowAnimator != null) {
|
||||
shrinkGrowAnimator.cancel();
|
||||
}
|
||||
shrinkGrowAnimator = itemView.animate()
|
||||
.scaleX(0.8f)
|
||||
.scaleY(0.8f)
|
||||
.setInterpolator(accelerateDecelerateInterpolator)
|
||||
.setDuration(ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout());
|
||||
shrinkGrowAnimator.start();
|
||||
}
|
||||
|
||||
private void grow() {
|
||||
if (shrinkGrowAnimator != null) {
|
||||
shrinkGrowAnimator.cancel();
|
||||
}
|
||||
shrinkGrowAnimator = itemView.animate()
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.setInterpolator(accelerateDecelerateInterpolator)
|
||||
.setDuration(200)
|
||||
.withEndAction(() -> shrinkGrowAnimator = null);
|
||||
shrinkGrowAnimator.start();
|
||||
}
|
||||
|
||||
public enum MessageDirection {
|
||||
INCOMING,
|
||||
OUTGOING
|
||||
|
@ -154,6 +154,7 @@ public class DirectItemVoiceMediaViewHolder extends DirectItemViewHolder {
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
super.cleanup();
|
||||
if (handler != null && positionChecker != null) {
|
||||
handler.removeCallbacks(positionChecker);
|
||||
handler = null;
|
||||
|
@ -0,0 +1,84 @@
|
||||
package awais.instagrabber.animations;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
/**
|
||||
* A {@link ViewOutlineProvider} that has helper functions to create reveal animations.
|
||||
* This class should be extended so that subclasses can define the reveal shape as the
|
||||
* animation progresses from 0 to 1.
|
||||
*/
|
||||
public abstract class RevealOutlineAnimation extends ViewOutlineProvider {
|
||||
protected Rect mOutline;
|
||||
protected float mOutlineRadius;
|
||||
|
||||
public RevealOutlineAnimation() {
|
||||
mOutline = new Rect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether elevation should be removed for the duration of the reveal animation.
|
||||
*/
|
||||
abstract boolean shouldRemoveElevationDuringAnimation();
|
||||
|
||||
/**
|
||||
* Sets the progress, from 0 to 1, of the reveal animation.
|
||||
*/
|
||||
abstract void setProgress(float progress);
|
||||
|
||||
public ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
|
||||
ValueAnimator va =
|
||||
isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
|
||||
final float elevation = revealView.getElevation();
|
||||
|
||||
va.addListener(new AnimatorListenerAdapter() {
|
||||
private boolean mIsClippedToOutline;
|
||||
private ViewOutlineProvider mOldOutlineProvider;
|
||||
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsClippedToOutline = revealView.getClipToOutline();
|
||||
mOldOutlineProvider = revealView.getOutlineProvider();
|
||||
|
||||
revealView.setOutlineProvider(RevealOutlineAnimation.this);
|
||||
revealView.setClipToOutline(true);
|
||||
if (shouldRemoveElevationDuringAnimation()) {
|
||||
revealView.setTranslationZ(-elevation);
|
||||
}
|
||||
}
|
||||
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
revealView.setOutlineProvider(mOldOutlineProvider);
|
||||
revealView.setClipToOutline(mIsClippedToOutline);
|
||||
if (shouldRemoveElevationDuringAnimation()) {
|
||||
revealView.setTranslationZ(0);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
va.addUpdateListener(v -> {
|
||||
float progress = (Float) v.getAnimatedValue();
|
||||
setProgress(progress);
|
||||
revealView.invalidateOutline();
|
||||
});
|
||||
return va;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View v, Outline outline) {
|
||||
outline.setRoundRect(mOutline, mOutlineRadius);
|
||||
}
|
||||
|
||||
public float getRadius() {
|
||||
return mOutlineRadius;
|
||||
}
|
||||
|
||||
public void getOutline(Rect out) {
|
||||
out.set(mOutline);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package awais.instagrabber.animations;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
/**
|
||||
* A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii
|
||||
* and two {@link Rect}s.
|
||||
* <p>
|
||||
* An example usage of this provider is an outline that starts out as a circle and ends
|
||||
* as a rounded rectangle.
|
||||
*/
|
||||
public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation {
|
||||
private final float mStartRadius;
|
||||
private final float mEndRadius;
|
||||
|
||||
private final Rect mStartRect;
|
||||
private final Rect mEndRect;
|
||||
|
||||
public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, Rect endRect) {
|
||||
mStartRadius = startRadius;
|
||||
mEndRadius = endRadius;
|
||||
mStartRect = startRect;
|
||||
mEndRect = endRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRemoveElevationDuringAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(float progress) {
|
||||
mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius;
|
||||
|
||||
mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left);
|
||||
mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top);
|
||||
mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right);
|
||||
mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom);
|
||||
}
|
||||
}
|
@ -93,7 +93,8 @@ public class ChatMessageLayout extends FrameLayout {
|
||||
widthSize += viewPartMainWidth;
|
||||
heightSize += viewPartMainHeight;
|
||||
} else if (firstChildId == R.id.raven_media_container || firstChildId == R.id.profile_container || firstChildId == R.id.voice_media
|
||||
|| firstChildId == R.id.story_container || firstChildId == R.id.media_share_container || firstChildId == R.id.link_container) {
|
||||
|| firstChildId == R.id.story_container || firstChildId == R.id.media_share_container || firstChildId == R.id.link_container
|
||||
|| firstChildId == R.id.ivAnimatedMessage) {
|
||||
widthSize += viewPartMainWidth;
|
||||
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
||||
} else {
|
||||
|
@ -0,0 +1,457 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.animations.RoundedRectRevealOutlineProvider;
|
||||
import awais.instagrabber.customviews.emoji.Emoji;
|
||||
import awais.instagrabber.customviews.emoji.ReactionsManager;
|
||||
import awais.instagrabber.databinding.LayoutDirectItemOptionsBinding;
|
||||
|
||||
import static android.view.View.MeasureSpec.makeMeasureSpec;
|
||||
|
||||
public class DirectItemContextMenu extends PopupWindow {
|
||||
private static final String TAG = DirectItemContextMenu.class.getSimpleName();
|
||||
private static final int DO_NOT_UPDATE_FLAG = -1;
|
||||
private static final int DURATION = 300;
|
||||
|
||||
private final Context context;
|
||||
private final boolean showReactions;
|
||||
private final ReactionsManager reactionsManager;
|
||||
private final int emojiSize;
|
||||
private final int emojiMargin;
|
||||
private final int emojiMarginHalf;
|
||||
private final Rect startRect = new Rect();
|
||||
private final Rect endRect = new Rect();
|
||||
private final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
|
||||
private final AnimatorListenerAdapter exitAnimationListener;
|
||||
private final TypedValue selectableItemBackgroundBorderless;
|
||||
private final TypedValue selectableItemBackground;
|
||||
private final int dividerHeight;
|
||||
private final int optionHeight;
|
||||
private final int optionPadding;
|
||||
private final int addAdjust;
|
||||
private final boolean hasOptions;
|
||||
private final List<MenuItem> options;
|
||||
|
||||
/* = ImmutableList.of(
|
||||
new MenuItem(R.id.reply, R.string.reply),
|
||||
new MenuItem(R.id.unsend, R.string.dms_inbox_unsend)
|
||||
);*/
|
||||
private AnimatorSet openCloseAnimator;
|
||||
private Point location;
|
||||
private Point point;
|
||||
private OnReactionClickListener onReactionClickListener;
|
||||
private OnOptionSelectListener onOptionSelectListener;
|
||||
private OnAddReactionClickListener onAddReactionListener;
|
||||
|
||||
public DirectItemContextMenu(@NonNull final Context context, final boolean showReactions, final List<MenuItem> options) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.showReactions = showReactions;
|
||||
this.options = options;
|
||||
if (!showReactions && (options == null || options.isEmpty())) {
|
||||
throw new IllegalArgumentException("showReactions is set false and options are empty");
|
||||
}
|
||||
reactionsManager = ReactionsManager.getInstance();
|
||||
emojiSize = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_size);
|
||||
emojiMargin = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin);
|
||||
emojiMarginHalf = emojiMargin / 2;
|
||||
addAdjust = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_add_padding_adjustment);
|
||||
dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.horizontal_divider_height);
|
||||
optionHeight = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_option_height);
|
||||
optionPadding = context.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius);
|
||||
exitAnimationListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(final Animator animation) {
|
||||
openCloseAnimator = null;
|
||||
point = null;
|
||||
getContentView().post(DirectItemContextMenu.super::dismiss);
|
||||
}
|
||||
};
|
||||
selectableItemBackgroundBorderless = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, selectableItemBackgroundBorderless, true);
|
||||
selectableItemBackground = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, selectableItemBackground, true);
|
||||
hasOptions = options != null && !options.isEmpty();
|
||||
}
|
||||
|
||||
public void show(@NonNull View rootView, @NonNull final Point location) {
|
||||
final View content = createContentView();
|
||||
content.measure(makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
setup(content);
|
||||
// rootView.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
// final Point correctedLocation = new Point(location.x, location.y - emojiSize * 2);
|
||||
this.location = location;
|
||||
showAtLocation(rootView, Gravity.TOP | Gravity.START, location.x, location.y);
|
||||
// fixPopupLocation(popupWindow, correctedLocation);
|
||||
animateOpen();
|
||||
}
|
||||
|
||||
private void setup(final View content) {
|
||||
setContentView(content);
|
||||
setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
setFocusable(true);
|
||||
setOutsideTouchable(true);
|
||||
setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
|
||||
setBackgroundDrawable(null);
|
||||
}
|
||||
|
||||
public void setOnOptionSelectListener(final OnOptionSelectListener onOptionSelectListener) {
|
||||
this.onOptionSelectListener = onOptionSelectListener;
|
||||
}
|
||||
|
||||
public void setOnReactionClickListener(final OnReactionClickListener onReactionClickListener) {
|
||||
this.onReactionClickListener = onReactionClickListener;
|
||||
}
|
||||
|
||||
public void setOnAddReactionListener(final OnAddReactionClickListener onAddReactionListener) {
|
||||
this.onAddReactionListener = onAddReactionListener;
|
||||
}
|
||||
|
||||
private void animateOpen() {
|
||||
final View contentView = getContentView();
|
||||
contentView.setVisibility(View.INVISIBLE);
|
||||
contentView.post(() -> {
|
||||
final AnimatorSet openAnim = new AnimatorSet();
|
||||
// Rectangular reveal.
|
||||
final ValueAnimator revealAnim = createOpenCloseOutlineProvider().createRevealAnimator(contentView, false);
|
||||
revealAnim.setDuration(DURATION);
|
||||
revealAnim.setInterpolator(revealInterpolator);
|
||||
|
||||
ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1);
|
||||
fadeIn.setDuration(DURATION);
|
||||
fadeIn.setInterpolator(revealInterpolator);
|
||||
fadeIn.addUpdateListener(anim -> {
|
||||
float alpha = (float) anim.getAnimatedValue();
|
||||
contentView.setAlpha(revealAnim.isStarted() ? alpha : 0);
|
||||
});
|
||||
openAnim.play(fadeIn);
|
||||
openAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
contentView.setAlpha(1f);
|
||||
openCloseAnimator = null;
|
||||
}
|
||||
});
|
||||
|
||||
openCloseAnimator = openAnim;
|
||||
openAnim.playSequentially(revealAnim);
|
||||
contentView.setVisibility(View.VISIBLE);
|
||||
openAnim.start();
|
||||
});
|
||||
}
|
||||
|
||||
protected void animateClose() {
|
||||
endRect.setEmpty();
|
||||
if (openCloseAnimator != null) {
|
||||
openCloseAnimator.cancel();
|
||||
}
|
||||
final View contentView = getContentView();
|
||||
final AnimatorSet closeAnim = new AnimatorSet();
|
||||
// Rectangular reveal (reversed).
|
||||
final ValueAnimator revealAnim = createOpenCloseOutlineProvider().createRevealAnimator(contentView, true);
|
||||
revealAnim.setDuration(DURATION);
|
||||
revealAnim.setInterpolator(revealInterpolator);
|
||||
closeAnim.play(revealAnim);
|
||||
|
||||
ValueAnimator fadeOut = ValueAnimator.ofFloat(contentView.getAlpha(), 0);
|
||||
fadeOut.setDuration(DURATION);
|
||||
fadeOut.setInterpolator(revealInterpolator);
|
||||
fadeOut.addUpdateListener(anim -> {
|
||||
float alpha = (float) anim.getAnimatedValue();
|
||||
contentView.setAlpha(revealAnim.isStarted() ? alpha : contentView.getAlpha());
|
||||
});
|
||||
closeAnim.playTogether(fadeOut);
|
||||
closeAnim.addListener(exitAnimationListener);
|
||||
openCloseAnimator = closeAnim;
|
||||
closeAnim.start();
|
||||
}
|
||||
|
||||
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
|
||||
final View contentView = getContentView();
|
||||
final int radius = context.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius_small);
|
||||
// Log.d(TAG, "createOpenCloseOutlineProvider: " + locationOnScreen(contentView) + " " + contentView.getMeasuredWidth() + " " + contentView
|
||||
// .getMeasuredHeight());
|
||||
if (point == null) {
|
||||
point = locationOnScreen(contentView);
|
||||
}
|
||||
final int left = location.x - point.x;
|
||||
final int top = location.y - point.y;
|
||||
startRect.set(left, top, left, top);
|
||||
endRect.set(0, 0, contentView.getMeasuredWidth(), contentView.getMeasuredHeight());
|
||||
return new RoundedRectRevealOutlineProvider(radius, radius, startRect, endRect);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
animateClose();
|
||||
}
|
||||
|
||||
private View createContentView() {
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
||||
final LayoutDirectItemOptionsBinding binding = LayoutDirectItemOptionsBinding.inflate(layoutInflater, null, false);
|
||||
Pair<View, View> firstLastEmojiView = null;
|
||||
if (showReactions) {
|
||||
firstLastEmojiView = addReactions(layoutInflater, binding.container);
|
||||
}
|
||||
if (hasOptions) {
|
||||
View divider = null;
|
||||
if (showReactions) {
|
||||
if (firstLastEmojiView == null) {
|
||||
throw new IllegalStateException("firstLastEmojiView is null even though reactions were added");
|
||||
}
|
||||
// add divider if reactions were added
|
||||
divider = addDivider(binding.container,
|
||||
firstLastEmojiView.first.getId(),
|
||||
firstLastEmojiView.first.getId(),
|
||||
firstLastEmojiView.second.getId());
|
||||
((ConstraintLayout.LayoutParams) firstLastEmojiView.first.getLayoutParams()).bottomToTop = divider.getId();
|
||||
}
|
||||
addOptions(layoutInflater, binding.container, divider);
|
||||
}
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private Pair<View, View> addReactions(final LayoutInflater layoutInflater, final ConstraintLayout container) {
|
||||
final List<Emoji> reactions = reactionsManager.getReactions();
|
||||
AppCompatImageView prevSquareImageView = null;
|
||||
View firstImageView = null;
|
||||
View lastImageView = null;
|
||||
for (int i = 0; i < reactions.size(); i++) {
|
||||
final Emoji reaction = reactions.get(i);
|
||||
final AppCompatImageView imageView = getEmojiImageView();
|
||||
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) imageView.getLayoutParams();
|
||||
if (i == 0 && !hasOptions) {
|
||||
// only connect bottom to parent bottom if there are no options
|
||||
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
}
|
||||
if (i == 0) {
|
||||
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
firstImageView = imageView;
|
||||
layoutParams.setMargins(emojiMargin, emojiMargin, emojiMarginHalf, emojiMargin);
|
||||
} else {
|
||||
layoutParams.startToEnd = prevSquareImageView.getId();
|
||||
final ConstraintLayout.LayoutParams prevViewLayoutParams = (ConstraintLayout.LayoutParams) prevSquareImageView.getLayoutParams();
|
||||
prevViewLayoutParams.endToStart = imageView.getId();
|
||||
// always connect the other image view's top and bottom to the first image view top and bottom
|
||||
layoutParams.topToTop = firstImageView.getId();
|
||||
layoutParams.bottomToBottom = firstImageView.getId();
|
||||
layoutParams.setMargins(emojiMarginHalf, emojiMargin, emojiMarginHalf, emojiMargin);
|
||||
}
|
||||
imageView.setImageDrawable(reaction.getDrawable());
|
||||
imageView.setOnClickListener(view -> {
|
||||
if (onReactionClickListener != null) {
|
||||
onReactionClickListener.onClick(reaction);
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
container.addView(imageView);
|
||||
prevSquareImageView = imageView;
|
||||
}
|
||||
// add the + icon
|
||||
if (prevSquareImageView != null) {
|
||||
final AppCompatImageView imageView = getEmojiImageView();
|
||||
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) imageView.getLayoutParams();
|
||||
layoutParams.topToTop = firstImageView.getId();
|
||||
layoutParams.bottomToBottom = firstImageView.getId();
|
||||
layoutParams.startToEnd = prevSquareImageView.getId();
|
||||
final ConstraintLayout.LayoutParams prevViewLayoutParams = (ConstraintLayout.LayoutParams) prevSquareImageView.getLayoutParams();
|
||||
prevViewLayoutParams.endToStart = imageView.getId();
|
||||
layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
layoutParams.setMargins(emojiMarginHalf - addAdjust, emojiMargin - addAdjust, emojiMargin - addAdjust, emojiMargin - addAdjust);
|
||||
imageView.setImageResource(R.drawable.ic_add);
|
||||
imageView.setOnClickListener(view -> {
|
||||
if (onAddReactionListener != null) {
|
||||
onAddReactionListener.onAdd();
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
lastImageView = imageView;
|
||||
container.addView(imageView);
|
||||
}
|
||||
return new Pair<>(firstImageView, lastImageView);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private AppCompatImageView getEmojiImageView() {
|
||||
final AppCompatImageView imageView = new AppCompatImageView(context);
|
||||
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(emojiSize, emojiSize);
|
||||
imageView.setBackgroundResource(selectableItemBackgroundBorderless.resourceId);
|
||||
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
imageView.setId(SquareImageView.generateViewId());
|
||||
imageView.setLayoutParams(layoutParams);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
private void addOptions(final LayoutInflater layoutInflater,
|
||||
final ConstraintLayout container,
|
||||
@Nullable final View divider) {
|
||||
View prevOptionView = null;
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
final MenuItem menuItem = options.get(i);
|
||||
final AppCompatTextView textView = getTextView();
|
||||
final ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) textView.getLayoutParams();
|
||||
layoutParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
layoutParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
if (i == 0) {
|
||||
if (divider != null) {
|
||||
layoutParams.topToBottom = divider.getId();
|
||||
} else {
|
||||
// if divider is null mean reactions were not added, so connect top to top of parent
|
||||
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
}
|
||||
((ConstraintLayout.LayoutParams) divider.getLayoutParams()).bottomToTop = textView.getId();
|
||||
} else {
|
||||
layoutParams.topToBottom = prevOptionView.getId();
|
||||
final ConstraintLayout.LayoutParams prevLayoutParams = (ConstraintLayout.LayoutParams) prevOptionView.getLayoutParams();
|
||||
prevLayoutParams.bottomToTop = textView.getId();
|
||||
}
|
||||
if (i == options.size() - 1) {
|
||||
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
layoutParams.bottomMargin = emojiMargin; // material design spec (https://material.io/components/menus#specs)
|
||||
}
|
||||
textView.setText(context.getString(menuItem.getTitleRes()));
|
||||
textView.setOnClickListener(v -> {
|
||||
if (onOptionSelectListener != null) {
|
||||
onOptionSelectListener.onSelect(menuItem.getItemId());
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
container.addView(textView);
|
||||
prevOptionView = textView;
|
||||
}
|
||||
}
|
||||
|
||||
private AppCompatTextView getTextView() {
|
||||
final AppCompatTextView textView = new AppCompatTextView(context);
|
||||
textView.setId(AppCompatEditText.generateViewId());
|
||||
textView.setBackgroundResource(selectableItemBackground.resourceId);
|
||||
textView.setGravity(Gravity.CENTER_VERTICAL);
|
||||
textView.setPaddingRelative(optionPadding, 0, optionPadding, 0);
|
||||
textView.setTextAppearance(context, R.style.TextAppearance_MaterialComponents_Body1);
|
||||
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
|
||||
optionHeight);
|
||||
textView.setLayoutParams(layoutParams);
|
||||
return textView;
|
||||
}
|
||||
|
||||
private View addDivider(final ConstraintLayout container,
|
||||
final int topViewId,
|
||||
final int startViewId,
|
||||
final int endViewId) {
|
||||
final View dividerView = new View(context);
|
||||
dividerView.setId(View.generateViewId());
|
||||
dividerView.setBackgroundResource(R.drawable.pref_list_divider_material);
|
||||
final ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT,
|
||||
dividerHeight);
|
||||
layoutParams.topToBottom = topViewId;
|
||||
layoutParams.startToStart = startViewId;
|
||||
layoutParams.endToEnd = endViewId;
|
||||
dividerView.setLayoutParams(layoutParams);
|
||||
container.addView(dividerView);
|
||||
return dividerView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Point locationOnScreen(@NonNull final View view) {
|
||||
final int[] location = new int[2];
|
||||
view.getLocationOnScreen(location);
|
||||
return new Point(location[0], location[1]);
|
||||
}
|
||||
|
||||
public static class MenuItem {
|
||||
@IdRes
|
||||
private final int itemId;
|
||||
@StringRes
|
||||
private final int titleRes;
|
||||
|
||||
public MenuItem(@IdRes final int itemId, @StringRes final int titleRes) {
|
||||
this.itemId = itemId;
|
||||
this.titleRes = titleRes;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public int getTitleRes() {
|
||||
return titleRes;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnOptionSelectListener {
|
||||
void onSelect(int itemId);
|
||||
}
|
||||
|
||||
public interface OnReactionClickListener {
|
||||
void onClick(Emoji emoji);
|
||||
}
|
||||
|
||||
public interface OnAddReactionClickListener {
|
||||
void onAdd();
|
||||
}
|
||||
|
||||
// @NonNull
|
||||
// private Rect getGlobalVisibleRect(@NonNull final View view) {
|
||||
// final Rect rect = new Rect();
|
||||
// view.getGlobalVisibleRect(rect);
|
||||
// return rect;
|
||||
// }
|
||||
|
||||
// private void fixPopupLocation(@NonNull final PopupWindow popupWindow, @NonNull final Point desiredLocation) {
|
||||
// popupWindow.getContentView().post(() -> {
|
||||
// final Point actualLocation = locationOnScreen(popupWindow.getContentView());
|
||||
//
|
||||
// if (!(actualLocation.x == desiredLocation.x && actualLocation.y == desiredLocation.y)) {
|
||||
// final int differenceX = actualLocation.x - desiredLocation.x;
|
||||
// final int differenceY = actualLocation.y - desiredLocation.y;
|
||||
//
|
||||
// final int fixedOffsetX;
|
||||
// final int fixedOffsetY;
|
||||
//
|
||||
// if (actualLocation.x > desiredLocation.x) {
|
||||
// fixedOffsetX = desiredLocation.x - differenceX;
|
||||
// } else {
|
||||
// fixedOffsetX = desiredLocation.x + differenceX;
|
||||
// }
|
||||
//
|
||||
// if (actualLocation.y > desiredLocation.y) {
|
||||
// fixedOffsetY = desiredLocation.y - differenceY;
|
||||
// } else {
|
||||
// fixedOffsetY = desiredLocation.y + differenceY;
|
||||
// }
|
||||
//
|
||||
// popupWindow.update(fixedOffsetX, fixedOffsetY, DO_NOT_UPDATE_FLAG, DO_NOT_UPDATE_FLAG);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class DirectItemFrameLayout extends FrameLayout {
|
||||
private static final String TAG = DirectItemFrameLayout.class.getSimpleName();
|
||||
|
||||
private boolean longPressed = false;
|
||||
private float touchX;
|
||||
private float touchY;
|
||||
private OnItemLongClickListener onItemLongClickListener;
|
||||
private int touchSlop;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
private final Runnable longPressRunnable = () -> {
|
||||
longPressed = true;
|
||||
if (onItemLongClickListener != null) {
|
||||
onItemLongClickListener.onLongClick(this, touchX, touchY);
|
||||
}
|
||||
};
|
||||
private final Runnable longPressStartRunnable = () -> {
|
||||
if (onItemLongClickListener != null) {
|
||||
onItemLongClickListener.onLongClickStart(this);
|
||||
}
|
||||
};
|
||||
|
||||
public DirectItemFrameLayout(@NonNull final Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public DirectItemFrameLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(final Context context) {
|
||||
ViewConfiguration vc = ViewConfiguration.get(context);
|
||||
touchSlop = vc.getScaledTouchSlop();
|
||||
}
|
||||
|
||||
public void setOnItemLongClickListener(final OnItemLongClickListener onItemLongClickListener) {
|
||||
this.onItemLongClickListener = onItemLongClickListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(final MotionEvent ev) {
|
||||
switch (ev.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
longPressed = false;
|
||||
handler.postDelayed(longPressRunnable, ViewConfiguration.getLongPressTimeout());
|
||||
handler.postDelayed(longPressStartRunnable, ViewConfiguration.getTapTimeout());
|
||||
touchX = ev.getRawX();
|
||||
touchY = ev.getRawY();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (longPressed || Math.abs(touchX - ev.getRawX()) > touchSlop || Math.abs(touchY - ev.getRawY()) > touchSlop) {
|
||||
handler.removeCallbacks(longPressStartRunnable);
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
handler.removeCallbacks(longPressStartRunnable);
|
||||
if (longPressed) {
|
||||
return true;
|
||||
}
|
||||
if (onItemLongClickListener != null) {
|
||||
onItemLongClickListener.onLongClickCancel(this);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
handler.removeCallbacks(longPressStartRunnable);
|
||||
break;
|
||||
}
|
||||
final boolean dispatchTouchEvent = super.dispatchTouchEvent(ev);
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN && !dispatchTouchEvent) {
|
||||
return true;
|
||||
}
|
||||
return dispatchTouchEvent;
|
||||
}
|
||||
|
||||
public interface OnItemLongClickListener {
|
||||
void onLongClickStart(View view);
|
||||
|
||||
void onLongClickCancel(View view);
|
||||
|
||||
void onLongClick(View view, float x, float y);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.IBinder;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
/**
|
||||
* https://stackoverflow.com/a/15766097/1436766
|
||||
*/
|
||||
public class PopupDialog extends Dialog {
|
||||
private final Context context;
|
||||
|
||||
public PopupDialog(Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
}
|
||||
|
||||
public void showAtLocation(final IBinder token, final int gravity, int x, int y) {
|
||||
final Window window = getWindow();
|
||||
if (window == null) return;
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
layoutParams.gravity = gravity;
|
||||
layoutParams.x = x;
|
||||
layoutParams.y = y;
|
||||
// layoutParams.token = token;
|
||||
show();
|
||||
}
|
||||
|
||||
public void showAsDropDown(View view) {
|
||||
float density = Utils.displayMetrics.density;
|
||||
final Window window = getWindow();
|
||||
if (window == null) return;
|
||||
WindowManager.LayoutParams layoutParams = window.getAttributes();
|
||||
int[] location = new int[2];
|
||||
view.getLocationInWindow(location);
|
||||
layoutParams.gravity = Gravity.TOP | Gravity.START;
|
||||
layoutParams.x = location[0] + (int) (view.getWidth() / density);
|
||||
layoutParams.y = location[1] + (int) (view.getHeight() / density);
|
||||
show();
|
||||
}
|
||||
|
||||
public void setBackgroundDrawable(final Drawable drawable) {
|
||||
final Window window = getWindow();
|
||||
if (window == null) return;
|
||||
window.setBackgroundDrawable(drawable);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package awais.instagrabber.customviews.emoji;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.utils.AppExecutors;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.utils.emoji.EmojiParser;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.PREF_REACTIONS;
|
||||
|
||||
public class ReactionsManager {
|
||||
private static final String TAG = ReactionsManager.class.getSimpleName();
|
||||
private static final Object LOCK = new Object();
|
||||
|
||||
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||
private final List<Emoji> reactions = new ArrayList<>();
|
||||
|
||||
private static ReactionsManager instance;
|
||||
|
||||
public static ReactionsManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (LOCK) {
|
||||
if (instance == null) {
|
||||
instance = new ReactionsManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ReactionsManager() {
|
||||
String reactionsJson = Utils.settingsHelper.getString(PREF_REACTIONS);
|
||||
if (TextUtils.isEmpty(reactionsJson)) {
|
||||
final ImmutableList<String> list = ImmutableList.of("❤️", "\uD83D\uDE02", "\uD83D\uDE2E", "\uD83D\uDE22", "\uD83D\uDE21", "\uD83D\uDC4D");
|
||||
reactionsJson = new JSONArray(list).toString();
|
||||
}
|
||||
final EmojiParser emojiParser = EmojiParser.getInstance();
|
||||
final Map<String, Emoji> allEmojis = emojiParser.getAllEmojis();
|
||||
try {
|
||||
final JSONArray reactionsJsonArray = new JSONArray(reactionsJson);
|
||||
for (int i = 0; i < reactionsJsonArray.length(); i++) {
|
||||
final String emojiUnicode = reactionsJsonArray.optString(i);
|
||||
if (emojiUnicode == null) continue;
|
||||
final Emoji emoji = allEmojis.get(emojiUnicode);
|
||||
if (emoji == null) continue;
|
||||
reactions.add(emoji);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "ReactionsManager: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Emoji> getReactions() {
|
||||
return reactions;
|
||||
}
|
||||
|
||||
// public void setVariant(final String parent, final String variant) {
|
||||
// if (parent == null || variant == null) return;
|
||||
// selectedVariantMap.put(parent, variant);
|
||||
// appExecutors.tasksThread().execute(() -> {
|
||||
// final JSONObject jsonObject = new JSONObject(selectedVariantMap);
|
||||
// final String json = jsonObject.toString();
|
||||
// Utils.settingsHelper.putString(PREF_EMOJI_VARIANTS, json);
|
||||
// });
|
||||
// }
|
||||
}
|
@ -57,11 +57,14 @@ import awais.instagrabber.R;
|
||||
import awais.instagrabber.activities.CameraActivity;
|
||||
import awais.instagrabber.activities.MainActivity;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemLongClickListener;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader;
|
||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
||||
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.helpers.HeaderItemDecoration;
|
||||
import awais.instagrabber.customviews.helpers.HeightProvider;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||
@ -133,7 +136,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
setMicToSendIcon();
|
||||
}
|
||||
};
|
||||
private final DirectItemsAdapter.DirectItemCallback directItemCallback = new DirectItemsAdapter.DirectItemCallback() {
|
||||
private final DirectItemCallback directItemCallback = new DirectItemCallback() {
|
||||
@Override
|
||||
public void onHashtagClick(final String hashtag) {
|
||||
final NavDirections action = DirectMessageThreadFragmentDirections.actionGlobalHashTagFragment(hashtag);
|
||||
@ -188,6 +191,18 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
Log.e(TAG, "onStoryClick: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReaction(final DirectItem item, final Emoji emoji) {
|
||||
if (item == null) return;
|
||||
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendReaction(item, emoji);
|
||||
if (resourceLiveData != null) {
|
||||
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
|
||||
}
|
||||
}
|
||||
};
|
||||
private final DirectItemLongClickListener directItemLongClickListener = position -> {
|
||||
// viewModel.setSelectedPosition(position);
|
||||
};
|
||||
|
||||
@Override
|
||||
@ -226,6 +241,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
init();
|
||||
binding.send.post(() -> initialSendX = binding.send.getX());
|
||||
shouldRefresh = false;
|
||||
setObservers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -309,7 +325,6 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
binding.input.post(this::showKeyboard);
|
||||
wasKbShowing = false;
|
||||
}
|
||||
setObservers();
|
||||
if (initialSendX != 0) {
|
||||
binding.send.setX(initialSendX);
|
||||
}
|
||||
@ -354,7 +369,6 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
setupList();
|
||||
root.post(this::setupInput);
|
||||
root.post(this::getInitialData);
|
||||
setObservers();
|
||||
}
|
||||
|
||||
private void getInitialData() {
|
||||
@ -578,7 +592,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
itemsAdapter.setThread(thread);
|
||||
return;
|
||||
}
|
||||
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback);
|
||||
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener);
|
||||
itemsAdapter.setHasStableIds(true);
|
||||
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||
binding.chats.setAdapter(itemsAdapter);
|
||||
@ -694,7 +708,8 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
binding.send.setOnRecordClickListener(v -> {
|
||||
final Editable text = binding.input.getText();
|
||||
if (TextUtils.isEmpty(text)) return;
|
||||
viewModel.sendText(text.toString()).observe(getViewLifecycleOwner(), this::handleSentMessage);
|
||||
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendText(text.toString());
|
||||
resourceLiveData.observe(getViewLifecycleOwner(), resource -> handleSentMessage(resourceLiveData));
|
||||
binding.input.setText("");
|
||||
});
|
||||
binding.send.setOnRecordLongClickListener(v -> {
|
||||
@ -754,16 +769,21 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
navController.navigate(navDirections);
|
||||
}
|
||||
|
||||
private void handleSentMessage(@NonNull final Resource<DirectItem> resource) {
|
||||
private void handleSentMessage(final LiveData<Resource<DirectItem>> resourceLiveData) {
|
||||
final Resource<DirectItem> resource = resourceLiveData.getValue();
|
||||
if (resource == null) return;
|
||||
final Resource.Status status = resource.status;
|
||||
switch (status) {
|
||||
case SUCCESS:
|
||||
resourceLiveData.removeObservers(getViewLifecycleOwner());
|
||||
break;
|
||||
case LOADING:
|
||||
break;
|
||||
case ERROR:
|
||||
if (resource.message != null) {
|
||||
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
resourceLiveData.removeObservers(getViewLifecycleOwner());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1093,6 +1113,10 @@ public class DirectMessageThreadFragment extends Fragment {
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
private void showLongClickOptions(final View itemView) {
|
||||
|
||||
}
|
||||
|
||||
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> {
|
||||
private User user;
|
||||
private DirectThread thread;
|
||||
|
@ -4,17 +4,21 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.models.enums.BroadcastItemType;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
public class ReactionBroadcastOptions extends BroadcastOptions {
|
||||
private final String itemId;
|
||||
private final String emoji;
|
||||
private final boolean delete;
|
||||
|
||||
public ReactionBroadcastOptions(final String clientContext,
|
||||
final ThreadIdOrUserIds threadIdOrUserIds,
|
||||
final String itemId,
|
||||
final String emoji,
|
||||
final boolean delete) {
|
||||
super(clientContext, threadIdOrUserIds, BroadcastItemType.REACTION);
|
||||
this.itemId = itemId;
|
||||
this.emoji = emoji;
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
@ -24,6 +28,9 @@ public class ReactionBroadcastOptions extends BroadcastOptions {
|
||||
form.put("item_id", itemId);
|
||||
form.put("reaction_status", delete ? "deleted" : "created");
|
||||
form.put("reaction_type", "like");
|
||||
if (!TextUtils.isEmpty(emoji)) {
|
||||
form.put("emoji", emoji);
|
||||
}
|
||||
return form;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public class DirectItem implements Cloneable {
|
||||
private final DirectItemFelixShare felixShare;
|
||||
private final DirectItemVisualMedia visualMedia;
|
||||
private final DirectItemAnimatedMedia animatedMedia;
|
||||
private final DirectItemReactions reactions;
|
||||
private DirectItemReactions reactions;
|
||||
private final DirectItem repliedToMessage;
|
||||
private final DirectItemVoiceMedia voiceMedia;
|
||||
private final Location location;
|
||||
@ -218,6 +218,10 @@ public class DirectItem implements Cloneable {
|
||||
isPending = pending;
|
||||
}
|
||||
|
||||
public void setReactions(final DirectItemReactions reactions) {
|
||||
this.reactions = reactions;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package awais.instagrabber.repositories.responses.directmessages;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DirectItemEmojiReaction {
|
||||
private final long senderId;
|
||||
private final long timestamp;
|
||||
@ -28,4 +30,20 @@ public class DirectItemEmojiReaction {
|
||||
public String getSuperReactType() {
|
||||
return superReactType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final DirectItemEmojiReaction that = (DirectItemEmojiReaction) o;
|
||||
return senderId == that.senderId &&
|
||||
timestamp == that.timestamp &&
|
||||
Objects.equals(emoji, that.emoji) &&
|
||||
Objects.equals(superReactType, that.superReactType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(senderId, timestamp, emoji, superReactType);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package awais.instagrabber.repositories.responses.directmessages;
|
||||
|
||||
import java.util.List;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class DirectItemReactions {
|
||||
private final List<DirectItemEmojiReaction> emojis;
|
||||
private final List<DirectItemEmojiReaction> likes;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class DirectItemReactions implements Cloneable {
|
||||
private List<DirectItemEmojiReaction> emojis;
|
||||
private List<DirectItemEmojiReaction> likes;
|
||||
|
||||
public DirectItemReactions(final List<DirectItemEmojiReaction> emojis,
|
||||
final List<DirectItemEmojiReaction> likes) {
|
||||
@ -19,4 +22,32 @@ public class DirectItemReactions {
|
||||
public List<DirectItemEmojiReaction> getLikes() {
|
||||
return likes;
|
||||
}
|
||||
|
||||
public void setLikes(final List<DirectItemEmojiReaction> likes) {
|
||||
this.likes = likes;
|
||||
}
|
||||
|
||||
public void setEmojis(final List<DirectItemEmojiReaction> emojis) {
|
||||
this.emojis = emojis;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final DirectItemReactions that = (DirectItemReactions) o;
|
||||
return Objects.equals(emojis, that.emojis) &&
|
||||
Objects.equals(likes, that.likes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(emojis, likes);
|
||||
}
|
||||
}
|
||||
|
@ -95,4 +95,5 @@ public final class Constants {
|
||||
public static final String PREF_TAGGED_POSTS_LAYOUT = "tagged_posts_layout";
|
||||
public static final String PREF_SAVED_POSTS_LAYOUT = "saved_posts_layout";
|
||||
public static final String PREF_EMOJI_VARIANTS = "emoji_variants";
|
||||
public static final String PREF_REACTIONS = "reactions";
|
||||
}
|
@ -34,6 +34,7 @@ import static awais.instagrabber.utils.Constants.PREF_LIKED_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_LOCATION_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_PROFILE_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_REACTIONS;
|
||||
import static awais.instagrabber.utils.Constants.PREF_SAVED_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_TAGGED_POSTS_LAYOUT;
|
||||
import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT;
|
||||
@ -123,7 +124,7 @@ public final class SettingsHelper {
|
||||
{APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT,
|
||||
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT,
|
||||
PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT,
|
||||
PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, STORY_SORT, PREF_EMOJI_VARIANTS})
|
||||
PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS})
|
||||
public @interface StringSettings {}
|
||||
|
||||
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
|
||||
|
@ -14,6 +14,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -51,6 +52,7 @@ public final class EmojiParser {
|
||||
private static final UnicodeSet SKIN_TONE_MODIFIERS = new UnicodeSet("[🏻-🏿]").freeze();
|
||||
private static final String SKIN_TONE_PATTERN = SKIN_TONE_MODIFIERS.toPattern(true);
|
||||
private static final Map<EmojiCategoryType, EmojiCategory> CATEGORY_MAP = new LinkedHashMap<>();
|
||||
private static final Map<String, Emoji> ALL_EMOJIS = new HashMap<>();
|
||||
|
||||
// private final UnicodeMap<String> emojiToMajorCategory = new UnicodeMap<>();
|
||||
// private final UnicodeMap<String> emojiToMinorCategory = new UnicodeMap<>();
|
||||
@ -201,6 +203,7 @@ public final class EmojiParser {
|
||||
spacePos = comment.indexOf(' ', spacePos + 1); // get second space
|
||||
final String name = comment.substring(spacePos + 1).trim();
|
||||
final Emoji emoji = new Emoji(original, name);
|
||||
ALL_EMOJIS.put(original, emoji);
|
||||
String minimal = original.replace(EMOJI_VARIANT, "");
|
||||
//noinspection deprecation
|
||||
boolean singleton = CharSequences.getSingleCodePoint(minimal) != Integer.MAX_VALUE;
|
||||
@ -263,6 +266,10 @@ public final class EmojiParser {
|
||||
return categories;
|
||||
}
|
||||
|
||||
public Map<String, Emoji> getAllEmojis() {
|
||||
return ALL_EMOJIS;
|
||||
}
|
||||
|
||||
// public String getMinorCategory(String emoji) {
|
||||
// String minorCat = emojiToMinorCategory.get(emoji);
|
||||
// if (minorCat == null) {
|
||||
|
@ -30,12 +30,15 @@ import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import awais.instagrabber.customviews.emoji.Emoji;
|
||||
import awais.instagrabber.models.Resource;
|
||||
import awais.instagrabber.models.UploadVideoOptions;
|
||||
import awais.instagrabber.repositories.requests.UploadFinishOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponseMessageMetadata;
|
||||
@ -74,6 +77,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
private final MutableLiveData<String> threadTitle = new MutableLiveData<>("");
|
||||
private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
private final DirectMessagesService service;
|
||||
private final ContentResolver contentResolver;
|
||||
private final MediaService mediaService;
|
||||
@ -154,6 +158,74 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
this.items.postValue(list);
|
||||
}
|
||||
|
||||
private void addReaction(final DirectItem item, final Emoji emoji) {
|
||||
if (item == null || emoji == null || currentUser == null) return;
|
||||
final boolean isLike = emoji.getUnicode().equals("❤️");
|
||||
DirectItemReactions reactions = item.getReactions();
|
||||
if (reactions == null) {
|
||||
reactions = new DirectItemReactions(null, null);
|
||||
} else {
|
||||
try {
|
||||
reactions = (DirectItemReactions) reactions.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
Log.e(TAG, "addReaction: ", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isLike) {
|
||||
final List<DirectItemEmojiReaction> likes = addEmoji(reactions.getLikes(), null, false);
|
||||
reactions.setLikes(likes);
|
||||
}
|
||||
final List<DirectItemEmojiReaction> emojis = addEmoji(reactions.getEmojis(), emoji.getUnicode(), true);
|
||||
reactions.setEmojis(emojis);
|
||||
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) {
|
||||
try {
|
||||
final DirectItem clone = (DirectItem) list.get(index).clone();
|
||||
clone.setReactions(reactions);
|
||||
list.set(index, clone);
|
||||
} catch (CloneNotSupportedException e) {
|
||||
Log.e(TAG, "addReaction: error cloning", e);
|
||||
}
|
||||
}
|
||||
this.items.postValue(list);
|
||||
}
|
||||
|
||||
private List<DirectItemEmojiReaction> addEmoji(final List<DirectItemEmojiReaction> reactionList,
|
||||
final String emoji,
|
||||
final boolean shouldReplaceIfAlreadyReacted) {
|
||||
final List<DirectItemEmojiReaction> temp = reactionList == null ? new ArrayList<>() : new ArrayList<>(reactionList);
|
||||
int index = -1;
|
||||
for (int i = 0; i < temp.size(); i++) {
|
||||
final DirectItemEmojiReaction directItemEmojiReaction = temp.get(i);
|
||||
if (directItemEmojiReaction.getSenderId() == currentUser.getPk()) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final DirectItemEmojiReaction reaction = new DirectItemEmojiReaction(
|
||||
currentUser.getPk(),
|
||||
System.currentTimeMillis() * 1000,
|
||||
emoji,
|
||||
"none"
|
||||
);
|
||||
if (index < 0) {
|
||||
temp.add(reaction);
|
||||
} else if (shouldReplaceIfAlreadyReacted) {
|
||||
temp.set(index, reaction);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
private void updateItemSent(final String clientContext, final long timestamp) {
|
||||
if (clientContext == null) return;
|
||||
List<DirectItem> list = this.items.getValue();
|
||||
@ -551,6 +623,48 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<DirectItem>> sendReaction(final DirectItem item, final Emoji emoji) {
|
||||
final MutableLiveData<Resource<DirectItem>> data = new MutableLiveData<>();
|
||||
final Long userId = handleCurrentUser(data);
|
||||
if (userId == null) return data;
|
||||
final String clientContext = UUID.randomUUID().toString();
|
||||
// Log.d(TAG, "sendText: sending: itemId: " + directItem.getItemId());
|
||||
data.postValue(Resource.loading(item));
|
||||
addReaction(item, emoji);
|
||||
String emojiUnicode = null;
|
||||
if (!emoji.getUnicode().equals("❤️")) {
|
||||
emojiUnicode = emoji.getUnicode();
|
||||
}
|
||||
final Call<DirectThreadBroadcastResponse> request = service.broadcastReaction(
|
||||
clientContext, threadIdOrUserIds, item.getItemId(), emojiUnicode, false);
|
||||
request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call,
|
||||
@NonNull final Response<DirectThreadBroadcastResponse> response) {
|
||||
if (!response.isSuccessful()) {
|
||||
if (response.errorBody() != null) {
|
||||
handleErrorBody(call, response, data, item);
|
||||
return;
|
||||
}
|
||||
data.postValue(Resource.error("request was not successful and response error body was null", item));
|
||||
return;
|
||||
}
|
||||
final DirectThreadBroadcastResponse body = response.body();
|
||||
if (body == null) {
|
||||
data.postValue(Resource.error("Response is null!", item));
|
||||
}
|
||||
// otherwise nothing to do? maybe update the timestamp in the emoji?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
|
||||
data.postValue(Resource.error(t.getMessage(), item));
|
||||
Log.e(TAG, "enqueueRequest: onFailure: ", t);
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setCurrentUser(final User currentUser) {
|
||||
this.currentUser = currentUser;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
||||
import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.PhotoBroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.ReactionBroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.StoryReplyBroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.TextBroadcastOptions;
|
||||
import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions;
|
||||
@ -158,6 +159,15 @@ public class DirectMessagesService extends BaseService {
|
||||
return broadcast(new StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId));
|
||||
}
|
||||
|
||||
public Call<DirectThreadBroadcastResponse> broadcastReaction(final String clientContext,
|
||||
final ThreadIdOrUserIds threadIdOrUserIds,
|
||||
final String itemId,
|
||||
final String emoji,
|
||||
final boolean delete) {
|
||||
return broadcast(new ReactionBroadcastOptions(clientContext, threadIdOrUserIds, itemId, emoji, delete));
|
||||
}
|
||||
|
||||
|
||||
private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) {
|
||||
if (TextUtils.isEmpty(broadcastOptions.getClientContext())) {
|
||||
throw new IllegalArgumentException("Broadcast requires a valid client context value");
|
||||
|
12
app/src/main/res/drawable/bg_rounded_corner.xml
Normal file
12
app/src/main/res/drawable/bg_rounded_corner.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?colorSurface" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="4dp"
|
||||
android:right="4dp"
|
||||
android:top="4dp" />
|
||||
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
@ -1,10 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
|
||||
</vector>
|
||||
|
@ -143,4 +143,14 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/long_click_backdrop"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:elevation="5dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:visibility="gone" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_height="@dimen/horizontal_divider_height"
|
||||
android:background="@drawable/pref_list_divider_material" />
|
69
app/src/main/res/layout/layout_direct_item_options.xml
Normal file
69
app/src/main/res/layout/layout_direct_item_options.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/card"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!--<androidx.appcompat.widget.AppCompatImageView-->
|
||||
<!-- android:id="@+id/img1"-->
|
||||
<!-- android:layout_width="48dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:padding="4dp"-->
|
||||
<!-- android:scaleType="fitCenter"-->
|
||||
<!-- app:layout_constraintBottom_toTopOf="@id/divider"-->
|
||||
<!-- app:layout_constraintDimensionRatio="1"-->
|
||||
<!-- app:layout_constraintEnd_toStartOf="@id/img2"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- tools:srcCompat="@mipmap/ic_launcher" />-->
|
||||
|
||||
<!--<androidx.appcompat.widget.AppCompatImageView-->
|
||||
<!-- android:id="@+id/img2"-->
|
||||
<!-- android:layout_width="48dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:padding="4dp"-->
|
||||
<!-- android:scaleType="fitCenter"-->
|
||||
<!-- app:layout_constraintDimensionRatio="1"-->
|
||||
<!-- app:layout_constraintEnd_toStartOf="@id/img3"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/img1"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- tools:srcCompat="@mipmap/ic_launcher" />-->
|
||||
|
||||
<!--<awais.instagrabber.customviews.SquareImageView-->
|
||||
<!-- android:id="@+id/img3"-->
|
||||
<!-- android:layout_width="48dp"-->
|
||||
<!-- android:layout_height="48dp"-->
|
||||
<!-- android:padding="4dp"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/img2"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- tools:srcCompat="@mipmap/ic_launcher" />-->
|
||||
|
||||
<!--<include-->
|
||||
<!-- android:id="@+id/divider"-->
|
||||
<!-- layout="@layout/item_pref_divider"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="1dp"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="@id/img3"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="@id/img1"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@id/img1" />-->
|
||||
|
||||
<!--<androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:text="test"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@id/divider" />-->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<awais.instagrabber.customviews.DirectItemFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
@ -10,7 +10,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:padding="4dp">
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
tools:layout_gravity="end"
|
||||
tools:paddingBottom="@dimen/dm_reaction_adjust_margin">
|
||||
|
||||
<View
|
||||
android:id="@+id/quote_line"
|
||||
@ -114,11 +119,11 @@
|
||||
tools:text="@string/app_name"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!--app:layout_constraintBottom_toTopOf="@id/reactions"-->
|
||||
<awais.instagrabber.customviews.ChatMessageLayout
|
||||
android:id="@+id/chat_message_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@id/reactions"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvUsername"
|
||||
@ -129,7 +134,9 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout_height="200dp"
|
||||
tools:layout_width="100dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/message_info"
|
||||
@ -158,7 +165,7 @@
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginStart="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="visible"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_check_all_24"
|
||||
app:tint="@color/grey_500"
|
||||
tools:visibility="visible" />
|
||||
@ -166,24 +173,49 @@
|
||||
|
||||
</awais.instagrabber.customviews.ChatMessageLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/reactions"
|
||||
<androidx.emoji.widget.EmojiAppCompatTextView
|
||||
android:id="@+id/emojis"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@drawable/bg_rounded_corner"
|
||||
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:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@id/chat_message_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="@id/chat_message_layout"
|
||||
app:layout_constraintTop_toBottomOf="@id/chat_message_layout"
|
||||
tools:visibility="visible">
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintWidth_max="wrap"
|
||||
tools:text="😀"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/emojis"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
tools:text="😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀" />
|
||||
</FrameLayout>
|
||||
<!--<FrameLayout-->
|
||||
<!-- android:id="@+id/reactions"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:background="@drawable/bg_rounded_corner"-->
|
||||
<!-- android:elevation="1dp"-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="@id/chat_message_layout"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="@id/chat_message_layout"-->
|
||||
<!-- app:layout_constraintTop_toBottomOf="@id/chat_message_layout"-->
|
||||
<!-- tools:visibility="visible">-->
|
||||
|
||||
<!-- <androidx.appcompat.widget.AppCompatTextView-->
|
||||
<!-- android:id="@+id/emojis"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:padding="4dp"-->
|
||||
<!-- android:textColor="?android:textColorPrimary"-->
|
||||
<!-- tools:text="😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀" />-->
|
||||
<!--</FrameLayout>-->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
||||
</awais.instagrabber.customviews.DirectItemFrameLayout>
|
@ -3,9 +3,8 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/media_share_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/top_barrier"
|
||||
|
@ -32,7 +32,17 @@
|
||||
<dimen name="dm_message_item_margin">80dp</dimen>
|
||||
<dimen name="dm_message_item_avatar_size">48dp</dimen>
|
||||
<dimen name="dm_message_info_padding_small">4dp</dimen>
|
||||
<dimen name="dm_reaction_adjust_margin">22dp</dimen>
|
||||
<dimen name="dm_reaction_translation_y_type_1">6dp</dimen>
|
||||
<dimen name="dm_reaction_translation_y_type_2">-12dp</dimen>
|
||||
|
||||
<dimen name="feed_item_bottom_icon_size">32dp</dimen>
|
||||
<dimen name="keyboard_height">200dp</dimen>
|
||||
|
||||
<dimen name="reaction_picker_emoji_size">40dp</dimen>
|
||||
<dimen name="reaction_picker_emoji_margin">8dp</dimen>
|
||||
<dimen name="reaction_picker_option_height">48dp</dimen>
|
||||
<dimen name="reaction_picker_add_padding_adjustment">4dp</dimen>
|
||||
|
||||
<dimen name="horizontal_divider_height">1dp</dimen>
|
||||
</resources>
|
5
app/src/main/res/values/ids.xml
Normal file
5
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="reply" type="id" />
|
||||
<item name="unsend" type="id" />
|
||||
</resources>
|
@ -396,4 +396,5 @@
|
||||
<string name="dms_action_remove_admin">Remove as Admin</string>
|
||||
<string name="edit_unsuccessful">Edit was unsuccessful</string>
|
||||
<string name="message">Message</string>
|
||||
<string name="reply">Reply</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user