24 changed files with 650 additions and 55 deletions
-
2app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemActionLogViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLikeViewHolder.java
-
17app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemPlaceholderViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemProfileViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java
-
6app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVideoCallEventViewHolder.java
-
14app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
-
16app/src/main/java/awais/instagrabber/customviews/DirectItemFrameLayout.java
-
184app/src/main/java/awais/instagrabber/customviews/helpers/SwipeAndRestoreItemTouchHelperCallback.java
-
214app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
6app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java
-
19app/src/main/java/awais/instagrabber/repositories/requests/directmessages/BroadcastOptions.java
-
6app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
-
5app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java
-
6app/src/main/java/awais/instagrabber/utils/Utils.java
-
38app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java
-
24app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java
-
10app/src/main/res/drawable/ic_round_reply_24.xml
-
90app/src/main/res/layout/fragment_direct_messages_thread.xml
-
6app/src/main/res/values/strings.xml
@ -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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue