Swipe to reply
This commit is contained in:
parent
59aa14e2f6
commit
13d95523a3
@ -342,7 +342,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
|
||||
public static class DirectItemOrHeader {
|
||||
Date date;
|
||||
DirectItem item;
|
||||
public DirectItem item;
|
||||
|
||||
public boolean isHeader() {
|
||||
return date != null;
|
||||
|
@ -10,6 +10,7 @@ import android.text.style.StyleSpan;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -86,4 +87,9 @@ public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
||||
protected boolean allowLongClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
|
||||
@ -59,4 +60,9 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
||||
.setAutoPlayAnimations(true)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package awais.instagrabber.adapters.viewholder.directmessages;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
@ -35,4 +36,9 @@ public class DirectItemDefaultViewHolder extends DirectItemViewHolder {
|
||||
protected boolean allowLongClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package awais.instagrabber.adapters.viewholder.directmessages;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
@ -27,4 +28,9 @@ public class DirectItemLikeViewHolder extends DirectItemViewHolder {
|
||||
protected boolean canForward() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
@ -36,6 +37,7 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
private final LayoutDmMediaShareBinding binding;
|
||||
private final RoundingParams incomingRoundingParams;
|
||||
private final RoundingParams outgoingRoundingParams;
|
||||
private DirectItemType itemType;
|
||||
|
||||
public DirectItemMediaShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding,
|
||||
@NonNull final LayoutDmMediaShareBinding binding,
|
||||
@ -148,13 +150,14 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
@Nullable
|
||||
private Media getMedia(@NonNull final DirectItem item) {
|
||||
Media media = null;
|
||||
if (item.getItemType() == DirectItemType.MEDIA_SHARE) {
|
||||
itemType = item.getItemType();
|
||||
if (itemType == DirectItemType.MEDIA_SHARE) {
|
||||
media = item.getMediaShare();
|
||||
} else if (item.getItemType() == DirectItemType.CLIP) {
|
||||
} else if (itemType == DirectItemType.CLIP) {
|
||||
final DirectItemClip clip = item.getClip();
|
||||
if (clip == null) return null;
|
||||
media = clip.getClip();
|
||||
} else if (item.getItemType() == DirectItemType.FELIX_SHARE) {
|
||||
} else if (itemType == DirectItemType.FELIX_SHARE) {
|
||||
final DirectItemFelixShare felixShare = item.getFelixShare();
|
||||
if (felixShare == null) return null;
|
||||
media = felixShare.getVideo();
|
||||
@ -166,4 +169,12 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
protected int getReactionsTranslationY() {
|
||||
return reactionTranslationYType2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
if (itemType != null && (itemType == DirectItemType.CLIP || itemType == DirectItemType.FELIX_SHARE)) {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
return super.getSwipeDirection();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package awais.instagrabber.adapters.viewholder.directmessages;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
@ -38,4 +39,9 @@ public class DirectItemPlaceholderViewHolder extends DirectItemViewHolder {
|
||||
protected boolean allowLongClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.content.res.Resources;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.generic.RoundingParams;
|
||||
@ -117,4 +118,9 @@ public class DirectItemProfileViewHolder extends DirectItemViewHolder {
|
||||
binding.isVerified.setVisibility(View.GONE);
|
||||
itemView.setOnClickListener(v -> openLocation(location.getPk()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils;
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
@ -110,4 +111,9 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
|
||||
protected boolean canForward() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import android.text.style.ForegroundColorSpan;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -76,4 +77,9 @@ public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
||||
protected boolean allowLongClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.widget.ImageViewCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
@ -33,6 +34,7 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemInternalLongClic
|
||||
import awais.instagrabber.customviews.DirectItemContextMenu;
|
||||
import awais.instagrabber.customviews.DirectItemFrameLayout;
|
||||
import awais.instagrabber.customviews.RamboTextViewV2;
|
||||
import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback.SwipeableViewHolder;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
import awais.instagrabber.models.enums.DirectItemType;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
@ -46,7 +48,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.DeepLinkParser;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
|
||||
public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder implements SwipeableViewHolder {
|
||||
private static final String TAG = DirectItemViewHolder.class.getSimpleName();
|
||||
|
||||
private final LayoutDmBaseBinding binding;
|
||||
@ -72,6 +74,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
private DirectItemInternalLongClickListener longClickListener;
|
||||
private DirectItem item;
|
||||
private ViewPropertyAnimator shrinkGrowAnimator;
|
||||
private MessageDirection messageDirection;
|
||||
// private View.OnLayoutChangeListener layoutChangeListener;
|
||||
|
||||
public DirectItemViewHolder(@NonNull final LayoutDmBaseBinding binding,
|
||||
@ -108,7 +111,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public void bind(final int position, final DirectItem item) {
|
||||
this.item = item;
|
||||
final MessageDirection messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
||||
messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
||||
itemView.post(() -> bindBase(item, messageDirection, position));
|
||||
itemView.post(() -> bindItem(item, messageDirection));
|
||||
itemView.post(() -> setupLongClickListener(position, messageDirection));
|
||||
@ -266,6 +269,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
// if (media == null) break;
|
||||
// url = ResponseBodyUtils.getThumbUrl(media.getImageVersions2());
|
||||
// break;
|
||||
// case LOCATION
|
||||
}
|
||||
if (text == null && url == null) {
|
||||
binding.quoteLine.setVisibility(View.GONE);
|
||||
@ -568,6 +572,12 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
||||
shrinkGrowAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSwipeDirection() {
|
||||
if (item == null || messageDirection == null) return ItemTouchHelper.ACTION_STATE_IDLE;
|
||||
return messageDirection == MessageDirection.OUTGOING ? ItemTouchHelper.START : ItemTouchHelper.END;
|
||||
}
|
||||
|
||||
public enum MessageDirection {
|
||||
INCOMING,
|
||||
OUTGOING
|
||||
|
@ -73,9 +73,20 @@ public class DirectItemFrameLayout extends FrameLayout {
|
||||
touchY = ev.getRawY();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (longPressed || Math.abs(touchX - ev.getRawX()) > touchSlop || Math.abs(touchY - ev.getRawY()) > touchSlop) {
|
||||
final float diffX = touchX - ev.getRawX();
|
||||
final float diffXAbs = Math.abs(diffX);
|
||||
final boolean isMoved = diffXAbs > touchSlop || Math.abs(touchY - ev.getRawY()) > touchSlop;
|
||||
if (longPressed || isMoved) {
|
||||
handler.removeCallbacks(longPressStartRunnable);
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
if (!longPressed) {
|
||||
if (onItemLongClickListener != null) {
|
||||
onItemLongClickListener.onLongClickCancel(this);
|
||||
}
|
||||
}
|
||||
// if (diffXAbs > touchSlop) {
|
||||
// setTranslationX(-diffX);
|
||||
// }
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
@ -91,6 +102,9 @@ public class DirectItemFrameLayout extends FrameLayout {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handler.removeCallbacks(longPressRunnable);
|
||||
handler.removeCallbacks(longPressStartRunnable);
|
||||
if (onItemLongClickListener != null) {
|
||||
onItemLongClickListener.onLongClickCancel(this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
final boolean dispatchTouchEvent = super.dispatchTouchEvent(ev);
|
||||
|
@ -0,0 +1,184 @@
|
||||
package awais.instagrabber.customviews.helpers;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
/**
|
||||
* Thanks to https://github.com/izjumovfs/SwipeToReply/blob/master/swipetoreply/src/main/java/com/capybaralabs/swipetoreply/SwipeController.java
|
||||
*/
|
||||
public class SwipeAndRestoreItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
private static final String TAG = "SwipeRestoreCallback";
|
||||
|
||||
private final float swipeThreshold;
|
||||
private final float swipeAutoCancelThreshold;
|
||||
private final OnSwipeListener onSwipeListener;
|
||||
private final Drawable replyIcon;
|
||||
// private final Drawable replyIconBackground;
|
||||
private final int replyIconShowThreshold;
|
||||
private final float replyIconMaxTranslation;
|
||||
private final Rect replyIconBounds = new Rect();
|
||||
private final float replyIconXOffset;
|
||||
private final int replyIconSize;
|
||||
|
||||
private boolean mSwipeBack = false;
|
||||
private boolean hasVibrated;
|
||||
|
||||
public SwipeAndRestoreItemTouchHelperCallback(final Context context, final OnSwipeListener onSwipeListener) {
|
||||
this.onSwipeListener = onSwipeListener;
|
||||
swipeThreshold = Utils.displayMetrics.widthPixels * 0.25f;
|
||||
swipeAutoCancelThreshold = swipeThreshold + Utils.convertDpToPx(5);
|
||||
replyIcon = AppCompatResources.getDrawable(context, R.drawable.ic_round_reply_24);
|
||||
if (replyIcon == null) {
|
||||
throw new IllegalArgumentException("reply icon is null");
|
||||
}
|
||||
replyIcon.setTint(context.getResources().getColor(R.color.white)); //todo need to update according to theme
|
||||
replyIconShowThreshold = Utils.convertDpToPx(24);
|
||||
replyIconMaxTranslation = swipeThreshold - replyIconShowThreshold;
|
||||
// Log.d(TAG, "replyIconShowThreshold: " + replyIconShowThreshold + ", swipeThreshold: " + swipeThreshold);
|
||||
replyIconSize = replyIconShowThreshold; // Utils.convertDpToPx(24);
|
||||
replyIconXOffset = swipeThreshold * 0.25f /*Utils.convertDpToPx(20)*/;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (!(viewHolder instanceof SwipeableViewHolder)) {
|
||||
return makeMovementFlags(ItemTouchHelper.ACTION_STATE_IDLE, ItemTouchHelper.ACTION_STATE_IDLE);
|
||||
}
|
||||
return makeMovementFlags(ItemTouchHelper.ACTION_STATE_IDLE, ((SwipeableViewHolder) viewHolder).getSwipeDirection());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {}
|
||||
|
||||
@Override
|
||||
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
|
||||
if (mSwipeBack) {
|
||||
mSwipeBack = false;
|
||||
return 0;
|
||||
}
|
||||
return super.convertToAbsoluteDirection(flags, layoutDirection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c,
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
float dX,
|
||||
float dY,
|
||||
int actionState,
|
||||
boolean isCurrentlyActive) {
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||
setTouchListener(recyclerView, viewHolder);
|
||||
}
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
drawReplyButton(c, viewHolder);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void setTouchListener(RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder) {
|
||||
recyclerView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (Math.abs(viewHolder.itemView.getTranslationX()) >= swipeAutoCancelThreshold) {
|
||||
if (!hasVibrated) {
|
||||
viewHolder.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
|
||||
HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
|
||||
hasVibrated = true;
|
||||
}
|
||||
// MotionEvent cancelEvent = MotionEvent.obtain(event);
|
||||
// cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
|
||||
// recyclerView.dispatchTouchEvent(cancelEvent);
|
||||
// cancelEvent.recycle();
|
||||
}
|
||||
}
|
||||
mSwipeBack = event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP;
|
||||
if (mSwipeBack) {
|
||||
hasVibrated = false;
|
||||
if (Math.abs(viewHolder.itemView.getTranslationX()) >= swipeThreshold) {
|
||||
if (onSwipeListener != null) {
|
||||
onSwipeListener.onSwipe(viewHolder.getBindingAdapterPosition(), viewHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public interface SwipeableViewHolder {
|
||||
int getSwipeDirection();
|
||||
}
|
||||
|
||||
public interface OnSwipeListener {
|
||||
void onSwipe(final int adapterPosition, final RecyclerView.ViewHolder viewHolder);
|
||||
}
|
||||
|
||||
private void drawReplyButton(Canvas canvas, final RecyclerView.ViewHolder viewHolder) {
|
||||
if (!(viewHolder instanceof SwipeableViewHolder)) return;
|
||||
final int swipeDirection = ((SwipeableViewHolder) viewHolder).getSwipeDirection();
|
||||
if (swipeDirection != ItemTouchHelper.START && swipeDirection != ItemTouchHelper.END) return;
|
||||
final View view = viewHolder.itemView;
|
||||
float translationX = view.getTranslationX();
|
||||
boolean show = false;
|
||||
float progress;
|
||||
final float translationXAbs = Math.abs(translationX);
|
||||
if (translationXAbs >= replyIconShowThreshold) {
|
||||
show = true;
|
||||
}
|
||||
if (show) {
|
||||
// replyIconShowThreshold -> swipeThreshold <=> progress 0 -> 1
|
||||
final float replyIconTranslation = translationXAbs - replyIconShowThreshold;
|
||||
progress = replyIconTranslation / replyIconMaxTranslation;
|
||||
if (progress > 1) {
|
||||
progress = 1f;
|
||||
}
|
||||
if (progress < 0) {
|
||||
progress = 0;
|
||||
}
|
||||
// Log.d(TAG, /*"translationX: " + translationX + ", replyIconTranslation: " + replyIconTranslation +*/ "progress: " + progress);
|
||||
} else {
|
||||
progress = 0f;
|
||||
// Log.d(TAG, /*"translationX: " + translationX + ", replyIconTranslation: " + 0 +*/ "progress: " + progress);
|
||||
}
|
||||
if (progress > 0) {
|
||||
// calculate the reply icon y position, then offset top, bottom with icon size
|
||||
final int y = view.getTop() + (view.getMeasuredHeight() / 2);
|
||||
final int tempIconSize = (int) (replyIconSize * progress);
|
||||
final int tempIconSizeHalf = tempIconSize / 2;
|
||||
final int xOffset = (int) (replyIconXOffset * progress);
|
||||
final int left;
|
||||
if (swipeDirection == ItemTouchHelper.END) {
|
||||
// draw arrow of left side
|
||||
left = xOffset;
|
||||
} else {
|
||||
// draw arrow of right side
|
||||
left = view.getMeasuredWidth() - xOffset - tempIconSize;
|
||||
}
|
||||
final int right = tempIconSize + left;
|
||||
replyIconBounds.set(left, y - tempIconSizeHalf, right, y + tempIconSizeHalf);
|
||||
replyIcon.setBounds(replyIconBounds);
|
||||
replyIcon.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -41,9 +41,11 @@ import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat;
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
|
||||
|
||||
@ -73,6 +75,7 @@ import awais.instagrabber.customviews.emoji.Emoji;
|
||||
import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
|
||||
import awais.instagrabber.customviews.helpers.HeightProvider;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||
import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback;
|
||||
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
||||
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
|
||||
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
|
||||
@ -81,16 +84,19 @@ import awais.instagrabber.fragments.PostViewV2Fragment;
|
||||
import awais.instagrabber.fragments.UserSearchFragment;
|
||||
import awais.instagrabber.fragments.UserSearchFragmentDirections;
|
||||
import awais.instagrabber.models.Resource;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||
import awais.instagrabber.utils.AppExecutors;
|
||||
import awais.instagrabber.utils.PermissionUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.viewmodels.AppStateViewModel;
|
||||
@ -133,6 +139,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
private DirectItemReactionDialogFragment reactionDialogFragment;
|
||||
private DirectItem itemToForward;
|
||||
private MutableLiveData<Object> backStackSavedStateResultLiveData;
|
||||
private int prevLength;
|
||||
|
||||
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
|
||||
@ -252,7 +259,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final DirectItemLongClickListener directItemLongClickListener = position -> {
|
||||
// viewModel.setSelectedPosition(position);
|
||||
};
|
||||
@ -280,6 +286,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
// clear result
|
||||
backStackSavedStateResultLiveData.postValue(null);
|
||||
};
|
||||
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
@ -491,6 +498,17 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
}
|
||||
});
|
||||
binding.chats.addItemDecoration(headerItemDecoration);
|
||||
final SwipeAndRestoreItemTouchHelperCallback touchHelperCallback = new SwipeAndRestoreItemTouchHelperCallback(
|
||||
context,
|
||||
(adapterPosition, viewHolder) -> {
|
||||
if (itemsAdapter == null) return;
|
||||
final DirectItemOrHeader directItemOrHeader = itemsAdapter.getList().get(adapterPosition);
|
||||
if (directItemOrHeader.isHeader()) return;
|
||||
viewModel.setReplyToItem(directItemOrHeader.item);
|
||||
}
|
||||
);
|
||||
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback);
|
||||
itemTouchHelper.attachToRecyclerView(binding.chats);
|
||||
// final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> searchUsername(text);
|
||||
// final DialogInterface.OnClickListener onDialogListener = (dialogInterface, which) -> {
|
||||
// if (which == 0) {
|
||||
@ -632,6 +650,141 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
setupItemsAdapter(userThreadPair.first, userThreadPair.second);
|
||||
});
|
||||
viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
|
||||
viewModel.getReplyToItem().observe(getViewLifecycleOwner(), item -> {
|
||||
if (item == null) {
|
||||
if (binding.input.length() == 0) {
|
||||
showExtraInputOption(true);
|
||||
}
|
||||
binding.getRoot().post(() -> {
|
||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||
binding.replyBg.setVisibility(View.GONE);
|
||||
binding.replyInfo.setVisibility(View.GONE);
|
||||
binding.replyPreviewImage.setVisibility(View.GONE);
|
||||
binding.replyCancel.setVisibility(View.GONE);
|
||||
binding.replyPreviewText.setVisibility(View.GONE);
|
||||
});
|
||||
return;
|
||||
}
|
||||
showExtraInputOption(false);
|
||||
binding.getRoot().postDelayed(() -> {
|
||||
binding.replyBg.setVisibility(View.VISIBLE);
|
||||
binding.replyInfo.setVisibility(View.VISIBLE);
|
||||
binding.replyPreviewImage.setVisibility(View.VISIBLE);
|
||||
binding.replyCancel.setVisibility(View.VISIBLE);
|
||||
binding.replyPreviewText.setVisibility(View.VISIBLE);
|
||||
if (item.getUserId() == viewModel.getViewerId()) {
|
||||
binding.replyInfo.setText(R.string.replying_to_yourself);
|
||||
} else {
|
||||
final User user = viewModel.getUser(item.getUserId());
|
||||
if (user != null) {
|
||||
binding.replyInfo.setText(getString(R.string.replying_to_user, user.getFullName()));
|
||||
} else {
|
||||
binding.replyInfo.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
final String previewText = getDirectItemPreviewText(item);
|
||||
binding.replyPreviewText.setText(TextUtils.isEmpty(previewText) ? getString(R.string.message) : previewText);
|
||||
final String previewImageUrl = getDirectItemPreviewImageUrl(item);
|
||||
if (TextUtils.isEmpty(previewImageUrl)) {
|
||||
binding.replyPreviewImage.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.replyPreviewImage.setImageURI(previewImageUrl);
|
||||
}
|
||||
binding.replyCancel.setOnClickListener(v -> viewModel.setReplyToItem(null));
|
||||
}, 200);
|
||||
});
|
||||
inputLength.observe(getViewLifecycleOwner(), length -> {
|
||||
if (length == null) return;
|
||||
final boolean hasReplyToItem = viewModel.getReplyToItem().getValue() != null;
|
||||
if (hasReplyToItem) {
|
||||
prevLength = length;
|
||||
return;
|
||||
}
|
||||
if ((prevLength == 0 && length != 0) || (prevLength != 0 && length == 0)) {
|
||||
showExtraInputOption(length == 0);
|
||||
}
|
||||
prevLength = length;
|
||||
});
|
||||
}
|
||||
|
||||
private void showExtraInputOption(final boolean show) {
|
||||
if (show) {
|
||||
if (!binding.send.isListenForRecord()) {
|
||||
binding.send.setListenForRecord(true);
|
||||
startIconAnimation();
|
||||
}
|
||||
binding.gallery.setVisibility(View.VISIBLE);
|
||||
binding.camera.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
if (binding.send.isListenForRecord()) {
|
||||
binding.send.setListenForRecord(false);
|
||||
startIconAnimation();
|
||||
}
|
||||
binding.gallery.setVisibility(View.GONE);
|
||||
binding.camera.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private String getDirectItemPreviewText(final DirectItem item) {
|
||||
switch (item.getItemType()) {
|
||||
case TEXT:
|
||||
return item.getText();
|
||||
case LINK:
|
||||
return item.getLink().getText();
|
||||
case MEDIA: {
|
||||
final Media media = item.getMedia();
|
||||
return getMediaPreviewTextString(media);
|
||||
}
|
||||
case RAVEN_MEDIA: {
|
||||
final DirectItemVisualMedia visualMedia = item.getVisualMedia();
|
||||
final Media media = visualMedia.getMedia();
|
||||
return getMediaPreviewTextString(media);
|
||||
}
|
||||
case VOICE_MEDIA:
|
||||
return getString(R.string.voice_message);
|
||||
case MEDIA_SHARE:
|
||||
return getString(R.string.post);
|
||||
case REEL_SHARE:
|
||||
return item.getReelShare().getText();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getMediaPreviewTextString(final Media media) {
|
||||
final MediaItemType mediaType = media.getMediaType();
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
return getString(R.string.photo);
|
||||
case MEDIA_TYPE_VIDEO:
|
||||
return getString(R.string.video);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getDirectItemPreviewImageUrl(final DirectItem item) {
|
||||
switch (item.getItemType()) {
|
||||
case TEXT:
|
||||
case LINK:
|
||||
case VOICE_MEDIA:
|
||||
case REEL_SHARE:
|
||||
return null;
|
||||
case MEDIA: {
|
||||
final Media media = item.getMedia();
|
||||
return ResponseBodyUtils.getThumbUrl(media);
|
||||
}
|
||||
case RAVEN_MEDIA: {
|
||||
final DirectItemVisualMedia visualMedia = item.getVisualMedia();
|
||||
final Media media = visualMedia.getMedia();
|
||||
return ResponseBodyUtils.getThumbUrl(media);
|
||||
}
|
||||
case MEDIA_SHARE: {
|
||||
final Media media = item.getMediaShare();
|
||||
return ResponseBodyUtils.getThumbUrl(media);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setupBackStackResultObserver() {
|
||||
@ -750,33 +903,29 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
binding.camera.setVisibility(View.VISIBLE);
|
||||
});
|
||||
binding.input.addTextChangedListener(new TextWatcherAdapter() {
|
||||
int prevLength = 0;
|
||||
// int prevLength = 0;
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||
final int length = s.length();
|
||||
if (prevLength != 0 && length == 0) {
|
||||
binding.send.setListenForRecord(true);
|
||||
startIconAnimation();
|
||||
}
|
||||
if (prevLength == 0 && length != 0) {
|
||||
binding.send.setListenForRecord(false);
|
||||
startIconAnimation();
|
||||
}
|
||||
binding.gallery.setVisibility(length == 0 ? View.VISIBLE : View.GONE);
|
||||
binding.camera.setVisibility(length == 0 ? View.VISIBLE : View.GONE);
|
||||
prevLength = length;
|
||||
}
|
||||
|
||||
private void startIconAnimation() {
|
||||
final Drawable icon = binding.send.getIcon();
|
||||
if (icon instanceof Animatable) {
|
||||
final Animatable animatable = (Animatable) icon;
|
||||
if (animatable.isRunning()) {
|
||||
animatable.stop();
|
||||
}
|
||||
animatable.start();
|
||||
}
|
||||
inputLength.postValue(length);
|
||||
// boolean showExtraInputOptionsChanged = false;
|
||||
// if (prevLength != 0 && length == 0) {
|
||||
// inputLength.postValue(true);
|
||||
// showExtraInputOptionsChanged = true;
|
||||
// binding.send.setListenForRecord(true);
|
||||
// startIconAnimation();
|
||||
// }
|
||||
// if (prevLength == 0 && length != 0) {
|
||||
// inputLength.postValue(false);
|
||||
// showExtraInputOptionsChanged = true;
|
||||
// binding.send.setListenForRecord(false);
|
||||
// startIconAnimation();
|
||||
// }
|
||||
// if (!showExtraInputOptionsChanged) {
|
||||
// showExtraInputOptions.postValue(length == 0);
|
||||
// }
|
||||
// prevLength = length;
|
||||
}
|
||||
});
|
||||
binding.send.setOnRecordClickListener(v -> {
|
||||
@ -785,6 +934,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
final LiveData<Resource<DirectItem>> resourceLiveData = viewModel.sendText(text.toString());
|
||||
resourceLiveData.observe(getViewLifecycleOwner(), resource -> handleSentMessage(resourceLiveData));
|
||||
binding.input.setText("");
|
||||
viewModel.setReplyToItem(null);
|
||||
});
|
||||
binding.send.setOnRecordLongClickListener(v -> {
|
||||
Log.d(TAG, "setOnRecordLongClickListener");
|
||||
@ -833,6 +983,17 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
});
|
||||
}
|
||||
|
||||
private void startIconAnimation() {
|
||||
final Drawable icon = binding.send.getIcon();
|
||||
if (icon instanceof Animatable) {
|
||||
final Animatable animatable = (Animatable) icon;
|
||||
if (animatable.isRunning()) {
|
||||
animatable.stop();
|
||||
}
|
||||
animatable.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void navigateToImageEditFragment(final String path) {
|
||||
navigateToImageEditFragment(Uri.fromFile(new File(path)));
|
||||
}
|
||||
@ -1168,6 +1329,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
||||
ObjectAnimator.ofFloat(binding.gallery, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.camera, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.send, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.replyBg, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.replyInfo, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.replyCancel, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.replyPreviewImage, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.replyPreviewText, TRANSLATION_Y, -height),
|
||||
ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, keyboardHeight - height)
|
||||
);
|
||||
// if (headerItemDecoration != null && headerItemDecoration.getCurrentHeader() != null) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
package awais.instagrabber.models.enums;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -65,7 +63,6 @@ public enum DirectItemType implements Serializable {
|
||||
return map.get(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getName() {
|
||||
switch (this) {
|
||||
case TEXT:
|
||||
@ -102,8 +99,7 @@ public enum DirectItemType implements Serializable {
|
||||
return "felix_share";
|
||||
case LOCATION:
|
||||
return "location";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -13,6 +13,9 @@ public abstract class BroadcastOptions {
|
||||
private final ThreadIdOrUserIds threadIdOrUserIds;
|
||||
private final BroadcastItemType itemType;
|
||||
|
||||
private String repliedToItemId;
|
||||
private String repliedToClientContext;
|
||||
|
||||
public BroadcastOptions(final String clientContext,
|
||||
@NonNull final ThreadIdOrUserIds threadIdOrUserIds,
|
||||
@NonNull final BroadcastItemType itemType) {
|
||||
@ -39,6 +42,22 @@ public abstract class BroadcastOptions {
|
||||
|
||||
public abstract Map<String, String> getFormMap();
|
||||
|
||||
public String getRepliedToItemId() {
|
||||
return repliedToItemId;
|
||||
}
|
||||
|
||||
public void setRepliedToItemId(final String repliedToItemId) {
|
||||
this.repliedToItemId = repliedToItemId;
|
||||
}
|
||||
|
||||
public String getRepliedToClientContext() {
|
||||
return repliedToClientContext;
|
||||
}
|
||||
|
||||
public void setRepliedToClientContext(final String repliedToClientContext) {
|
||||
this.repliedToClientContext = repliedToClientContext;
|
||||
}
|
||||
|
||||
public static final class ThreadIdOrUserIds {
|
||||
private final String threadId;
|
||||
private final List<String> userIds;
|
||||
|
@ -11,7 +11,7 @@ import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
|
||||
public class DirectItem implements Cloneable {
|
||||
private final String itemId;
|
||||
private String itemId;
|
||||
private final long userId;
|
||||
private long timestamp;
|
||||
private final DirectItemType itemType;
|
||||
@ -213,6 +213,10 @@ public class DirectItem implements Cloneable {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setItemId(final String itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return isPending;
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ public class DirectItemFactory {
|
||||
|
||||
public static DirectItem createText(final long userId,
|
||||
final String clientContext,
|
||||
final String text) {
|
||||
final String text,
|
||||
final DirectItem repliedToMessage) {
|
||||
return new DirectItem(
|
||||
UUID.randomUUID().toString(),
|
||||
userId,
|
||||
@ -44,7 +45,7 @@ public class DirectItemFactory {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
repliedToMessage,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
|
@ -62,8 +62,8 @@ public final class Utils {
|
||||
public static SettingsHelper settingsHelper;
|
||||
public static boolean sessionVolumeFull = false;
|
||||
public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
|
||||
public static final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
public static ClipboardManager clipboardManager;
|
||||
public static DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
public static SimpleDateFormat datetimeParser;
|
||||
public static SimpleCache simpleCache;
|
||||
private static int statusBarHeight;
|
||||
@ -73,9 +73,7 @@ public final class Utils {
|
||||
private static int defaultStatusBarColor;
|
||||
|
||||
public static int convertDpToPx(final float dp) {
|
||||
if (displayMetrics == null)
|
||||
displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
return Math.round((dp * displayMetrics.densityDpi) / 160.0f);
|
||||
return Math.round((dp * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT);
|
||||
}
|
||||
|
||||
public static void copyText(@NonNull final Context context, final CharSequence string) {
|
||||
|
@ -81,6 +81,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
private final MutableLiveData<Boolean> fetching = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<List<User>> users = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<User>> leftUsers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<DirectItem> replyToItem = new MutableLiveData<>();
|
||||
|
||||
private final DirectMessagesService service;
|
||||
private final ContentResolver contentResolver;
|
||||
@ -285,7 +286,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
return index;
|
||||
}
|
||||
|
||||
private void updateItemSent(final String clientContext, final long timestamp) {
|
||||
private void updateItemSent(final String clientContext, final long timestamp, final String itemId) {
|
||||
if (clientContext == null) return;
|
||||
List<DirectItem> list = this.items.getValue();
|
||||
list = list == null ? new LinkedList<>() : new LinkedList<>(list);
|
||||
@ -297,6 +298,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
final DirectItem directItem = list.get(index);
|
||||
try {
|
||||
final DirectItem itemClone = (DirectItem) directItem.clone();
|
||||
itemClone.setItemId(itemId);
|
||||
itemClone.setPending(false);
|
||||
itemClone.setTimestamp(timestamp);
|
||||
list.set(index, itemClone);
|
||||
@ -322,6 +324,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
return leftUsers;
|
||||
}
|
||||
|
||||
public LiveData<DirectItem> getReplyToItem() {
|
||||
return replyToItem;
|
||||
}
|
||||
|
||||
public void fetchChats() {
|
||||
final Boolean isFetching = fetching.getValue();
|
||||
if ((isFetching != null && isFetching) || !hasOlder) return;
|
||||
@ -392,12 +398,21 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
final Long userId = handleCurrentUser(data);
|
||||
if (userId == null) return data;
|
||||
final String clientContext = UUID.randomUUID().toString();
|
||||
final DirectItem directItem = DirectItemFactory.createText(userId, clientContext, text);
|
||||
final DirectItem replyToItemValue = replyToItem.getValue();
|
||||
final DirectItem directItem = DirectItemFactory.createText(userId, clientContext, text, replyToItemValue);
|
||||
// Log.d(TAG, "sendText: sending: itemId: " + directItem.getItemId());
|
||||
directItem.setPending(true);
|
||||
addItems(0, Collections.singletonList(directItem));
|
||||
data.postValue(Resource.loading(directItem));
|
||||
final Call<DirectThreadBroadcastResponse> request = service.broadcastText(clientContext, threadIdOrUserIds, text);
|
||||
final String repliedToItemId = replyToItemValue != null ? replyToItemValue.getItemId() : null;
|
||||
final String repliedToClientContext = replyToItemValue != null ? replyToItemValue.getClientContext() : null;
|
||||
final Call<DirectThreadBroadcastResponse> request = service.broadcastText(
|
||||
clientContext,
|
||||
threadIdOrUserIds,
|
||||
text,
|
||||
repliedToItemId,
|
||||
repliedToClientContext
|
||||
);
|
||||
enqueueRequest(request, data, directItem);
|
||||
return data;
|
||||
}
|
||||
@ -848,6 +863,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
}
|
||||
final String payloadClientContext;
|
||||
final long timestamp;
|
||||
final String itemId;
|
||||
final DirectThreadBroadcastResponsePayload payload = broadcastResponse.getPayload();
|
||||
if (payload == null) {
|
||||
final List<DirectThreadBroadcastResponseMessageMetadata> messageMetadata = broadcastResponse.getMessageMetadata();
|
||||
@ -857,12 +873,14 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
}
|
||||
final DirectThreadBroadcastResponseMessageMetadata metadata = messageMetadata.get(0);
|
||||
payloadClientContext = metadata.getClientContext();
|
||||
itemId = metadata.getItemId();
|
||||
timestamp = metadata.getTimestamp();
|
||||
} else {
|
||||
payloadClientContext = payload.getClientContext();
|
||||
timestamp = payload.getTimestamp();
|
||||
itemId = payload.getItemId();
|
||||
}
|
||||
updateItemSent(payloadClientContext, timestamp);
|
||||
updateItemSent(payloadClientContext, timestamp, itemId);
|
||||
data.postValue(Resource.success(directItem));
|
||||
return;
|
||||
}
|
||||
@ -987,8 +1005,13 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
|
||||
private void forward(@NonNull final DirectThread thread, @NonNull final DirectItem itemToForward) {
|
||||
final DirectItemType itemType = itemToForward.getItemType();
|
||||
final String itemTypeName = itemType.getName();
|
||||
if (itemTypeName == null) {
|
||||
Log.e(TAG, "forward: itemTypeName was null!");
|
||||
return;
|
||||
}
|
||||
final Call<DirectThreadBroadcastResponse> request = service.forward(thread.getThreadId(),
|
||||
itemType.getName(),
|
||||
itemTypeName,
|
||||
threadId,
|
||||
itemToForward.getItemId());
|
||||
request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
|
||||
@ -1019,4 +1042,9 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setReplyToItem(final DirectItem item) {
|
||||
// Log.d(TAG, "setReplyToItem: " + item);
|
||||
replyToItem.postValue(item);
|
||||
}
|
||||
}
|
||||
|
@ -119,19 +119,29 @@ public class DirectMessagesService extends BaseService {
|
||||
|
||||
public Call<DirectThreadBroadcastResponse> broadcastText(final String clientContext,
|
||||
final ThreadIdOrUserIds threadIdOrUserIds,
|
||||
final String text) {
|
||||
final String text,
|
||||
final String repliedToItemId,
|
||||
final String repliedToClientContext) {
|
||||
final List<String> urls = TextUtils.extractUrls(text);
|
||||
if (!urls.isEmpty()) {
|
||||
return broadcastLink(clientContext, threadIdOrUserIds, text, urls);
|
||||
return broadcastLink(clientContext, threadIdOrUserIds, text, urls, repliedToItemId, repliedToClientContext);
|
||||
}
|
||||
return broadcast(new TextBroadcastOptions(clientContext, threadIdOrUserIds, text));
|
||||
final TextBroadcastOptions broadcastOptions = new TextBroadcastOptions(clientContext, threadIdOrUserIds, text);
|
||||
broadcastOptions.setRepliedToItemId(repliedToItemId);
|
||||
broadcastOptions.setRepliedToClientContext(repliedToClientContext);
|
||||
return broadcast(broadcastOptions);
|
||||
}
|
||||
|
||||
public Call<DirectThreadBroadcastResponse> broadcastLink(final String clientContext,
|
||||
final ThreadIdOrUserIds threadIdOrUserIds,
|
||||
final String linkText,
|
||||
final List<String> urls) {
|
||||
return broadcast(new LinkBroadcastOptions(clientContext, threadIdOrUserIds, linkText, urls));
|
||||
final List<String> urls,
|
||||
final String repliedToItemId,
|
||||
final String repliedToClientContext) {
|
||||
final LinkBroadcastOptions broadcastOptions = new LinkBroadcastOptions(clientContext, threadIdOrUserIds, linkText, urls);
|
||||
broadcastOptions.setRepliedToItemId(repliedToItemId);
|
||||
broadcastOptions.setRepliedToClientContext(repliedToClientContext);
|
||||
return broadcast(broadcastOptions);
|
||||
}
|
||||
|
||||
public Call<DirectThreadBroadcastResponse> broadcastPhoto(final String clientContext,
|
||||
@ -187,6 +197,10 @@ public class DirectMessagesService extends BaseService {
|
||||
form.put("__uuid", deviceUuid);
|
||||
form.put("client_context", broadcastOptions.getClientContext());
|
||||
form.put("mutation_token", broadcastOptions.getClientContext());
|
||||
if (!TextUtils.isEmpty(broadcastOptions.getRepliedToItemId()) && !TextUtils.isEmpty(broadcastOptions.getRepliedToClientContext())) {
|
||||
form.put("replied_to_item_id", broadcastOptions.getRepliedToItemId());
|
||||
form.put("replied_to_client_context", broadcastOptions.getRepliedToClientContext());
|
||||
}
|
||||
form.putAll(broadcastOptions.getFormMap());
|
||||
form.put("action", "send_item");
|
||||
final Map<String, String> signedForm = Utils.sign(form);
|
||||
|
10
app/src/main/res/drawable/ic_round_reply_24.xml
Normal file
10
app/src/main/res/drawable/ic_round_reply_24.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,9V7.41c0,-0.89 -1.08,-1.34 -1.71,-0.71L3.7,11.29c-0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59c0.63,0.63 1.71,0.19 1.71,-0.7V14.9c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/>
|
||||
</vector>
|
@ -11,12 +11,98 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scrollbars="none"
|
||||
app:layout_constraintBottom_toTopOf="@id/input"
|
||||
app:layout_constraintBottom_toTopOf="@id/reply_info"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/layout_dm_base" />
|
||||
|
||||
<View
|
||||
android:id="@+id/reply_bg"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/bg_input"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/input_bg"
|
||||
app:layout_constraintEnd_toEndOf="@id/input_bg"
|
||||
app:layout_constraintStart_toStartOf="@id/input_bg"
|
||||
app:layout_constraintTop_toTopOf="@id/reply_info"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/reply_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
|
||||
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
|
||||
app:layout_constraintStart_toStartOf="@id/input_bg"
|
||||
app:layout_constraintTop_toBottomOf="@id/chats"
|
||||
tools:text="Replying to yourself"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.emoji.widget.EmojiAppCompatTextView
|
||||
android:id="@+id/reply_preview_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@id/input_bg"
|
||||
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
|
||||
app:layout_constraintStart_toStartOf="@id/input_bg"
|
||||
app:layout_constraintTop_toBottomOf="@id/reply_info"
|
||||
app:layout_goneMarginTop="8dp"
|
||||
tools:text="Post"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/reply_preview_image"
|
||||
android:layout_width="@dimen/dm_inbox_avatar_size_small"
|
||||
android:layout_height="@dimen/dm_inbox_avatar_size_small"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"
|
||||
app:actualImageScaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
|
||||
app:layout_constraintEnd_toStartOf="@id/reply_cancel"
|
||||
app:layout_constraintStart_toEndOf="@id/reply_preview_text"
|
||||
app:layout_constraintTop_toTopOf="@id/reply_info"
|
||||
tools:background="@mipmap/ic_launcher"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/reply_cancel"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
|
||||
app:layout_constraintEnd_toEndOf="@id/input_bg"
|
||||
app:layout_constraintStart_toEndOf="@id/reply_preview_image"
|
||||
app:layout_constraintTop_toTopOf="@id/reply_info"
|
||||
app:srcCompat="@drawable/ic_close_24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!--<androidx.constraintlayout.widget.Group-->
|
||||
<!-- android:id="@+id/reply_group"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:visibility="gone"-->
|
||||
<!-- app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"-->
|
||||
<!-- tools:visibility="visible" />-->
|
||||
|
||||
<View
|
||||
android:id="@+id/input_bg"
|
||||
android:layout_width="0dp"
|
||||
@ -65,7 +151,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/camera"
|
||||
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
|
||||
app:layout_constraintTop_toBottomOf="@id/chats"
|
||||
app:layout_constraintTop_toBottomOf="@id/reply_preview_text"
|
||||
app:layout_goneMarginBottom="4dp"
|
||||
app:layout_goneMarginEnd="24dp" />
|
||||
|
||||
|
@ -401,4 +401,10 @@
|
||||
<string name="forward">Forward</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="replying_to_yourself">Replying to yourself</string>
|
||||
<string name="replying_to_user">Replying to %s</string>
|
||||
<string name="photo">Photo</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="voice_message">Voice message</string>
|
||||
<string name="post">Post</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user