diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java index 5b7fc442..785aad31 100644 --- a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java @@ -4,6 +4,7 @@ import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.AdapterListUpdateCallback; @@ -392,6 +393,8 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter bindBase(item, messageDirection, position)); itemView.post(() -> bindItem(item, messageDirection)); - itemView.post(() -> setupLongClickListener(position)); + itemView.post(() -> setupLongClickListener(position, messageDirection)); // bindBase(item, messageDirection); // bindItem(item, messageDirection); // setupLongClickListener(position); @@ -479,7 +480,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder { } @SuppressLint("ClickableViewAccessibility") - private void setupLongClickListener(final int position) { + private void setupLongClickListener(final int position, final MessageDirection messageDirection) { if (!allowLongClick()) return; binding.getRoot().setOnItemLongClickListener(new DirectItemFrameLayout.OnItemLongClickListener() { @Override @@ -498,17 +499,24 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder { // longClickListener.onLongClick(position, this); itemView.post(() -> grow()); setSelected(true); - showLongClickOptions(new Point((int) x, (int) y)); + showLongClickOptions(new Point((int) x, (int) y), messageDirection); } }); } - private void showLongClickOptions(final Point location) { - final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), allowReaction(), getLongClickOptions()); + private void showLongClickOptions(final Point location, final MessageDirection messageDirection) { + final List longClickOptions = getLongClickOptions(); + final ImmutableList.Builder builder = ImmutableList.builder(); + if (longClickOptions != null) { + builder.addAll(longClickOptions); + } + if (messageDirection == MessageDirection.OUTGOING) { + builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend)); + } + final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), allowReaction(), builder.build()); menu.setOnDismissListener(() -> setSelected(false)); - menu.setOnReactionClickListener(emoji -> { - callback.onReaction(item, emoji); - }); + menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji)); + menu.setOnOptionSelectListener(itemId -> callback.onOptionSelect(item, itemId)); menu.show(itemView, location); } diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index 3d64f18c..11708e0c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -222,6 +222,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact public void onReactionClick(final DirectItem item, final int position) { showReactionsDialog(item); } + + @Override + public void onOptionSelect(final DirectItem item, final int itemId) { + if (itemId == R.id.unsend) { + handleSentMessage(viewModel.unsend(item)); + return; + } + } }; private final DirectItemLongClickListener directItemLongClickListener = position -> { diff --git a/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java b/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java index 37b0df1e..ff52bf27 100644 --- a/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/DirectMessagesRepository.java @@ -56,4 +56,10 @@ public interface DirectMessagesRepository { @POST("/api/v1/direct_v2/threads/{threadId}/remove_admins/") Call removeAdmins(@Path("threadId") String threadId, @FieldMap final Map form); + + @FormUrlEncoded + @POST("/api/v1/direct_v2/threads/{threadId}/items/{itemId}/delete/") + Call deleteItem(@Path("threadId") String threadId, + @Path("itemId") String itemId, + @FieldMap final Map form); } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 3e8ba883..5f0d93f0 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -186,14 +186,7 @@ public class DirectThreadViewModel extends AndroidViewModel { reactions.setEmojis(emojis); List 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; - } - } + int index = getItemIndex(item, list); if (index >= 0) { try { final DirectItem clone = (DirectItem) list.get(index).clone(); @@ -255,14 +248,7 @@ public class DirectThreadViewModel extends AndroidViewModel { itemClone.setReactions(reactionsClone); List 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; - } - } + int index = getItemIndex(item, list); if (index >= 0) { list.set(index, itemClone); } @@ -272,6 +258,30 @@ public class DirectThreadViewModel extends AndroidViewModel { } } + private int removeItem(final DirectItem item) { + if (item == null) return 0; + List list = this.items.getValue(); + list = list == null ? new LinkedList<>() : new LinkedList<>(list); + int index = getItemIndex(item, list); + if (index >= 0) { + list.remove(index); + this.items.postValue(list); + } + return index; + } + + private int getItemIndex(final DirectItem item, final List 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; + } + } + return index; + } + private void updateItemSent(final String clientContext, final long timestamp) { if (clientContext == null) return; List list = this.items.getValue(); @@ -675,7 +685,10 @@ public class DirectThreadViewModel extends AndroidViewModel { public LiveData> sendReaction(final DirectItem item, final Emoji emoji) { final MutableLiveData> data = new MutableLiveData<>(); final Long userId = handleCurrentUser(data); - if (userId == null) return data; + if (userId == null) { + data.postValue(Resource.error("userId is null", null)); + return data; + } final String clientContext = UUID.randomUUID().toString(); // Log.d(TAG, "sendText: sending: itemId: " + directItem.getItemId()); data.postValue(Resource.loading(item)); @@ -710,6 +723,39 @@ public class DirectThreadViewModel extends AndroidViewModel { return data; } + public LiveData> unsend(final DirectItem item) { + final MutableLiveData> data = new MutableLiveData<>(); + if (item == null) { + data.postValue(Resource.error("item is null", null)); + return data; + } + final int index = removeItem(item); + final Call request = service.deleteItem(threadId, item.getItemId()); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + if (response.isSuccessful()) { + // Log.d(TAG, "onResponse: " + response.body()); + return; + } + // add the item back if unsuccessful + addItems(index, Collections.singletonList(item)); + 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)); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + data.postValue(Resource.error(t.getMessage(), item)); + Log.e(TAG, "enqueueRequest: onFailure: ", t); + } + }); + return data; + } + private void handleBroadcastReactionRequest(final MutableLiveData> data, final DirectItem item, @NonNull final Call request) { diff --git a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java index 89f74aa3..48dd5863 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java @@ -238,4 +238,13 @@ public class DirectMessagesService extends BaseService { ); return repository.removeAdmins(threadId, form); } + + public Call deleteItem(final String threadId, + final String itemId) { + final ImmutableMap form = ImmutableMap.of( + "_csrftoken", csrfToken, + "_uuid", deviceUuid + ); + return repository.deleteItem(threadId, itemId, form); + } }