mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 22:57:29 +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.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder;
|
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemAnimatedMediaViewHolder;
|
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.DirectItemVideoCallEventViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder;
|
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder;
|
||||||
|
import awais.instagrabber.customviews.emoji.Emoji;
|
||||||
import awais.instagrabber.databinding.LayoutDmActionLogBinding;
|
import awais.instagrabber.databinding.LayoutDmActionLogBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
|
import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
@ -57,11 +59,14 @@ import awais.instagrabber.utils.DateUtils;
|
|||||||
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
|
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
|
||||||
|
|
||||||
private final User currentUser;
|
private List<DirectItem> items;
|
||||||
private DirectThread thread;
|
private DirectThread thread;
|
||||||
|
private DirectItemViewHolder selectedViewHolder;
|
||||||
|
|
||||||
|
private final User currentUser;
|
||||||
private final DirectItemCallback callback;
|
private final DirectItemCallback callback;
|
||||||
private final AsyncListDiffer<DirectItemOrHeader> differ;
|
private final AsyncListDiffer<DirectItemOrHeader> differ;
|
||||||
private List<DirectItem> items;
|
private final DirectItemInternalLongClickListener longClickListener;
|
||||||
|
|
||||||
private static final DiffUtil.ItemCallback<DirectItemOrHeader> diffCallback = new DiffUtil.ItemCallback<DirectItemOrHeader>() {
|
private static final DiffUtil.ItemCallback<DirectItemOrHeader> diffCallback = new DiffUtil.ItemCallback<DirectItemOrHeader>() {
|
||||||
@Override
|
@Override
|
||||||
@ -96,21 +101,30 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
if (bothHeaders) {
|
if (bothHeaders) {
|
||||||
return oldItem.date.equals(newItem.date);
|
return oldItem.date.equals(newItem.date);
|
||||||
}
|
}
|
||||||
return oldItem.item.getTimestamp() == newItem.item.getTimestamp()
|
final boolean timestampEqual = oldItem.item.getTimestamp() == newItem.item.getTimestamp();
|
||||||
&& oldItem.item.isPending() == newItem.item.isPending(); // todo need to be more specific
|
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,
|
public DirectItemsAdapter(@NonNull final User currentUser,
|
||||||
@NonNull final DirectThread thread,
|
@NonNull final DirectThread thread,
|
||||||
@NonNull final DirectItemCallback callback) {
|
@NonNull final DirectItemCallback callback,
|
||||||
|
@NonNull final DirectItemLongClickListener itemLongClickListener) {
|
||||||
this.currentUser = currentUser;
|
this.currentUser = currentUser;
|
||||||
this.thread = thread;
|
this.thread = thread;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
|
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
|
||||||
new AsyncDifferConfig.Builder<>(diffCallback).build());
|
new AsyncDifferConfig.Builder<>(diffCallback).build());
|
||||||
// this.onClickListener = onClickListener;
|
longClickListener = (position, viewHolder) -> {
|
||||||
// this.mentionClickListener = mentionClickListener;
|
if (selectedViewHolder != null) {
|
||||||
|
selectedViewHolder.setSelected(false);
|
||||||
|
}
|
||||||
|
selectedViewHolder = viewHolder;
|
||||||
|
viewHolder.setSelected(true);
|
||||||
|
itemLongClickListener.onLongClick(position);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -123,7 +137,9 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
}
|
}
|
||||||
final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
|
final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
|
||||||
final DirectItemType directItemType = DirectItemType.valueOf(type);
|
final DirectItemType directItemType = DirectItemType.valueOf(type);
|
||||||
return getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
final DirectItemViewHolder itemViewHolder = getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
||||||
|
itemViewHolder.setLongClickListener(longClickListener);
|
||||||
|
return itemViewHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -205,7 +221,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (thread == null) return;
|
if (thread == null) return;
|
||||||
((DirectItemViewHolder) holder).bind(itemOrHeader.item);
|
((DirectItemViewHolder) holder).bind(position, itemOrHeader.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DirectItemOrHeader getItem(int position) {
|
protected DirectItemOrHeader getItem(int position) {
|
||||||
@ -372,5 +388,15 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
void onMediaClick(Media media);
|
void onMediaClick(Media media);
|
||||||
|
|
||||||
void onStoryClick(DirectItemStoryShare storyShare);
|
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);
|
subtitle = getMediaSpecificSubtitle(username, mediaType);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case STORY_SHARE:
|
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";
|
String format = "%s shared a story by @%s";
|
||||||
if (item.getStoryShare().getReelType().equals("highlight_reel")) {
|
if (reelType.equals("highlight_reel")) {
|
||||||
format = "%s shared a story highlight by @%s";
|
format = "%s shared a story highlight by @%s";
|
||||||
}
|
}
|
||||||
subtitle = String.format(format, username != null ? username : "",
|
subtitle = String.format(format, username != null ? username : "",
|
||||||
item.getStoryShare().getMedia().getUser().getUsername());
|
item.getStoryShare().getMedia().getUser().getUsername());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case VOICE_MEDIA:
|
case VOICE_MEDIA:
|
||||||
subtitle = String.format("%s sent a voice message", username != null ? username : "");
|
subtitle = String.format("%s sent a voice message", username != null ? username : "");
|
||||||
break;
|
break;
|
||||||
|
@ -70,6 +70,7 @@ public class DirectItemLinkViewHolder extends DirectItemViewHolder {
|
|||||||
setupRamboTextListeners(binding.text);
|
setupRamboTextListeners(binding.text);
|
||||||
final View.OnClickListener onClickListener = v -> openURL(linkContext.getLinkUrl());
|
final View.OnClickListener onClickListener = v -> openURL(linkContext.getLinkUrl());
|
||||||
binding.preview.setOnClickListener(onClickListener);
|
binding.preview.setOnClickListener(onClickListener);
|
||||||
|
// binding.preview.setOnLongClickListener(v -> itemView.performLongClick());
|
||||||
binding.title.setOnClickListener(onClickListener);
|
binding.title.setOnClickListener(onClickListener);
|
||||||
binding.summary.setOnClickListener(onClickListener);
|
binding.summary.setOnClickListener(onClickListener);
|
||||||
binding.url.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.GenericDraweeHierarchyBuilder;
|
||||||
import com.facebook.drawee.generic.RoundingParams;
|
import com.facebook.drawee.generic.RoundingParams;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
@ -29,6 +31,7 @@ import awais.instagrabber.utils.NumberUtils;
|
|||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
|
|
||||||
public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||||
|
private static final String TAG = DirectItemMediaShareViewHolder.class.getSimpleName();
|
||||||
|
|
||||||
private final LayoutDmMediaShareBinding binding;
|
private final LayoutDmMediaShareBinding binding;
|
||||||
private final RoundingParams incomingRoundingParams;
|
private final RoundingParams incomingRoundingParams;
|
||||||
@ -48,11 +51,6 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
|
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
|
binding.topBg.setBackgroundResource(messageDirection == MessageDirection.INCOMING
|
||||||
? R.drawable.bg_media_share_top_incoming
|
? R.drawable.bg_media_share_top_incoming
|
||||||
: R.drawable.bg_media_share_top_outgoing);
|
: R.drawable.bg_media_share_top_outgoing);
|
||||||
@ -67,10 +65,10 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
final MediaItemType mediaType = media.getMediaType();
|
final MediaItemType mediaType = media.getMediaType();
|
||||||
setupTypeIndicator(mediaType);
|
setupTypeIndicator(mediaType);
|
||||||
if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
|
if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
|
||||||
setupPreview(media.getCarouselMedia().get(0));
|
setupPreview(media.getCarouselMedia().get(0), messageDirection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setupPreview(media);
|
setupPreview(media, messageDirection);
|
||||||
});
|
});
|
||||||
itemView.setOnClickListener(v -> openMedia(media));
|
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(
|
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
@ -98,7 +106,7 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||||
binding.mediaPreview.requestLayout();
|
binding.mediaPreview.requestLayout();
|
||||||
final String url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
|
binding.mediaPreview.setTag(url);
|
||||||
binding.mediaPreview.setImageURI(url);
|
binding.mediaPreview.setImageURI(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,4 +161,9 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
}
|
}
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getReactionsTranslationY() {
|
||||||
|
return reactionTranslationYType2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
package awais.instagrabber.adapters.viewholder.directmessages;
|
package awais.instagrabber.adapters.viewholder.directmessages;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.ViewPropertyAnimator;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.core.widget.ImageViewCompat;
|
import androidx.core.widget.ImageViewCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
|
||||||
|
import com.google.android.material.transition.MaterialFade;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -20,6 +29,9 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
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.customviews.RamboTextViewV2;
|
||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.models.enums.DirectItemType;
|
import awais.instagrabber.models.enums.DirectItemType;
|
||||||
@ -43,6 +55,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
private final int groupMessageWidth;
|
private final int groupMessageWidth;
|
||||||
private final List<Long> userIds;
|
private final List<Long> userIds;
|
||||||
private final DirectItemCallback callback;
|
private final DirectItemCallback callback;
|
||||||
|
private final int reactionAdjustMargin;
|
||||||
|
private final AccelerateDecelerateInterpolator accelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
|
||||||
|
|
||||||
protected final int margin;
|
protected final int margin;
|
||||||
protected final int dmRadius;
|
protected final int dmRadius;
|
||||||
@ -51,6 +65,14 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
protected final int mediaImageMaxHeight;
|
protected final int mediaImageMaxHeight;
|
||||||
protected final int windowWidth;
|
protected final int windowWidth;
|
||||||
protected final int mediaImageMaxWidth;
|
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,
|
public DirectItemViewHolder(@NonNull final LayoutDmBaseBinding binding,
|
||||||
@NonNull final User currentUser,
|
@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);
|
messageInfoPaddingSmall = resources.getDimensionPixelSize(R.dimen.dm_message_info_padding_small);
|
||||||
windowWidth = resources.getDisplayMetrics().widthPixels;
|
windowWidth = resources.getDisplayMetrics().widthPixels;
|
||||||
mediaImageMaxHeight = resources.getDimensionPixelSize(R.dimen.dm_media_img_max_height);
|
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;
|
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
|
// messageInfoPaddingSmall is used cuz it's also 4dp, 1 avatar margin + 2 paddings = 3
|
||||||
groupMessageWidth = windowWidth - margin - groupWidthCorrection;
|
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;
|
final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
||||||
itemView.post(() -> bindBase(item, messageDirection));
|
itemView.post(() -> bindBase(item, messageDirection));
|
||||||
itemView.post(() -> bindItem(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) {
|
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);
|
binding.messageInfo.setPadding(0, 0, messageInfoPaddingSmall, dmRadiusSmall);
|
||||||
}
|
}
|
||||||
setupReply(item, messageDirection);
|
setupReply(item, messageDirection);
|
||||||
setReactions(item, thread.getUsers());
|
setReactions(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBackground(final MessageDirection messageDirection) {
|
private void setBackground(final MessageDirection messageDirection) {
|
||||||
@ -304,14 +334,21 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
|
replyInfoLayoutParams.endToStart = isIncoming ? ConstraintLayout.LayoutParams.UNSET : quoteLineId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setReactions(final DirectItem item, final List<User> users) {
|
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 DirectItemReactions reactions = item.getReactions();
|
||||||
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
|
final List<DirectItemEmojiReaction> emojis = reactions != null ? reactions.getEmojis() : null;
|
||||||
if (emojis == null || emojis.isEmpty()) {
|
if (emojis == null || emojis.isEmpty()) {
|
||||||
binding.reactions.setVisibility(View.GONE);
|
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall);
|
||||||
|
binding.emojis.setVisibility(View.GONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
binding.reactions.setVisibility(View.VISIBLE);
|
binding.emojis.setVisibility(View.VISIBLE);
|
||||||
|
binding.emojis.setTranslationY(getReactionsTranslationY());
|
||||||
|
binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, reactionAdjustMargin);
|
||||||
final String emojisJoined = emojis.stream()
|
final String emojisJoined = emojis.stream()
|
||||||
.map(DirectItemEmojiReaction::getEmoji)
|
.map(DirectItemEmojiReaction::getEmoji)
|
||||||
.collect(Collectors.joining());
|
.collect(Collectors.joining());
|
||||||
@ -329,6 +366,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
// profilePicView.setImageURI(user.getProfilePicUrl());
|
// profilePicView.setImageURI(user.getProfilePicUrl());
|
||||||
// binding.reactions.addView(profilePicView);
|
// binding.reactions.addView(profilePicView);
|
||||||
// }
|
// }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isSelf(final DirectItem directItem) {
|
protected boolean isSelf(final DirectItem directItem) {
|
||||||
@ -370,7 +408,28 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
return true;
|
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) {
|
protected void setupRamboTextListeners(@NonNull final RamboTextViewV2 textView) {
|
||||||
textView.addOnHashtagListener(autoLinkItem -> callback.onHashtagClick(autoLinkItem.getOriginalText().trim()));
|
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 {
|
public enum MessageDirection {
|
||||||
INCOMING,
|
INCOMING,
|
||||||
OUTGOING
|
OUTGOING
|
||||||
|
@ -154,6 +154,7 @@ public class DirectItemVoiceMediaViewHolder extends DirectItemViewHolder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
super.cleanup();
|
||||||
if (handler != null && positionChecker != null) {
|
if (handler != null && positionChecker != null) {
|
||||||
handler.removeCallbacks(positionChecker);
|
handler.removeCallbacks(positionChecker);
|
||||||
handler = null;
|
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;
|
widthSize += viewPartMainWidth;
|
||||||
heightSize += viewPartMainHeight;
|
heightSize += viewPartMainHeight;
|
||||||
} else if (firstChildId == R.id.raven_media_container || firstChildId == R.id.profile_container || firstChildId == R.id.voice_media
|
} 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;
|
widthSize += viewPartMainWidth;
|
||||||
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
||||||
} else {
|
} 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.CameraActivity;
|
||||||
import awais.instagrabber.activities.MainActivity;
|
import awais.instagrabber.activities.MainActivity;
|
||||||
import awais.instagrabber.adapters.DirectItemsAdapter;
|
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.DirectItemsAdapter.DirectItemOrHeader;
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
|
||||||
import awais.instagrabber.animations.CubicBezierInterpolator;
|
import awais.instagrabber.animations.CubicBezierInterpolator;
|
||||||
import awais.instagrabber.customviews.RecordView;
|
import awais.instagrabber.customviews.RecordView;
|
||||||
import awais.instagrabber.customviews.Tooltip;
|
import awais.instagrabber.customviews.Tooltip;
|
||||||
|
import awais.instagrabber.customviews.emoji.Emoji;
|
||||||
import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
|
import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
|
||||||
import awais.instagrabber.customviews.helpers.HeightProvider;
|
import awais.instagrabber.customviews.helpers.HeightProvider;
|
||||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||||
@ -133,7 +136,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
setMicToSendIcon();
|
setMicToSendIcon();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final DirectItemsAdapter.DirectItemCallback directItemCallback = new DirectItemsAdapter.DirectItemCallback() {
|
private final DirectItemCallback directItemCallback = new DirectItemCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onHashtagClick(final String hashtag) {
|
public void onHashtagClick(final String hashtag) {
|
||||||
final NavDirections action = DirectMessageThreadFragmentDirections.actionGlobalHashTagFragment(hashtag);
|
final NavDirections action = DirectMessageThreadFragmentDirections.actionGlobalHashTagFragment(hashtag);
|
||||||
@ -188,6 +191,18 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
Log.e(TAG, "onStoryClick: ", e);
|
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
|
@Override
|
||||||
@ -226,6 +241,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
init();
|
init();
|
||||||
binding.send.post(() -> initialSendX = binding.send.getX());
|
binding.send.post(() -> initialSendX = binding.send.getX());
|
||||||
shouldRefresh = false;
|
shouldRefresh = false;
|
||||||
|
setObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -309,7 +325,6 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
binding.input.post(this::showKeyboard);
|
binding.input.post(this::showKeyboard);
|
||||||
wasKbShowing = false;
|
wasKbShowing = false;
|
||||||
}
|
}
|
||||||
setObservers();
|
|
||||||
if (initialSendX != 0) {
|
if (initialSendX != 0) {
|
||||||
binding.send.setX(initialSendX);
|
binding.send.setX(initialSendX);
|
||||||
}
|
}
|
||||||
@ -354,7 +369,6 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
setupList();
|
setupList();
|
||||||
root.post(this::setupInput);
|
root.post(this::setupInput);
|
||||||
root.post(this::getInitialData);
|
root.post(this::getInitialData);
|
||||||
setObservers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getInitialData() {
|
private void getInitialData() {
|
||||||
@ -578,7 +592,7 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
itemsAdapter.setThread(thread);
|
itemsAdapter.setThread(thread);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback);
|
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener);
|
||||||
itemsAdapter.setHasStableIds(true);
|
itemsAdapter.setHasStableIds(true);
|
||||||
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||||
binding.chats.setAdapter(itemsAdapter);
|
binding.chats.setAdapter(itemsAdapter);
|
||||||
@ -694,7 +708,8 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
binding.send.setOnRecordClickListener(v -> {
|
binding.send.setOnRecordClickListener(v -> {
|
||||||
final Editable text = binding.input.getText();
|
final Editable text = binding.input.getText();
|
||||||
if (TextUtils.isEmpty(text)) return;
|
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.input.setText("");
|
||||||
});
|
});
|
||||||
binding.send.setOnRecordLongClickListener(v -> {
|
binding.send.setOnRecordLongClickListener(v -> {
|
||||||
@ -754,16 +769,21 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
navController.navigate(navDirections);
|
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;
|
final Resource.Status status = resource.status;
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
|
resourceLiveData.removeObservers(getViewLifecycleOwner());
|
||||||
|
break;
|
||||||
case LOADING:
|
case LOADING:
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
if (resource.message != null) {
|
if (resource.message != null) {
|
||||||
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
resourceLiveData.removeObservers(getViewLifecycleOwner());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1093,6 +1113,10 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
animatorSet.start();
|
animatorSet.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showLongClickOptions(final View itemView) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> {
|
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> {
|
||||||
private User user;
|
private User user;
|
||||||
private DirectThread thread;
|
private DirectThread thread;
|
||||||
|
@ -4,17 +4,21 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import awais.instagrabber.models.enums.BroadcastItemType;
|
import awais.instagrabber.models.enums.BroadcastItemType;
|
||||||
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
public class ReactionBroadcastOptions extends BroadcastOptions {
|
public class ReactionBroadcastOptions extends BroadcastOptions {
|
||||||
private final String itemId;
|
private final String itemId;
|
||||||
|
private final String emoji;
|
||||||
private final boolean delete;
|
private final boolean delete;
|
||||||
|
|
||||||
public ReactionBroadcastOptions(final String clientContext,
|
public ReactionBroadcastOptions(final String clientContext,
|
||||||
final ThreadIdOrUserIds threadIdOrUserIds,
|
final ThreadIdOrUserIds threadIdOrUserIds,
|
||||||
final String itemId,
|
final String itemId,
|
||||||
|
final String emoji,
|
||||||
final boolean delete) {
|
final boolean delete) {
|
||||||
super(clientContext, threadIdOrUserIds, BroadcastItemType.REACTION);
|
super(clientContext, threadIdOrUserIds, BroadcastItemType.REACTION);
|
||||||
this.itemId = itemId;
|
this.itemId = itemId;
|
||||||
|
this.emoji = emoji;
|
||||||
this.delete = delete;
|
this.delete = delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +28,9 @@ public class ReactionBroadcastOptions extends BroadcastOptions {
|
|||||||
form.put("item_id", itemId);
|
form.put("item_id", itemId);
|
||||||
form.put("reaction_status", delete ? "deleted" : "created");
|
form.put("reaction_status", delete ? "deleted" : "created");
|
||||||
form.put("reaction_type", "like");
|
form.put("reaction_type", "like");
|
||||||
|
if (!TextUtils.isEmpty(emoji)) {
|
||||||
|
form.put("emoji", emoji);
|
||||||
|
}
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ public class DirectItem implements Cloneable {
|
|||||||
private final DirectItemFelixShare felixShare;
|
private final DirectItemFelixShare felixShare;
|
||||||
private final DirectItemVisualMedia visualMedia;
|
private final DirectItemVisualMedia visualMedia;
|
||||||
private final DirectItemAnimatedMedia animatedMedia;
|
private final DirectItemAnimatedMedia animatedMedia;
|
||||||
private final DirectItemReactions reactions;
|
private DirectItemReactions reactions;
|
||||||
private final DirectItem repliedToMessage;
|
private final DirectItem repliedToMessage;
|
||||||
private final DirectItemVoiceMedia voiceMedia;
|
private final DirectItemVoiceMedia voiceMedia;
|
||||||
private final Location location;
|
private final Location location;
|
||||||
@ -218,6 +218,10 @@ public class DirectItem implements Cloneable {
|
|||||||
isPending = pending;
|
isPending = pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReactions(final DirectItemReactions reactions) {
|
||||||
|
this.reactions = reactions;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object clone() throws CloneNotSupportedException {
|
public Object clone() throws CloneNotSupportedException {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package awais.instagrabber.repositories.responses.directmessages;
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class DirectItemEmojiReaction {
|
public class DirectItemEmojiReaction {
|
||||||
private final long senderId;
|
private final long senderId;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
@ -28,4 +30,20 @@ public class DirectItemEmojiReaction {
|
|||||||
public String getSuperReactType() {
|
public String getSuperReactType() {
|
||||||
return superReactType;
|
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;
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
import java.util.List;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class DirectItemReactions {
|
import java.util.List;
|
||||||
private final List<DirectItemEmojiReaction> emojis;
|
import java.util.Objects;
|
||||||
private final List<DirectItemEmojiReaction> likes;
|
|
||||||
|
public class DirectItemReactions implements Cloneable {
|
||||||
|
private List<DirectItemEmojiReaction> emojis;
|
||||||
|
private List<DirectItemEmojiReaction> likes;
|
||||||
|
|
||||||
public DirectItemReactions(final List<DirectItemEmojiReaction> emojis,
|
public DirectItemReactions(final List<DirectItemEmojiReaction> emojis,
|
||||||
final List<DirectItemEmojiReaction> likes) {
|
final List<DirectItemEmojiReaction> likes) {
|
||||||
@ -19,4 +22,32 @@ public class DirectItemReactions {
|
|||||||
public List<DirectItemEmojiReaction> getLikes() {
|
public List<DirectItemEmojiReaction> getLikes() {
|
||||||
return likes;
|
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_TAGGED_POSTS_LAYOUT = "tagged_posts_layout";
|
||||||
public static final String PREF_SAVED_POSTS_LAYOUT = "saved_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_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_LOCATION_POSTS_LAYOUT;
|
||||||
import static awais.instagrabber.utils.Constants.PREF_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_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_SAVED_POSTS_LAYOUT;
|
||||||
import static awais.instagrabber.utils.Constants.PREF_TAGGED_POSTS_LAYOUT;
|
import static awais.instagrabber.utils.Constants.PREF_TAGGED_POSTS_LAYOUT;
|
||||||
import static awais.instagrabber.utils.Constants.PREF_TOPIC_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,
|
{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,
|
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_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 {}
|
public @interface StringSettings {}
|
||||||
|
|
||||||
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
|
@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.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
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 UnicodeSet SKIN_TONE_MODIFIERS = new UnicodeSet("[🏻-🏿]").freeze();
|
||||||
private static final String SKIN_TONE_PATTERN = SKIN_TONE_MODIFIERS.toPattern(true);
|
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<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> emojiToMajorCategory = new UnicodeMap<>();
|
||||||
// private final UnicodeMap<String> emojiToMinorCategory = 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
|
spacePos = comment.indexOf(' ', spacePos + 1); // get second space
|
||||||
final String name = comment.substring(spacePos + 1).trim();
|
final String name = comment.substring(spacePos + 1).trim();
|
||||||
final Emoji emoji = new Emoji(original, name);
|
final Emoji emoji = new Emoji(original, name);
|
||||||
|
ALL_EMOJIS.put(original, emoji);
|
||||||
String minimal = original.replace(EMOJI_VARIANT, "");
|
String minimal = original.replace(EMOJI_VARIANT, "");
|
||||||
//noinspection deprecation
|
//noinspection deprecation
|
||||||
boolean singleton = CharSequences.getSingleCodePoint(minimal) != Integer.MAX_VALUE;
|
boolean singleton = CharSequences.getSingleCodePoint(minimal) != Integer.MAX_VALUE;
|
||||||
@ -263,6 +266,10 @@ public final class EmojiParser {
|
|||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Emoji> getAllEmojis() {
|
||||||
|
return ALL_EMOJIS;
|
||||||
|
}
|
||||||
|
|
||||||
// public String getMinorCategory(String emoji) {
|
// public String getMinorCategory(String emoji) {
|
||||||
// String minorCat = emojiToMinorCategory.get(emoji);
|
// String minorCat = emojiToMinorCategory.get(emoji);
|
||||||
// if (minorCat == null) {
|
// if (minorCat == null) {
|
||||||
|
@ -30,12 +30,15 @@ import java.util.Locale;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.customviews.emoji.Emoji;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.models.UploadVideoOptions;
|
import awais.instagrabber.models.UploadVideoOptions;
|
||||||
import awais.instagrabber.repositories.requests.UploadFinishOptions;
|
import awais.instagrabber.repositories.requests.UploadFinishOptions;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponseMessageMetadata;
|
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<String> threadTitle = new MutableLiveData<>("");
|
||||||
private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
private final DirectMessagesService service;
|
private final DirectMessagesService service;
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
private final MediaService mediaService;
|
private final MediaService mediaService;
|
||||||
@ -154,6 +158,74 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
|||||||
this.items.postValue(list);
|
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) {
|
private void updateItemSent(final String clientContext, final long timestamp) {
|
||||||
if (clientContext == null) return;
|
if (clientContext == null) return;
|
||||||
List<DirectItem> list = this.items.getValue();
|
List<DirectItem> list = this.items.getValue();
|
||||||
@ -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) {
|
public void setCurrentUser(final User currentUser) {
|
||||||
this.currentUser = 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.BroadcastOptions.ThreadIdOrUserIds;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions;
|
import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.PhotoBroadcastOptions;
|
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.StoryReplyBroadcastOptions;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.TextBroadcastOptions;
|
import awais.instagrabber.repositories.requests.directmessages.TextBroadcastOptions;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions;
|
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));
|
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) {
|
private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) {
|
||||||
if (TextUtils.isEmpty(broadcastOptions.getClientContext())) {
|
if (TextUtils.isEmpty(broadcastOptions.getClientContext())) {
|
||||||
throw new IllegalArgumentException("Broadcast requires a valid client context value");
|
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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
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>
|
</vector>
|
||||||
|
@ -143,4 +143,14 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="@dimen/horizontal_divider_height"
|
||||||
android:background="@drawable/pref_list_divider_material" />
|
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"?>
|
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -10,7 +10,12 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="start"
|
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
|
<View
|
||||||
android:id="@+id/quote_line"
|
android:id="@+id/quote_line"
|
||||||
@ -114,11 +119,11 @@
|
|||||||
tools:text="@string/app_name"
|
tools:text="@string/app_name"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<!--app:layout_constraintBottom_toTopOf="@id/reactions"-->
|
||||||
<awais.instagrabber.customviews.ChatMessageLayout
|
<awais.instagrabber.customviews.ChatMessageLayout
|
||||||
android:id="@+id/chat_message_layout"
|
android:id="@+id/chat_message_layout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toTopOf="@id/reactions"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvUsername"
|
app:layout_constraintTop_toBottomOf="@id/tvUsername"
|
||||||
@ -129,7 +134,9 @@
|
|||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/message"
|
android:id="@+id/message"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
tools:layout_height="200dp"
|
||||||
|
tools:layout_width="100dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/message_info"
|
android:id="@+id/message_info"
|
||||||
@ -158,7 +165,7 @@
|
|||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:visibility="visible"
|
android:visibility="gone"
|
||||||
app:srcCompat="@drawable/ic_check_all_24"
|
app:srcCompat="@drawable/ic_check_all_24"
|
||||||
app:tint="@color/grey_500"
|
app:tint="@color/grey_500"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
@ -166,24 +173,49 @@
|
|||||||
|
|
||||||
</awais.instagrabber.customviews.ChatMessageLayout>
|
</awais.instagrabber.customviews.ChatMessageLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<androidx.emoji.widget.EmojiAppCompatTextView
|
||||||
android:id="@+id/reactions"
|
android:id="@+id/emojis"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:layout_marginStart="4dp"
|
||||||
app:layout_constraintEnd_toEndOf="@id/chat_message_layout"
|
android:layout_marginBottom="4dp"
|
||||||
app:layout_constraintStart_toStartOf="@id/chat_message_layout"
|
android:background="@drawable/bg_rounded_corner"
|
||||||
app:layout_constraintTop_toBottomOf="@id/chat_message_layout"
|
android:elevation="1dp"
|
||||||
tools:visibility="visible">
|
android:maxLines="1"
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/emojis"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
tools:text="😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀" />
|
android:textSize="18sp"
|
||||||
</FrameLayout>
|
android:translationY="@dimen/dm_reaction_translation_y_type_1"
|
||||||
|
android:visibility="gone"
|
||||||
|
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="parent"
|
||||||
|
app:layout_constraintWidth_max="wrap"
|
||||||
|
tools:text="😀"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<!--<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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</FrameLayout>
|
</awais.instagrabber.customviews.DirectItemFrameLayout>
|
@ -3,9 +3,8 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/media_share_container"
|
android:id="@+id/media_share_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
<androidx.constraintlayout.widget.Barrier
|
||||||
android:id="@+id/top_barrier"
|
android:id="@+id/top_barrier"
|
||||||
|
@ -32,7 +32,17 @@
|
|||||||
<dimen name="dm_message_item_margin">80dp</dimen>
|
<dimen name="dm_message_item_margin">80dp</dimen>
|
||||||
<dimen name="dm_message_item_avatar_size">48dp</dimen>
|
<dimen name="dm_message_item_avatar_size">48dp</dimen>
|
||||||
<dimen name="dm_message_info_padding_small">4dp</dimen>
|
<dimen name="dm_message_info_padding_small">4dp</dimen>
|
||||||
|
<dimen name="dm_reaction_adjust_margin">22dp</dimen>
|
||||||
|
<dimen name="dm_reaction_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="feed_item_bottom_icon_size">32dp</dimen>
|
||||||
<dimen name="keyboard_height">200dp</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>
|
</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="dms_action_remove_admin">Remove as Admin</string>
|
||||||
<string name="edit_unsuccessful">Edit was unsuccessful</string>
|
<string name="edit_unsuccessful">Edit was unsuccessful</string>
|
||||||
<string name="message">Message</string>
|
<string name="message">Message</string>
|
||||||
|
<string name="reply">Reply</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user