From 89441a356236245180f1ca0ad2b99141976f4cf1 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Fri, 22 Jan 2021 23:33:36 -0500 Subject: [PATCH 1/4] more collection features --- .../adapters/SavedCollectionsAdapter.java | 7 +- .../viewholder/TopicClusterViewHolder.java | 2 +- .../asyncs/DiscoverPostFetchService.java | 6 +- .../fragments/CollectionPostsFragment.java | 73 ++++++++++- .../fragments/PostViewV2Fragment.java | 30 ++++- .../fragments/SavedCollectionsFragment.java | 38 +++++- .../fragments/main/ProfileFragment.java | 2 +- .../repositories/CollectionRepository.java | 23 ++++ .../repositories/ProfileRepository.java | 7 +- .../responses/WrappedFeedResponse.java | 43 +++++++ .../repositories/responses/WrappedMedia.java | 13 ++ .../discover/TopicalExploreFeedResponse.java | 7 +- .../discover/TopicalExploreItem.java | 15 --- .../responses/saved/SavedCollection.java | 8 ++ .../viewmodels/PostViewV2ViewModel.java | 18 ++- .../webservices/CollectionService.java | 120 ++++++++++++++++++ .../webservices/MediaService.java | 14 +- .../webservices/ProfileService.java | 30 ++++- .../main/res/menu/collection_posts_menu.xml | 21 +++ .../res/menu/saved_collection_select_menu.xml | 22 ++++ .../navigation/direct_messages_nav_graph.xml | 10 ++ .../res/navigation/discover_nav_graph.xml | 10 ++ .../main/res/navigation/feed_nav_graph.xml | 10 ++ .../main/res/navigation/hashtag_nav_graph.xml | 10 ++ .../res/navigation/location_nav_graph.xml | 10 ++ .../notification_viewer_nav_graph.xml | 10 ++ .../main/res/navigation/profile_nav_graph.xml | 6 +- .../main/res/navigation/saved_nav_graph.xml | 4 + app/src/main/res/values/strings.xml | 6 + 29 files changed, 517 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/repositories/CollectionRepository.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/WrappedFeedResponse.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java delete mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreItem.java create mode 100644 app/src/main/java/awais/instagrabber/webservices/CollectionService.java create mode 100644 app/src/main/res/menu/collection_posts_menu.xml create mode 100644 app/src/main/res/menu/saved_collection_select_menu.xml diff --git a/app/src/main/java/awais/instagrabber/adapters/SavedCollectionsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SavedCollectionsAdapter.java index f708a6d5..8d9ff290 100644 --- a/app/src/main/java/awais/instagrabber/adapters/SavedCollectionsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/SavedCollectionsAdapter.java @@ -22,10 +22,13 @@ public class SavedCollectionsAdapter extends ListAdapter items = result.getItems(); + final List items = result.getItems(); final List posts; if (items == null) { posts = Collections.emptyList(); } else { posts = items.stream() - .map(TopicalExploreItem::getMedia) + .map(WrappedMedia::getMedia) .filter(Objects::nonNull) .collect(Collectors.toList()); } diff --git a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java index 82487b3c..ee154b63 100644 --- a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java @@ -10,6 +10,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Handler; +import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; @@ -17,11 +18,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.Toast; import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.PermissionChecker; import androidx.core.graphics.ColorUtils; @@ -49,20 +53,23 @@ import awais.instagrabber.asyncs.SavedPostFetchService; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentCollectionPostsBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; -import awais.instagrabber.fragments.CollectionPostsFragmentDirections; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.saved.SavedCollection; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.Utils; +import awais.instagrabber.webservices.CollectionService; +import awais.instagrabber.webservices.ServiceCallback; import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { + private static final String TAG = "CollectionPostsFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; @@ -75,6 +82,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay private Set selectedFeedModels; private Media downloadFeedModel; private int downloadChildPosition = -1; + private CollectionService collectionService; private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_POSTS_LAYOUT); private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @@ -84,7 +92,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( - R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { + R.menu.saved_collection_select_menu, new PrimaryActionModeCallback.CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { binding.posts.endSelection(); @@ -241,6 +249,11 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay setSharedElementEnterTransition(transitionSet); postponeEnterTransition(); setHasOptionsMenu(true); + final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); + final long userId = CookieUtils.getUserIdFromCookie(cookie); + final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); + final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + collectionService = CollectionService.getInstance(deviceUuid, csrfToken, userId); } @Nullable @@ -267,7 +280,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { - inflater.inflate(R.menu.topic_posts_menu, menu); + inflater.inflate(R.menu.collection_posts_menu, menu); } @Override @@ -276,6 +289,58 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay showPostsLayoutPreferences(); return true; } + else if (item.getItemId() == R.id.delete) { + final Context context = getContext(); + new AlertDialog.Builder(context) + .setTitle(R.string.edit_collection) + .setMessage(R.string.delete_collection_note) + .setPositiveButton(R.string.confirm, (d, w) -> { + collectionService.deleteCollection( + savedCollection.getId(), + new ServiceCallback() { + @Override + public void onSuccess(final String result) { + SavedCollectionsFragment.pleaseRefresh = true; + NavHostFragment.findNavController(CollectionPostsFragment.this).navigateUp(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error deleting collection", t); + Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + else if (item.getItemId() == R.id.edit) { + final Context context = getContext(); + final EditText input = new EditText(context); + new AlertDialog.Builder(context) + .setTitle(R.string.edit_collection) + .setView(input) + .setPositiveButton(R.string.confirm, (d, w) -> { + collectionService.editCollectionName( + savedCollection.getId(), + input.getText().toString(), + new ServiceCallback() { + @Override + public void onSuccess(final String result) { + binding.collapsingToolbarLayout.setTitle(input.getText().toString()); + SavedCollectionsFragment.pleaseRefresh = true; + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error editing collection", t); + Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } return super.onOptionsItemSelected(item); } @@ -372,7 +437,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay private void setupCover() { final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMedias() == null - ? null + ? savedCollection.getCoverMedia() : savedCollection.getCoverMedias().get(0)); final DraweeController controller = Fresco .newDraweeControllerBuilder() diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 1a718e6b..74bee61d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -49,7 +49,10 @@ import androidx.core.view.ViewCompat; import androidx.core.widget.NestedScrollView; import androidx.fragment.app.DialogFragment; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.RecyclerView; @@ -130,6 +133,17 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im private PopupMenu optionsPopup; private EditTextDialogFragment editTextDialogFragment; + private MutableLiveData backStackSavedStateResultLiveData; + private final Observer backStackSavedStateObserver = result -> { + if (result == null) return; + if (result instanceof String) { + final String collection = (String) result; + handleSaveUnsaveResourceLiveData(viewModel.toggleSave(collection, viewModel.getMedia().hasViewerSaved())); + } + // clear result + backStackSavedStateResultLiveData.postValue(null); + }; + // private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() { // // @Override @@ -305,6 +319,17 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im } } + @Override + public void onResume() { + super.onResume(); + final NavController navController = NavHostFragment.findNavController(this); + final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry(); + if (backStackEntry != null) { + backStackSavedStateResultLiveData = backStackEntry.getSavedStateHandle().getLiveData("collection"); + backStackSavedStateResultLiveData.observe(getViewLifecycleOwner(), backStackSavedStateObserver); + } + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -666,7 +691,10 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im handleSaveUnsaveResourceLiveData(viewModel.toggleSave()); }); binding.save.setOnLongClickListener(v -> { - Utils.displayToastAboveView(context, v, getString(R.string.save)); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putBoolean("isSaving", true); + navController.navigate(R.id.action_global_savedCollectionsFragment, bundle); return true; }); } diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java index cf3de76e..b7b0ed5c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java @@ -17,7 +17,10 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; +import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavBackStackEntry; +import androidx.navigation.NavController; import androidx.navigation.fragment.FragmentNavigator; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -27,7 +30,6 @@ import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.SavedCollectionsAdapter; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.databinding.FragmentSavedCollectionsBinding; -import awais.instagrabber.repositories.responses.StoryStickerResponse; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; @@ -40,12 +42,14 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "SavedCollectionsFragment"; + public static boolean pleaseRefresh = false; private MainActivity fragmentActivity; private CoordinatorLayout root; private FragmentSavedCollectionsBinding binding; private SavedCollectionsViewModel savedCollectionsViewModel; private boolean shouldRefresh = true; + private boolean isSaving; private ProfileService profileService; @Override @@ -82,6 +86,12 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa inflater.inflate(R.menu.saved_collection_menu, menu); } + @Override + public void onResume() { + super.onResume(); + if (pleaseRefresh) onRefresh(); + } + @Override public boolean onOptionsItemSelected(@NonNull final MenuItem item) { if (item.getItemId() == R.id.add) { @@ -120,6 +130,8 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa private void init() { setupTopics(); fetchTopics(null); + final SavedCollectionsFragmentArgs fragmentArgs = SavedCollectionsFragmentArgs.fromBundle(getArguments()); + isSaving = fragmentArgs.getIsSaving(); } @Override @@ -131,11 +143,18 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa savedCollectionsViewModel = new ViewModelProvider(fragmentActivity).get(SavedCollectionsViewModel.class); binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2))); final SavedCollectionsAdapter adapter = new SavedCollectionsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> { - final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() - .addSharedElement(cover, "collection-" + topicCluster.getId()); - final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections - .actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor); - NavHostFragment.findNavController(this).navigate(action, builder.build()); + final NavController navController = NavHostFragment.findNavController(this); + if (isSaving) { + setNavControllerResult(navController, topicCluster.getId()); + navController.navigateUp(); + } + else { + final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() + .addSharedElement(cover, "collection-" + topicCluster.getId()); + final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections + .actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor); + navController.navigate(action, builder.build()); + } }); binding.topicsRecyclerView.setAdapter(adapter); savedCollectionsViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); @@ -158,4 +177,11 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa } }); } + + private void setNavControllerResult(@NonNull final NavController navController, final String result) { + final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry(); + if (navBackStackEntry == null) return; + final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle(); + savedStateHandle.set("collection", result); + } } 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 0752cb29..b6a80087 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -946,7 +946,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } }); profileDetailsBinding.btnSaved.setOnClickListener(v -> { - final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(); + final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false); NavHostFragment.findNavController(this).navigate(action); }); profileDetailsBinding.btnLiked.setOnClickListener(v -> { diff --git a/app/src/main/java/awais/instagrabber/repositories/CollectionRepository.java b/app/src/main/java/awais/instagrabber/repositories/CollectionRepository.java new file mode 100644 index 00000000..3afca7e0 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/CollectionRepository.java @@ -0,0 +1,23 @@ +package awais.instagrabber.repositories; + +import java.util.Map; + +import awais.instagrabber.repositories.responses.UserFeedResponse; +import awais.instagrabber.repositories.responses.WrappedFeedResponse; +import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; +import retrofit2.Call; +import retrofit2.http.FieldMap; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +public interface CollectionRepository { + + @FormUrlEncoded + @POST("/api/v1/collections/{id}/{action}/") + Call changeCollection(@Path("id") String id, + @Path("action") String action, + @FieldMap Map signedForm); +} diff --git a/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java index 57071f88..38fdffc7 100644 --- a/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java @@ -2,6 +2,7 @@ package awais.instagrabber.repositories; import java.util.Map; +import awais.instagrabber.repositories.responses.WrappedFeedResponse; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.repositories.responses.UserFeedResponse; import retrofit2.Call; @@ -18,11 +19,11 @@ public interface ProfileRepository { Call fetch(@Path("uid") final long uid, @QueryMap Map queryParams); @GET("/api/v1/feed/saved/") - Call fetchSaved(@QueryMap Map queryParams); + Call fetchSaved(@QueryMap Map queryParams); @GET("/api/v1/feed/collection/{collectionId}/") - Call fetchSavedCollection(@Path("collectionId") final String collectionId, - @QueryMap Map queryParams); + Call fetchSavedCollection(@Path("collectionId") final String collectionId, + @QueryMap Map queryParams); @GET("/api/v1/feed/liked/") Call fetchLiked(@QueryMap Map queryParams); diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/WrappedFeedResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedFeedResponse.java new file mode 100644 index 00000000..e40b584d --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedFeedResponse.java @@ -0,0 +1,43 @@ +package awais.instagrabber.repositories.responses; + +import java.util.List; + +public class WrappedFeedResponse { + private final int numResults; + private final String nextMaxId; + private final boolean moreAvailable; + private final String status; + private final List items; + + public WrappedFeedResponse(final int numResults, + final String nextMaxId, + final boolean moreAvailable, + final String status, + final List items) { + this.numResults = numResults; + this.nextMaxId = nextMaxId; + this.moreAvailable = moreAvailable; + this.status = status; + this.items = items; + } + + public int getNumResults() { + return numResults; + } + + public String getNextMaxId() { + return nextMaxId; + } + + public boolean isMoreAvailable() { + return moreAvailable; + } + + public String getStatus() { + return status; + } + + public List getItems() { + return items; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java new file mode 100644 index 00000000..2bfb1fa1 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedMedia.java @@ -0,0 +1,13 @@ +package awais.instagrabber.repositories.responses; + +public class WrappedMedia { + private final Media media; + + public WrappedMedia(final Media media) { + this.media = media; + } + + public Media getMedia() { + return media; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreFeedResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreFeedResponse.java index 26aa9aa9..f3e7b60f 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreFeedResponse.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreFeedResponse.java @@ -1,6 +1,7 @@ package awais.instagrabber.repositories.responses.discover; import java.util.List; +import awais.instagrabber.repositories.responses.WrappedMedia; public class TopicalExploreFeedResponse { private final boolean moreAvailable; @@ -9,7 +10,7 @@ public class TopicalExploreFeedResponse { private final String status; private final int numResults; private final List clusters; - private final List items; + private final List items; public TopicalExploreFeedResponse(final boolean moreAvailable, final String nextMaxId, @@ -17,7 +18,7 @@ public class TopicalExploreFeedResponse { final String status, final int numResults, final List clusters, - final List items) { + final List items) { this.moreAvailable = moreAvailable; this.nextMaxId = nextMaxId; this.maxId = maxId; @@ -51,7 +52,7 @@ public class TopicalExploreFeedResponse { return clusters; } - public List getItems() { + public List getItems() { return items; } } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreItem.java b/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreItem.java deleted file mode 100644 index 242790a3..00000000 --- a/app/src/main/java/awais/instagrabber/repositories/responses/discover/TopicalExploreItem.java +++ /dev/null @@ -1,15 +0,0 @@ -package awais.instagrabber.repositories.responses.discover; - -import awais.instagrabber.repositories.responses.Media; - -public class TopicalExploreItem { - private final Media media; - - public TopicalExploreItem(final Media media) { - this.media = media; - } - - public Media getMedia() { - return media; - } -} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/saved/SavedCollection.java b/app/src/main/java/awais/instagrabber/repositories/responses/saved/SavedCollection.java index 9e1c264f..2bedfb77 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/saved/SavedCollection.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/saved/SavedCollection.java @@ -10,17 +10,20 @@ public class SavedCollection implements Serializable { private final String collectionName; private final String collectionType; private final int collectionMediacount; + private final Media coverMedia; private final List coverMediaList; public SavedCollection(final String collectionId, final String collectionName, final String collectionType, final int collectionMediacount, + final Media coverMedia, final List coverMediaList) { this.collectionId = collectionId; this.collectionName = collectionName; this.collectionType = collectionType; this.collectionMediacount = collectionMediacount; + this.coverMedia = coverMedia; this.coverMediaList = coverMediaList; } @@ -40,6 +43,11 @@ public class SavedCollection implements Serializable { return collectionMediacount; } + // check the list first, then the single + // i have no idea what condition is required + + public Media getCoverMedia() { return coverMedia; } + public List getCoverMedias() { return coverMediaList; } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java index b17a8d75..41fd7930 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java @@ -188,27 +188,33 @@ public class PostViewV2ViewModel extends ViewModel { @NonNull public LiveData> toggleSave() { if (!media.hasViewerSaved()) { - return save(); + return save(null, false); } return unsave(); } - public LiveData> save() { + @NonNull + public LiveData> toggleSave(final String collection, final boolean ignoreSaveState) { + return save(collection, ignoreSaveState); + } + + public LiveData> save(final String collection, final boolean ignoreSaveState) { final MutableLiveData> data = new MutableLiveData<>(); data.postValue(Resource.loading(null)); - mediaService.save(media.getPk(), getSaveUnsaveCallback(data)); + mediaService.save(media.getPk(), collection, getSaveUnsaveCallback(data, ignoreSaveState)); return data; } public LiveData> unsave() { final MutableLiveData> data = new MutableLiveData<>(); data.postValue(Resource.loading(null)); - mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data)); + mediaService.unsave(media.getPk(), getSaveUnsaveCallback(data, false)); return data; } @NonNull - private ServiceCallback getSaveUnsaveCallback(final MutableLiveData> data) { + private ServiceCallback getSaveUnsaveCallback(final MutableLiveData> data, + final boolean ignoreSaveState) { return new ServiceCallback() { @Override public void onSuccess(final Boolean result) { @@ -217,7 +223,7 @@ public class PostViewV2ViewModel extends ViewModel { return; } data.postValue(Resource.success(true)); - media.setHasViewerSaved(!media.hasViewerSaved()); + if (!ignoreSaveState) media.setHasViewerSaved(!media.hasViewerSaved()); saved.postValue(media.hasViewerSaved()); } diff --git a/app/src/main/java/awais/instagrabber/webservices/CollectionService.java b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java new file mode 100644 index 00000000..faafb6d0 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java @@ -0,0 +1,120 @@ +package awais.instagrabber.webservices; + +import androidx.annotation.NonNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import awais.instagrabber.repositories.CollectionRepository; +import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.utils.Utils; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class CollectionService extends BaseService { + private static final String TAG = "ProfileService"; + + private final CollectionRepository repository; + private final String deviceUuid, csrfToken; + private final long userId; + + private static CollectionService instance; + + private CollectionService(final String deviceUuid, + final String csrfToken, + final long userId) { + this.deviceUuid = deviceUuid; + this.csrfToken = csrfToken; + this.userId = userId; + final Retrofit retrofit = getRetrofitBuilder() + .baseUrl("https://i.instagram.com") + .build(); + repository = retrofit.create(CollectionRepository.class); + } + + public String getCsrfToken() { + return csrfToken; + } + + public String getDeviceUuid() { + return deviceUuid; + } + + public long getUserId() { + return userId; + } + + public static CollectionService getInstance(final String deviceUuid, final String csrfToken, final long userId) { + if (instance == null + || !Objects.equals(instance.getCsrfToken(), csrfToken) + || !Objects.equals(instance.getDeviceUuid(), deviceUuid) + || !Objects.equals(instance.getUserId(), userId)) { + instance = new CollectionService(deviceUuid, csrfToken, userId); + } + return instance; + } + + public void addPostsToCollection(final String collectionId, + final List posts, + final ServiceCallback callback) { + final Map form = new HashMap<>(2); + form.put("module_name", "feed_saved_add_to_collection"); + final List ids; + ids = posts.stream() + .map(Media::getPk) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + form.put("added_media_ids", "[" + String.join(",", ids) + "]"); + changeCollection(collectionId, "edit", form, callback); + } + + public void editCollectionName(final String collectionId, + final String name, + final ServiceCallback callback) { + final Map form = new HashMap<>(1); + form.put("name", name); + changeCollection(collectionId, "edit", form, callback); + } + + public void deleteCollection(final String collectionId, + final ServiceCallback callback) { + changeCollection(collectionId, "delete", null, callback); + } + + public void changeCollection(final String collectionId, + final String action, + final Map options, + final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("_csrftoken", csrfToken); + form.put("_uuid", deviceUuid); + form.put("_uid", userId); + if (options != null) form.putAll(options); + final Map signedForm = Utils.sign(form); + final Call request = repository.changeCollection(collectionId, action, signedForm); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + if (callback == null) return; + final String collectionsListResponse = response.body(); + if (collectionsListResponse == null) { + callback.onSuccess(null); + return; + } + callback.onSuccess(collectionsListResponse); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + if (callback != null) { + callback.onFailure(t); + } + } + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/MediaService.java b/app/src/main/java/awais/instagrabber/webservices/MediaService.java index 1c24f95d..210c5b32 100644 --- a/app/src/main/java/awais/instagrabber/webservices/MediaService.java +++ b/app/src/main/java/awais/instagrabber/webservices/MediaService.java @@ -102,33 +102,37 @@ public class MediaService extends BaseService { public void like(final String mediaId, final ServiceCallback callback) { - action(mediaId, "like", callback); + action(mediaId, "like", null, callback); } public void unlike(final String mediaId, final ServiceCallback callback) { - action(mediaId, "unlike", callback); + action(mediaId, "unlike", null, callback); } public void save(final String mediaId, + final String collection, final ServiceCallback callback) { - action(mediaId, "save", callback); + action(mediaId, "save", collection, callback); } public void unsave(final String mediaId, final ServiceCallback callback) { - action(mediaId, "unsave", callback); + action(mediaId, "unsave", null, callback); } private void action(final String mediaId, final String action, + final String collection, final ServiceCallback callback) { - final Map form = new HashMap<>(4); + final Map form = new HashMap<>(); form.put("media_id", mediaId); form.put("_csrftoken", csrfToken); form.put("_uid", userId); form.put("_uuid", deviceUuid); // form.put("radio_type", "wifi-none"); + if (action.equals("save") && !TextUtils.isEmpty(collection)) form.put("added_collection_ids", "[" + collection + "]"); + // there also exists "removed_collection_ids" which can be used with "save" and "unsave" final Map signedForm = Utils.sign(form); final Call request = repository.action(action, mediaId, signedForm); request.enqueue(new Callback() { diff --git a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java index 4480223a..65f787a6 100644 --- a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java +++ b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java @@ -4,12 +4,19 @@ import androidx.annotation.NonNull; import com.google.common.collect.ImmutableMap; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import awais.instagrabber.repositories.ProfileRepository; +import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.UserFeedResponse; +import awais.instagrabber.repositories.responses.WrappedFeedResponse; +import awais.instagrabber.repositories.responses.WrappedMedia; import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -76,30 +83,40 @@ public class ProfileService extends BaseService { final String collectionId, final ServiceCallback callback) { final ImmutableMap.Builder builder = ImmutableMap.builder(); - Call request = null; + Call request = null; if (!TextUtils.isEmpty(maxId)) { builder.put("max_id", maxId); } if (TextUtils.isEmpty(collectionId) || collectionId.equals("ALL_MEDIA_AUTO_COLLECTION")) request = repository.fetchSaved(builder.build()); else request = repository.fetchSavedCollection(collectionId, builder.build()); - request.enqueue(new Callback() { + request.enqueue(new Callback() { @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) { + public void onResponse(@NonNull final Call call, @NonNull final Response response) { if (callback == null) return; - final UserFeedResponse userFeedResponse = response.body(); + final WrappedFeedResponse userFeedResponse = response.body(); if (userFeedResponse == null) { callback.onSuccess(null); return; } + final List items = userFeedResponse.getItems(); + final List posts; + if (items == null) { + posts = Collections.emptyList(); + } else { + posts = items.stream() + .map(WrappedMedia::getMedia) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } callback.onSuccess(new PostsFetchResponse( - userFeedResponse.getItems(), + posts, userFeedResponse.isMoreAvailable(), userFeedResponse.getNextMaxId() )); } @Override - public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { if (callback != null) { callback.onFailure(t); } @@ -171,7 +188,6 @@ public class ProfileService extends BaseService { }); } - public void fetchLiked(final String maxId, final ServiceCallback callback) { final ImmutableMap.Builder builder = ImmutableMap.builder(); diff --git a/app/src/main/res/menu/collection_posts_menu.xml b/app/src/main/res/menu/collection_posts_menu.xml new file mode 100644 index 00000000..5759b068 --- /dev/null +++ b/app/src/main/res/menu/collection_posts_menu.xml @@ -0,0 +1,21 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/saved_collection_select_menu.xml b/app/src/main/res/menu/saved_collection_select_menu.xml new file mode 100644 index 00000000..466f28f5 --- /dev/null +++ b/app/src/main/res/menu/saved_collection_select_menu.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index 819a428c..470ccdf8 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -76,6 +76,16 @@ app:nullable="false" /> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/location_nav_graph.xml b/app/src/main/res/navigation/location_nav_graph.xml index 5d52d904..acc33413 100644 --- a/app/src/main/res/navigation/location_nav_graph.xml +++ b/app/src/main/res/navigation/location_nav_graph.xml @@ -38,6 +38,16 @@ app:nullable="false" /> + + + + + + diff --git a/app/src/main/res/navigation/notification_viewer_nav_graph.xml b/app/src/main/res/navigation/notification_viewer_nav_graph.xml index dffb4a38..af9764f6 100644 --- a/app/src/main/res/navigation/notification_viewer_nav_graph.xml +++ b/app/src/main/res/navigation/notification_viewer_nav_graph.xml @@ -61,6 +61,16 @@ app:nullable="false" /> + + + + + + + app:destination="@id/saved_nav_graph"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e8edf4c..876e398d 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,6 +99,12 @@ This will remove all added accounts from the app!\nTo remove just one account, long tap the account from the account switcher dialog.\nDo you want to continue? Date format Create new collection + Edit collection name + Delete collection + Are you sure you want to delete this collection? + All contained media will remain in other collections. + Add to collection... + Remove from collection Liked Saved Tagged From eba37705e2f55326617246e8235b0f922cea31da Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 23 Jan 2021 11:32:34 -0500 Subject: [PATCH 2/4] #583 --- .../fragments/CommentsViewerFragment.java | 9 +++++--- .../fragments/FollowViewerFragment.java | 21 +++++++++++++------ .../NotificationsViewerFragment.java | 14 +++++++++---- .../fragments/StoryViewerFragment.java | 7 +++++-- .../instagrabber/webservices/BaseService.java | 1 + 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java index 338300dd..18cc9762 100644 --- a/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java @@ -99,9 +99,12 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl @Override public void onFailure(Throwable t) { - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); - binding.swipeRefreshLayout.setRefreshing(false); - stopCurrentExecutor(); + try { + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + binding.swipeRefreshLayout.setRefreshing(false); + stopCurrentExecutor(); + } + catch(Throwable e) {} } }; diff --git a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java index 11ee076c..115f5849 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java @@ -83,8 +83,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh @Override public void onFailure(final Throwable t) { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} Log.e(TAG, "Error fetching list (double, following)", t); } }; @@ -109,8 +112,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh @Override public void onFailure(final Throwable t) { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} Log.e(TAG, "Error fetching list (double, follower)", t); } }; @@ -221,8 +227,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh @Override public void onFailure(final Throwable t) { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} Log.e(TAG, "Error fetching list (single)", t); } }; diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index 1b372ea1..34dc5922 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -271,8 +271,11 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe @Override public void onFailure(Throwable t) { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); break; @@ -287,8 +290,11 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe @Override public void onFailure(final Throwable t) { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} } }); break; diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index accf2b06..337f2754 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -236,8 +236,11 @@ public class StoryViewerFragment extends Fragment { @Override public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "onFailure: ", t); + try { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "onFailure: ", t); + } + catch(Throwable e) {} } }); } catch (UnsupportedEncodingException e) { diff --git a/app/src/main/java/awais/instagrabber/webservices/BaseService.java b/app/src/main/java/awais/instagrabber/webservices/BaseService.java index ca850400..e30aa992 100644 --- a/app/src/main/java/awais/instagrabber/webservices/BaseService.java +++ b/app/src/main/java/awais/instagrabber/webservices/BaseService.java @@ -35,6 +35,7 @@ public abstract class BaseService { final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) + .setLenient() .create(); builder = new Retrofit.Builder() .addConverterFactory(ScalarsConverterFactory.create()) From 8dabf3514671954253f0474fefd2748c73b93671 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 23 Jan 2021 12:00:54 -0500 Subject: [PATCH 3/4] unlock hd photos --- .../settings/SettingsPreferencesFragment.java | 5 ++ .../awais/instagrabber/utils/FlavorTown.java | 3 +- .../instagrabber/utils/UserAgentUtils.java | 48 ++++--------------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java index 15baafeb..00628d3f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java @@ -26,7 +26,9 @@ import awais.instagrabber.dialogs.TimeSettingsDialog; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DirectoryChooser; +import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.UserAgentUtils; import awais.instagrabber.utils.Utils; import static awais.instagrabber.utils.Constants.FOLDER_PATH; @@ -111,6 +113,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { preference.setEntryValues(values); preference.setOnPreferenceChangeListener((preference1, newValue) -> { shouldRecreate(); + final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE); + final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage()); + settingsHelper.putString(Constants.APP_UA, appUa); return true; }); return preference; diff --git a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java index d37d1ce9..85ad0a2f 100755 --- a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java +++ b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java @@ -102,7 +102,8 @@ public final class FlavorTown { } public static void changelogCheck(@NonNull final Context context) { - if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) { +// if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) { + if (true) { final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE); final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage()); settingsHelper.putString(Constants.APP_UA, appUa); diff --git a/app/src/main/java/awais/instagrabber/utils/UserAgentUtils.java b/app/src/main/java/awais/instagrabber/utils/UserAgentUtils.java index 36944cce..c9397cdc 100644 --- a/app/src/main/java/awais/instagrabber/utils/UserAgentUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/UserAgentUtils.java @@ -20,49 +20,21 @@ public class UserAgentUtils { // use APKpure, assume x86 private static final String igVersion = "169.3.0.30.135"; private static final String igVersionCode = "264009054"; - // https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json - // presumed constant, so no need to update + // only pick the ones that has width 1440 for maximum download quality public static final String[] devices = { - "25/7.1.1; 440dpi; 1080x1920; Xiaomi; Mi Note 3; jason; qcom", - "23/6.0.1; 480dpi; 1080x1920; Xiaomi; Redmi Note 3; kenzo; qcom", - "23/6.0; 480dpi; 1080x1920; Xiaomi; Redmi Note 4; nikel; mt6797", - "24/7.0; 480dpi; 1080x1920; Xiaomi/xiaomi; Redmi Note 4; mido; qcom", - "23/6.0; 480dpi; 1080x1920; Xiaomi; Redmi Note 4X; nikel; mt6797", - "27/8.1.0; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi Note 5; whyred; qcom", - "23/6.0.1; 480dpi; 1080x1920; Xiaomi; Redmi 4; markw; qcom", - "27/8.1.0; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom", - "25/7.1.2; 440dpi; 1080x2030; Xiaomi/xiaomi; Redmi 5 Plus; vince; qcom", - "26/8.0.0; 480dpi; 1080x1920; Xiaomi; MI 5; gemini; qcom", - "27/8.1.0; 480dpi; 1080x1920; Xiaomi/xiaomi; Mi A1; tissot_sprout; qcom", - "26/8.0.0; 480dpi; 1080x1920; Xiaomi; MI 6; sagit; qcom", - "25/7.1.1; 440dpi; 1080x1920; Xiaomi; MI MAX 2; oxygen; qcom", - "24/7.0; 480dpi; 1080x1920; Xiaomi; MI 5s; capricorn; qcom", - "26/8.0.0; 480dpi; 1080x1920; samsung; SM-A520F; a5y17lte; samsungexynos7880", - "26/8.0.0; 480dpi; 1080x2076; samsung; SM-G950F; dreamlte; samsungexynos8895", + // https://github.com/dilame/instagram-private-api/blob/master/src/samples/devices.json "26/8.0.0; 640dpi; 1440x2768; samsung; SM-G950F; dreamlte; samsungexynos8895", - "26/8.0.0; 420dpi; 1080x2094; samsung; SM-G955F; dream2lte; samsungexynos8895", "26/8.0.0; 560dpi; 1440x2792; samsung; SM-G955F; dream2lte; samsungexynos8895", - "24/7.0; 480dpi; 1080x1920; samsung; SM-A510F; a5xelte; samsungexynos7580", - "26/8.0.0; 480dpi; 1080x1920; samsung; SM-G930F; herolte; samsungexynos8890", - "26/8.0.0; 480dpi; 1080x1920; samsung; SM-G935F; hero2lte; samsungexynos8890", - "26/8.0.0; 420dpi; 1080x2094; samsung; SM-G965F; star2lte; samsungexynos9810", - "26/8.0.0; 480dpi; 1080x2076; samsung; SM-A530F; jackpotlte; samsungexynos7885", "24/7.0; 640dpi; 1440x2560; samsung; SM-G925F; zerolte; samsungexynos7420", - "26/8.0.0; 420dpi; 1080x1920; samsung; SM-A720F; a7y17lte; samsungexynos7880", "24/7.0; 640dpi; 1440x2560; samsung; SM-G920F; zeroflte; samsungexynos7420", - "24/7.0; 420dpi; 1080x1920; samsung; SM-J730FM; j7y17lte; samsungexynos7870", - "26/8.0.0; 480dpi; 1080x2076; samsung; SM-G960F; starlte; samsungexynos9810", - "26/8.0.0; 420dpi; 1080x2094; samsung; SM-N950F; greatlte; samsungexynos8895", - "26/8.0.0; 420dpi; 1080x2094; samsung; SM-A730F; jackpot2lte; samsungexynos7885", - "26/8.0.0; 420dpi; 1080x2094; samsung; SM-A605FN; a6plte; qcom", - "26/8.0.0; 480dpi; 1080x1920; HUAWEI/HONOR; STF-L09; HWSTF; hi3660", - "27/8.1.0; 480dpi; 1080x2280; HUAWEI/HONOR; COL-L29; HWCOL; kirin970", - "26/8.0.0; 480dpi; 1080x2032; HUAWEI/HONOR; LLD-L31; HWLLD-H; hi6250", - "26/8.0.0; 480dpi; 1080x2150; HUAWEI; ANE-LX1; HWANE; hi6250", - "26/8.0.0; 480dpi; 1080x2032; HUAWEI; FIG-LX1; HWFIG-H; hi6250", - "27/8.1.0; 480dpi; 1080x2150; HUAWEI/HONOR; COL-L29; HWCOL; kirin970", - "26/8.0.0; 480dpi; 1080x2038; HUAWEI/HONOR; BND-L21; HWBND-H; hi6250", - "23/6.0.1; 420dpi; 1080x1920; LeMobile/LeEco; Le X527; le_s2_ww; qcom" + // https://github.com/mimmi20/BrowserDetector/tree/master + "28/9; 560dpi; 1440x2792; samsung; SM-N960F; crownlte; samsungexynos9810", + // mgp25 + "23/6.0.1; 640dpi; 1440x2392; LGE/lge; RS988; h1; h1", + "24/7.0; 640dpi; 1440x2560; HUAWEI; LON-L29; HWLON; hi3660", + "23/6.0.1; 640dpi; 1440x2560; ZTE; ZTE A2017U; ailsa_ii; qcom", + "23/6.0.1; 640dpi; 1440x2560; samsung; SM-G935F; hero2lte; samsungexynos8890", + "23/6.0.1; 640dpi; 1440x2560; samsung; SM-G930F; herolte; samsungexynos8890" }; @NonNull From 78e3ffe04af3c40f8f3caba61f7c84b0fb36a701 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 23 Jan 2021 14:17:56 -0500 Subject: [PATCH 4/4] private api option for profilefetcher --- .../instagrabber/asyncs/ProfileFetcher.java | 156 ++++++------------ .../fragments/main/ProfileFragment.java | 2 +- .../repositories/GraphQLRepository.java | 4 + .../repositories/UserRepository.java | 11 +- .../repositories/responses/User.java | 6 +- .../repositories/responses/WrappedUser.java | 13 ++ .../awais/instagrabber/utils/FlavorTown.java | 3 +- .../viewmodels/AppStateViewModel.java | 1 + .../webservices/GraphQLService.java | 78 +++++++++ .../instagrabber/webservices/UserService.java | 55 +++++- 10 files changed, 214 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/WrappedUser.java diff --git a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java index 6218ef8b..46559dfb 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java @@ -5,131 +5,81 @@ import android.util.Log; import androidx.annotation.Nullable; -import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; -import java.net.HttpURLConnection; -import java.net.URL; - -import awais.instagrabber.BuildConfig; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.User; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.NetworkUtils; -import awais.instagrabber.utils.TextUtils; -import awaisomereport.LogCollector; +import awais.instagrabber.webservices.GraphQLService; +import awais.instagrabber.webservices.ServiceCallback; +import awais.instagrabber.webservices.UserService; -import static awais.instagrabber.utils.Utils.logCollector; -import static awais.instagrabber.utils.Utils.settingsHelper; - -public final class ProfileFetcher extends AsyncTask { +public final class ProfileFetcher extends AsyncTask { private static final String TAG = ProfileFetcher.class.getSimpleName(); + private final UserService userService; + private final GraphQLService graphQLService; private final FetchListener fetchListener; + private final boolean isLoggedIn; private final String userName; - public ProfileFetcher(String userName, FetchListener fetchListener) { + public ProfileFetcher(final String userName, + final boolean isLoggedIn, + final FetchListener fetchListener) { this.userName = userName; + this.isLoggedIn = isLoggedIn; this.fetchListener = fetchListener; + userService = isLoggedIn ? UserService.getInstance() : null; + graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); } @Nullable @Override - protected User doInBackground(final Void... voids) { - User result = null; + protected String doInBackground(final Void... voids) { + if (isLoggedIn) { + userService.getUsernameInfo(userName, new ServiceCallback() { + @Override + public void onSuccess(final User user) { + Log.d("austin_debug", user.getUsername() + " " + userName); + userService.getUserFriendship(user.getPk(), new ServiceCallback() { + @Override + public void onSuccess(final FriendshipStatus status) { + user.setFriendshipStatus(status); + fetchListener.onResult(user); + } - try { - final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/" + userName + "/?__a=1").openConnection(); - conn.setUseCaches(true); - conn.connect(); + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error", t); + } + }); + } - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - final String json = NetworkUtils.readFromConnection(conn); - // Log.d(TAG, "doInBackground: " + json); - final JSONObject userJson = new JSONObject(json).getJSONObject("graphql") - .getJSONObject(Constants.EXTRAS_USER); - - final String cookie = settingsHelper.getString(Constants.COOKIE); - - boolean isPrivate = userJson.getBoolean("is_private"); - final long id = userJson.optLong(Constants.EXTRAS_ID, 0); - final long uid = CookieUtils.getUserIdFromCookie(cookie); - final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media"); - // if (timelineMedia.has("edges")) { - // final JSONArray edges = timelineMedia.getJSONArray("edges"); - // } - - String url = userJson.optString("external_url"); - if (TextUtils.isEmpty(url)) url = null; - - return new User( - id, - userName, - userJson.getString("full_name"), - isPrivate, - userJson.getString("profile_pic_url_hd"), - null, - new FriendshipStatus( - userJson.optBoolean("followed_by_viewer"), - userJson.optBoolean("follows_viewer"), - userJson.optBoolean("blocked_by_viewer"), - false, - isPrivate, - userJson.optBoolean("has_requested_viewer"), - userJson.optBoolean("requested_by_viewer"), - false, - userJson.optBoolean("restricted_by_viewer"), - false - ), - userJson.getBoolean("is_verified"), - false, - false, - false, - false, - null, - null, - timelineMedia.getLong("count"), - userJson.getJSONObject("edge_followed_by").getLong("count"), - userJson.getJSONObject("edge_follow").getLong("count"), - 0, - userJson.getString("biography"), - url, - 0, - null); - - // result = new ProfileModel(isPrivate, - // !user.optBoolean("followed_by_viewer") && (id != uid && isPrivate), - // user.getBoolean("is_verified"), - // id, - // userName, - // user.getString("full_name"), - // user.getString("biography"), - // url, - // user.getString("profile_pic_url"), - // user.getString("profile_pic_url_hd"), - // timelineMedia.getLong("count"), - // user.getJSONObject("edge_followed_by").getLong("count"), - // user.getJSONObject("edge_follow").getLong("count"), - // user.optBoolean("followed_by_viewer"), - // user.optBoolean("follows_viewer"), - // user.optBoolean("restricted_by_viewer"), - // user.optBoolean("blocked_by_viewer"), - // user.optBoolean("requested_by_viewer")); - } - - conn.disconnect(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error", t); + } + }); } + else { + graphQLService.fetchUser(userName, new ServiceCallback() { + @Override + public void onSuccess(final User user) { + fetchListener.onResult(user); + } - return result; + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error", t); + } + }); + } + return "yeah"; } @Override - protected void onPostExecute(final User result) { - if (fetchListener != null) fetchListener.onResult(result); + protected void onPreExecute() { + if (fetchListener != null) fetchListener.doBefore(); } } 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 b6a80087..d0567bfb 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -538,7 +538,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private void fetchProfileDetails() { if (TextUtils.isEmpty(username)) return; - new ProfileFetcher(username.trim().substring(1), profileModel -> { + new ProfileFetcher(username.trim().substring(1), isLoggedIn, profileModel -> { if (getContext() == null) return; this.profileModel = profileModel; setProfileDetails(); diff --git a/app/src/main/java/awais/instagrabber/repositories/GraphQLRepository.java b/app/src/main/java/awais/instagrabber/repositories/GraphQLRepository.java index 23020035..0165ad9e 100644 --- a/app/src/main/java/awais/instagrabber/repositories/GraphQLRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/GraphQLRepository.java @@ -4,9 +4,13 @@ import java.util.Map; import retrofit2.Call; import retrofit2.http.GET; +import retrofit2.http.Path; import retrofit2.http.QueryMap; public interface GraphQLRepository { @GET("/graphql/query/") Call fetch(@QueryMap(encoded = true) Map queryParams); + + @GET("/{username}/?__a=1") + Call getUser(@Path("username") String username); } diff --git a/app/src/main/java/awais/instagrabber/repositories/UserRepository.java b/app/src/main/java/awais/instagrabber/repositories/UserRepository.java index 5812f5a6..4b14034b 100644 --- a/app/src/main/java/awais/instagrabber/repositories/UserRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/UserRepository.java @@ -1,7 +1,8 @@ package awais.instagrabber.repositories; -import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.UserSearchResponse; +import awais.instagrabber.repositories.responses.WrappedUser; import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Path; @@ -10,7 +11,13 @@ import retrofit2.http.Query; public interface UserRepository { @GET("/api/v1/users/{uid}/info/") - Call getUserInfo(@Path("uid") final long uid); + Call getUserInfo(@Path("uid") final long uid); + + @GET("/api/v1/users/{username}/usernameinfo/") + Call getUsernameInfo(@Path("username") final String username); + + @GET("/api/v1/friendships/show/{uid}/") + Call getUserFriendship(@Path("uid") final long uid); @GET("/api/v1/users/search/") Call search(@Query("timezone_offset") float timezoneOffset, diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/User.java b/app/src/main/java/awais/instagrabber/repositories/responses/User.java index 65646392..29660685 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/User.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/User.java @@ -10,7 +10,7 @@ public class User implements Serializable { private final boolean isPrivate; private final String profilePicUrl; private final String profilePicId; - private final FriendshipStatus friendshipStatus; + private FriendshipStatus friendshipStatus; private final boolean isVerified; private final boolean hasAnonymousProfilePicture; private final boolean isUnpublished; @@ -102,6 +102,10 @@ public class User implements Serializable { return friendshipStatus; } + public void setFriendshipStatus(final FriendshipStatus friendshipStatus) { + this.friendshipStatus = friendshipStatus; + } + public boolean isVerified() { return isVerified; } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/WrappedUser.java b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedUser.java new file mode 100644 index 00000000..66a30d88 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/WrappedUser.java @@ -0,0 +1,13 @@ +package awais.instagrabber.repositories.responses; + +public class WrappedUser { + private final User user; + + public WrappedUser(final User user) { + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java index 85ad0a2f..d37d1ce9 100755 --- a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java +++ b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java @@ -102,8 +102,7 @@ public final class FlavorTown { } public static void changelogCheck(@NonNull final Context context) { -// if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) { - if (true) { + if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) { final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE); final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage()); settingsHelper.putString(Constants.APP_UA, appUa); diff --git a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java index 37158b60..ce0335eb 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java @@ -83,6 +83,7 @@ public class AppStateViewModel extends AndroidViewModel { if (TextUtils.isEmpty(username)) return; new ProfileFetcher( username.trim().substring(1), + true, currentUser::postValue ).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } diff --git a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java index 06a7af8c..4b744ac2 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java @@ -271,4 +271,82 @@ public class GraphQLService extends BaseService { } }); } + + public void fetchUser(final String username, + final ServiceCallback callback) { + final Call request = repository.getUser(username); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String rawBody = response.body(); + if (rawBody == null) { + Log.e(TAG, "Error occurred while fetching gql user of " + username); + callback.onSuccess(null); + return; + } + try { + final JSONObject body = new JSONObject(rawBody); + final JSONObject userJson = body.getJSONObject("graphql") + .getJSONObject(Constants.EXTRAS_USER); + + boolean isPrivate = userJson.getBoolean("is_private"); + final long id = userJson.optLong(Constants.EXTRAS_ID, 0); + final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media"); + // if (timelineMedia.has("edges")) { + // final JSONArray edges = timelineMedia.getJSONArray("edges"); + // } + + String url = userJson.optString("external_url"); + if (TextUtils.isEmpty(url)) url = null; + + callback.onSuccess(new User( + id, + username, + userJson.getString("full_name"), + isPrivate, + userJson.getString("profile_pic_url_hd"), + null, + new FriendshipStatus( + userJson.optBoolean("followed_by_viewer"), + userJson.optBoolean("follows_viewer"), + userJson.optBoolean("blocked_by_viewer"), + false, + isPrivate, + userJson.optBoolean("has_requested_viewer"), + userJson.optBoolean("requested_by_viewer"), + false, + userJson.optBoolean("restricted_by_viewer"), + false + ), + userJson.getBoolean("is_verified"), + false, + false, + false, + false, + null, + null, + timelineMedia.getLong("count"), + userJson.getJSONObject("edge_followed_by").getLong("count"), + userJson.getJSONObject("edge_follow").getLong("count"), + 0, + userJson.getString("biography"), + url, + 0, + null)); + } catch (JSONException e) { + Log.e(TAG, "onResponse", e); + if (callback != null) { + callback.onFailure(e); + } + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + if (callback != null) { + callback.onFailure(t); + } + } + }); + } } diff --git a/app/src/main/java/awais/instagrabber/webservices/UserService.java b/app/src/main/java/awais/instagrabber/webservices/UserService.java index c3b3e9a4..0aff3f32 100644 --- a/app/src/main/java/awais/instagrabber/webservices/UserService.java +++ b/app/src/main/java/awais/instagrabber/webservices/UserService.java @@ -5,8 +5,10 @@ import androidx.annotation.NonNull; import java.util.TimeZone; import awais.instagrabber.repositories.UserRepository; +import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.UserSearchResponse; +import awais.instagrabber.repositories.responses.WrappedUser; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -34,25 +36,66 @@ public class UserService extends BaseService { } public void getUserInfo(final long uid, final ServiceCallback callback) { - final Call request = repository.getUserInfo(uid); - request.enqueue(new Callback() { + final Call request = repository.getUserInfo(uid); + request.enqueue(new Callback() { @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) { - final User user = response.body(); + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final WrappedUser user = response.body(); if (user == null) { callback.onSuccess(null); return; } - callback.onSuccess(user); + callback.onSuccess(user.getUser()); } @Override - public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { callback.onFailure(t); } }); } + public void getUsernameInfo(final String username, final ServiceCallback callback) { + final Call request = repository.getUsernameInfo(username); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final WrappedUser user = response.body(); + if (user == null) { + callback.onSuccess(null); + return; + } + callback.onSuccess(user.getUser()); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + callback.onFailure(t); + } + }); + } + + public void getUserFriendship(final long uid, final ServiceCallback callback) { + final Call request = repository.getUserFriendship(uid); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final FriendshipStatus status = response.body(); + if (status == null) { + callback.onSuccess(null); + return; + } + callback.onSuccess(status); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + callback.onFailure(t); + } + }); + } + + public Call search(final String query) { final float timezoneOffset = (float) TimeZone.getDefault().getRawOffset() / 1000; return repository.search(timezoneOffset, query);