From 839e30a4e56c268f3b5ce6bb4e3eae342fe34ed2 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 21 Mar 2021 01:14:57 +0900 Subject: [PATCH] Add delete option in post view. Fixes 1 part of https://github.com/austinhuang0131/barinsta/issues/289 --- .../fragments/PostViewV2Fragment.java | 83 ++++++++++++++++--- .../fragments/main/ProfileFragment.java | 11 ++- .../repositories/MediaRepository.java | 12 +++ .../viewmodels/PostViewV2ViewModel.java | 36 ++++++++ .../webservices/MediaService.java | 32 +++++++ app/src/main/res/menu/post_view_menu.xml | 3 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 162 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 5d0c2ead..0daf6561 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -24,7 +24,6 @@ import android.util.Log; import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; @@ -132,8 +131,10 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im private PostViewV2ViewModel viewModel; private PopupMenu optionsPopup; private EditTextDialogFragment editTextDialogFragment; - + private boolean wasDeleted; private MutableLiveData backStackSavedStateResultLiveData; + private OnDeleteListener onDeleteListener; + private final Observer backStackSavedStateObserver = result -> { if (result == null) return; if (result instanceof String) { @@ -196,6 +197,15 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im this.onShowListener = onShowListener; } + public void setOnDeleteListener(final OnDeleteListener onDeleteListener) { + if (onDeleteListener == null) return; + this.onDeleteListener = onDeleteListener; + } + + public interface OnDeleteListener { + void onDelete(); + } + public static class Builder { private final Media feedModel; private View profilePicElement; @@ -540,7 +550,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im viewModel.getSaved().observe(getViewLifecycleOwner(), this::setSavedResources); viewModel.getOptions().observe(getViewLifecycleOwner(), options -> binding.getRoot().post(() -> { setupOptions(options != null && !options.isEmpty()); - createOptionsPopupMenu(options); + createOptionsPopupMenu(); })); } @@ -1375,30 +1385,73 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im }); } - private void createOptionsPopupMenu(final List options) { - if (options == null) return; + private void createOptionsPopupMenu() { if (optionsPopup == null) { final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle); optionsPopup = new PopupMenu(themeWrapper, binding.options); + } else { + optionsPopup.getMenu().clear(); } optionsPopup.getMenuInflater().inflate(R.menu.post_view_menu, optionsPopup.getMenu()); - final Menu menu = optionsPopup.getMenu(); - final int size = menu.size(); - for (int i = 0; i < size; i++) { - final MenuItem item = menu.getItem(i); - if (item == null) continue; - if (options.contains(item.getItemId())) continue; - menu.removeItem(item.getItemId()); - } + // final Menu menu = optionsPopup.getMenu(); + // final int size = menu.size(); + // for (int i = 0; i < size; i++) { + // final MenuItem item = menu.getItem(i); + // if (item == null) continue; + // if (options.contains(item.getItemId())) continue; + // menu.removeItem(item.getItemId()); + // } optionsPopup.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); if (itemId == R.id.edit_caption) { showCaptionEditDialog(); + return true; + } + if (itemId == R.id.delete) { + item.setEnabled(false); + final LiveData> resourceLiveData = viewModel.delete(); + handleDeleteResource(resourceLiveData, item); } return true; }); } + private void handleDeleteResource(final LiveData> resourceLiveData, final MenuItem item) { + if (resourceLiveData == null) return; + resourceLiveData.observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(final Resource resource) { + try { + switch (resource.status) { + case SUCCESS: + wasDeleted = true; + if (onDeleteListener != null) { + onDeleteListener.onDelete(); + } + break; + case ERROR: + if (item != null) { + item.setEnabled(true); + } + final Snackbar snackbar = Snackbar.make(binding.getRoot(), + R.string.delete_unsuccessful, + Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(R.string.ok, null); + snackbar.show(); + break; + case LOADING: + if (item != null) { + item.setEnabled(false); + } + break; + } + } finally { + resourceLiveData.removeObserver(this); + } + } + }); + } + private void showCaptionEditDialog() { final Caption caption = viewModel.getCaption().getValue(); final String captionText = caption != null ? caption.getText() : null; @@ -1561,4 +1614,8 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im } return navController; } + + public boolean wasDeleted() { + return wasDeleted; + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 12a42ff6..161c8aa9 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -258,7 +258,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe builder.setSharedProfilePicElement(profilePicView) .setSharedMainPostElement(mainPostImage); } - builder.build().show(getChildFragmentManager(), "post_view"); + final PostViewV2Fragment postViewV2Fragment = builder.build(); + postViewV2Fragment.setOnDeleteListener(() -> { + postViewV2Fragment.dismiss(); + binding.postsRecyclerView.refresh(); + }); + postViewV2Fragment.show(getChildFragmentManager(), "post_view"); } }; private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { @@ -1086,7 +1091,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername()); profileDetailsBinding.btnDM.setEnabled(true); }).execute(); - }); + }); profileDetailsBinding.mainProfileImage.setOnClickListener(v -> { if (!hasStories) { // show profile pic @@ -1160,7 +1165,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void updateSwipeRefreshState() { - Log.d("austin_debug", "usrs: "+binding.postsRecyclerView.isFetching()); + Log.d("austin_debug", "usrs: " + binding.postsRecyclerView.isFetching()); binding.swipeRefreshLayout.setRefreshing(binding.postsRecyclerView.isFetching()); } diff --git a/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java b/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java index ea7d97bf..6c71040b 100644 --- a/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/MediaRepository.java @@ -11,6 +11,7 @@ import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.POST; import retrofit2.http.Path; +import retrofit2.http.Query; import retrofit2.http.QueryMap; public interface MediaRepository { @@ -60,4 +61,15 @@ public interface MediaRepository { Call uploadFinish(@Header("retry_context") final String retryContext, @QueryMap Map queryParams, @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{mediaId}/delete/") + Call delete(@Path("mediaId") final String mediaId, + @Query("media_type") final String mediaType, + @FieldMap final Map signedForm); + + @FormUrlEncoded + @POST("/api/v1/media/{mediaId}/archive/") + Call archive(@Path("mediaId") final String mediaId, + @FieldMap final Map signedForm); } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java index 41fd7930..7f53850f 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java @@ -24,6 +24,9 @@ import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.webservices.MediaService; import awais.instagrabber.webservices.ServiceCallback; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -75,6 +78,7 @@ public class PostViewV2ViewModel extends ViewModel { final ImmutableList.Builder builder = ImmutableList.builder(); if (isLoggedIn && media.getUser() != null && media.getUser().getPk() == viewerId) { builder.add(R.id.edit_caption); + builder.add(R.id.delete); } options.postValue(builder.build()); } @@ -290,4 +294,36 @@ public class PostViewV2ViewModel extends ViewModel { public void setViewCount(final Long viewCount) { this.viewCount.postValue(viewCount); } + + public LiveData> delete() { + final MutableLiveData> data = new MutableLiveData<>(); + data.postValue(Resource.loading(null)); + final Call request = mediaService.delete(media.getId(), media.getMediaType()); + if (request == null) { + data.postValue(Resource.success(new Object())); + return data; + } + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + if (!response.isSuccessful()) { + data.postValue(Resource.error(R.string.generic_null_response, null)); + return; + } + final String body = response.body(); + if (body == null) { + data.postValue(Resource.error(R.string.generic_null_response, null)); + return; + } + data.postValue(Resource.success(new Object())); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + Log.e(TAG, "onFailure: ", t); + data.postValue(Resource.error(t.getMessage(), null)); + } + }); + return data; + } } diff --git a/app/src/main/java/awais/instagrabber/webservices/MediaService.java b/app/src/main/java/awais/instagrabber/webservices/MediaService.java index 210c5b32..e1990e87 100644 --- a/app/src/main/java/awais/instagrabber/webservices/MediaService.java +++ b/app/src/main/java/awais/instagrabber/webservices/MediaService.java @@ -5,6 +5,7 @@ import android.util.Log; import androidx.annotation.NonNull; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.json.JSONException; @@ -17,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.repositories.MediaRepository; import awais.instagrabber.repositories.requests.UploadFinishOptions; import awais.instagrabber.repositories.responses.LikersResponse; @@ -33,6 +35,9 @@ import retrofit2.Retrofit; public class MediaService extends BaseService { private static final String TAG = "MediaService"; + private static final List DELETABLE_ITEMS_TYPES = ImmutableList.of(MediaItemType.MEDIA_TYPE_IMAGE, + MediaItemType.MEDIA_TYPE_VIDEO, + MediaItemType.MEDIA_TYPE_SLIDER); private final MediaRepository repository; private final String deviceUuid, csrfToken; @@ -444,4 +449,31 @@ public class MediaService extends BaseService { final Map signedForm = Utils.sign(formBuilder.build()); return repository.uploadFinish(MediaUploadHelper.getRetryContextString(), queryMap, signedForm); } + + public Call delete(@NonNull final String postId, + @NonNull final MediaItemType type) { + if (!DELETABLE_ITEMS_TYPES.contains(type)) return null; + final Map form = new HashMap<>(); + form.put("_csrftoken", csrfToken); + form.put("_uid", userId); + form.put("_uuid", deviceUuid); + form.put("igtv_feed_preview", "false"); + form.put("media_id", postId); + final Map signedForm = Utils.sign(form); + final String mediaType; + switch (type) { + case MEDIA_TYPE_IMAGE: + mediaType = "PHOTO"; + break; + case MEDIA_TYPE_VIDEO: + mediaType = "VIDEO"; + break; + case MEDIA_TYPE_SLIDER: + mediaType = "CAROUSEL"; + break; + default: + return null; + } + return repository.delete(postId, mediaType, signedForm); + } } diff --git a/app/src/main/res/menu/post_view_menu.xml b/app/src/main/res/menu/post_view_menu.xml index d9a8ebc1..aca7a6cc 100644 --- a/app/src/main/res/menu/post_view_menu.xml +++ b/app/src/main/res/menu/post_view_menu.xml @@ -3,4 +3,7 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d67da637..b13df845 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -467,4 +467,5 @@ Response status is not ok! Request failed! Marked as seen + Delete unsuccessful