From 895cf15623952e19c82fa193083cbd0cccc97494 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 3 Nov 2020 17:23:20 +0900 Subject: [PATCH] Add multi selection mode to all new post view fragments. Fix Delete action in download notification. --- app/src/main/AndroidManifest.xml | 2 + .../adapters/DiscoverAdapter.java | 57 ---- .../instagrabber/adapters/FeedAdapterV2.java | 95 +++++- .../viewholder/FeedGridItemViewHolder.java | 25 +- .../viewholder/PostMediaViewHolder.java | 2 +- .../adapters/viewholder/PostViewHolder.java | 2 +- .../instagrabber/asyncs/DiscoverFetcher.java | 202 ------------- .../instagrabber/asyncs/FeedFetcher.java | 270 ------------------ .../instagrabber/asyncs/PostsFetcher.java | 182 ------------ .../customviews/PostsRecyclerView.java | 12 +- .../fragments/HashTagFragment.java | 186 +++++------- .../fragments/LocationFragment.java | 200 +++++-------- .../fragments/SavedViewerFragment.java | 104 ++++--- .../fragments/TopicPostsFragment.java | 97 +++++++ .../fragments/main/FeedFragment.java | 102 ++++++- .../fragments/main/ProfileFragment.java | 112 +++++--- .../instagrabber/models/BasePostModel.java | 9 - .../awais/instagrabber/models/FeedModel.java | 51 +--- .../services/DeleteImageIntentService.java | 64 +++++ .../instagrabber/utils/DownloadUtils.java | 63 ++-- .../instagrabber/workers/DownloadWorker.java | 162 ++++++----- .../drawable/ic_baseline_check_circle_24.xml | 10 + app/src/main/res/layout/item_feed_grid.xml | 8 +- app/src/main/res/layout/item_post.xml | 2 +- 24 files changed, 796 insertions(+), 1223 deletions(-) delete mode 100755 app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java delete mode 100755 app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java delete mode 100755 app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java delete mode 100755 app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java create mode 100644 app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java create mode 100644 app/src/main/res/drawable/ic_baseline_check_circle_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0847f2f..58a12069 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,8 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> + + \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java deleted file mode 100755 index 5a1af57e..00000000 --- a/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java +++ /dev/null @@ -1,57 +0,0 @@ -package awais.instagrabber.adapters; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; - -import awais.instagrabber.R; -import awais.instagrabber.adapters.viewholder.DiscoverViewHolder; -import awais.instagrabber.models.DiscoverItemModel; -import awais.instagrabber.models.enums.MediaItemType; - -public final class DiscoverAdapter extends MultiSelectListAdapter { - - private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { - @Override - public boolean areItemsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) { - return oldItem.getPostId().equals(newItem.getPostId()); - } - - @Override - public boolean areContentsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) { - return oldItem.getPostId().equals(newItem.getPostId()); - } - }; - - public DiscoverAdapter(final OnItemClickListener clickListener, - final OnItemLongClickListener longClickListener) { - super(diffCallback, clickListener, longClickListener); - } - - @NonNull - @Override - public DiscoverViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); - return new DiscoverViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) { - final DiscoverItemModel itemModel = getItem(position); - if (itemModel != null) { - itemModel.setPosition(position); - holder.itemView.setTag(itemModel); - holder.itemView.setOnClickListener(v -> getInternalOnItemClickListener().onItemClick(itemModel, position)); - holder.itemView.setOnLongClickListener(v -> getInternalOnLongItemClickListener().onItemLongClick(itemModel, position)); - final MediaItemType mediaType = itemModel.getItemType(); - holder.typeIcon.setVisibility( - mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); - holder.typeIcon.setImageResource(mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? R.drawable.ic_slider_24 : R.drawable.ic_video_24); - holder.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE); - holder.postImage.setImageURI(itemModel.getDisplayUrl()); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java b/app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java index 30baeda0..0d17f17a 100644 --- a/app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java +++ b/app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java @@ -10,6 +10,9 @@ import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; +import java.util.HashSet; +import java.util.Set; + import awais.instagrabber.adapters.viewholder.FeedGridItemViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder; @@ -26,15 +29,13 @@ import awais.instagrabber.models.enums.MediaItemType; public final class FeedAdapterV2 extends ListAdapter { private static final String TAG = "FeedAdapterV2"; - private PostsLayoutPreferences layoutPreferences; private final FeedItemCallback feedItemCallback; + private final SelectionModeCallback selectionModeCallback; + private final Set selectedPositions = new HashSet<>(); + private final Set selectedFeedModels = new HashSet<>(); - // private final View.OnLongClickListener longClickListener = v -> { - // final Object tag; - // if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) - // Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption()); - // return true; - // }; + private PostsLayoutPreferences layoutPreferences; + private boolean selectionModeActive = false; private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { @@ -48,12 +49,56 @@ public final class FeedAdapterV2 extends ListAdapter selectedFeedModels); + + void onSelectionEnd(); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java index 0f26bb86..db0545ac 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java @@ -31,16 +31,29 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { public FeedGridItemViewHolder(@NonNull final ItemFeedGridBinding binding) { super(binding.getRoot()); this.binding = binding; - // for rounded borders (clip view to background shape) - // } - public void bind(@NonNull final FeedModel feedModel, + public void bind(final int position, + @NonNull final FeedModel feedModel, @NonNull final PostsLayoutPreferences layoutPreferences, - final FeedAdapterV2.FeedItemCallback feedItemCallback) { - if (feedItemCallback != null) { - itemView.setOnClickListener(v -> feedItemCallback.onPostClick(feedModel, binding.profilePic, binding.postImage)); + final FeedAdapterV2.FeedItemCallback feedItemCallback, + final FeedAdapterV2.AdapterSelectionCallback adapterSelectionCallback, + final boolean selectionModeActive, + final boolean selected) { + itemView.setOnClickListener(v -> { + if (!selectionModeActive && feedItemCallback != null) { + feedItemCallback.onPostClick(feedModel, binding.profilePic, binding.postImage); + return; + } + if (selectionModeActive && adapterSelectionCallback != null) { + adapterSelectionCallback.onPostClick(position, feedModel); + } + }); + if (adapterSelectionCallback != null) { + itemView.setOnLongClickListener(v -> adapterSelectionCallback.onPostLongClick(position, feedModel)); } + binding.selectedView.setVisibility(selected ? View.VISIBLE : View.GONE); + // for rounded borders (clip view to background shape) itemView.setClipToOutline(layoutPreferences.getHasRoundedCorners()); if (layoutPreferences.getType() == STAGGERED_GRID) { final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight(); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java index 8d247dcf..d2d265a8 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java @@ -19,7 +19,7 @@ public final class PostMediaViewHolder extends RecyclerView.ViewHolder { public void bind(final ViewerPostModel model, final int position, final View.OnClickListener clickListener) { if (model == null) return; - model.setPosition(position); + // model.setPosition(position); itemView.setTag(model); itemView.setOnClickListener(clickListener); binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java index 39c1d61a..5900ebc1 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java @@ -25,7 +25,7 @@ public final class PostViewHolder extends RecyclerView.ViewHolder { final OnItemClickListener clickListener, final OnItemLongClickListener longClickListener) { if (postModel == null) return; - postModel.setPosition(position); + // postModel.setPosition(position); itemView.setOnClickListener(v -> clickListener.onItemClick(postModel, position)); itemView.setOnLongClickListener(v -> longClickListener.onItemLongClick(postModel, position)); diff --git a/app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java deleted file mode 100755 index f86958aa..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java +++ /dev/null @@ -1,202 +0,0 @@ -package awais.instagrabber.asyncs; - -import android.os.AsyncTask; -import android.os.Environment; -import android.util.Log; -import android.util.Pair; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.File; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.DiscoverItemModel; -import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.NetworkUtils; -import awais.instagrabber.utils.ResponseBodyUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; -import static awais.instagrabber.utils.Utils.logCollector; -import static awais.instagrabber.utils.Utils.settingsHelper; - -public final class DiscoverFetcher extends AsyncTask { - private final String cluster, maxId, rankToken; - private final FetchListener fetchListener; - private int lastId = 0; - private boolean isFirst, moreAvailable; - private String nextMaxId; - - public DiscoverFetcher(final String cluster, final String maxId, final String rankToken, - final FetchListener fetchListener, final boolean isFirst) { - this.cluster = cluster == null ? "explore_all%3A0" : cluster.replace(":", "%3A"); - this.maxId = maxId == null ? "" : "&max_id=" + maxId; - this.rankToken = rankToken; - this.fetchListener = fetchListener; - this.isFirst = isFirst; - } - - @Nullable - @Override - protected final DiscoverItemModel[] doInBackground(final Void... voids) { - - DiscoverItemModel[] result = null; - - final ArrayList discoverItemModels = fetchItems(null, maxId); - if (discoverItemModels != null) { - result = discoverItemModels.toArray(new DiscoverItemModel[0]); - if (result.length > 0) { - final DiscoverItemModel lastModel = result[result.length - 1]; - if (lastModel != null && nextMaxId != null) lastModel.setMore(moreAvailable, nextMaxId); - } - } - - return result; - } - - private ArrayList fetchItems(ArrayList discoverItemModels, final String maxId) { - try { - final String url = "https://www.instagram.com/explore/grid/?is_prefetch=false&omit_cover_media=true&module=explore_popular" + - "&use_sectional_payload=false&cluster_id=" + cluster + "&include_fixed_destinations=true&session_id=" + rankToken + maxId; - - final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); - - urlConnection.setUseCaches(false); - urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT); - - if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { - final JSONObject discoverResponse = new JSONObject(NetworkUtils.readFromConnection(urlConnection)); - - moreAvailable = discoverResponse.getBoolean("more_available"); - nextMaxId = discoverResponse.optString("next_max_id"); - - final JSONArray sectionalItems = discoverResponse.getJSONArray("sectional_items"); - if (discoverItemModels == null) discoverItemModels = new ArrayList<>(sectionalItems.length() * 2); - - for (int i = 0; i < sectionalItems.length(); ++i) { - final JSONObject sectionItem = sectionalItems.getJSONObject(i); - - final String feedType = sectionItem.getString("feed_type"); - final String layoutType = sectionItem.getString("layout_type"); - - if (sectionItem.has("layout_content") && feedType.equals("media")) { - final JSONObject layoutContent = sectionItem.getJSONObject("layout_content"); - - if ("media_grid".equals(layoutType)) { - final JSONArray medias = layoutContent.getJSONArray("medias"); - for (int j = 0; j < medias.length(); ++j) - discoverItemModels.add(makeDiscoverModel(medias.getJSONObject(j).getJSONObject("media"))); - - } else { - final boolean isOneSide = "one_by_two_left".equals(layoutType); - if (isOneSide || "two_by_two_right".equals(layoutType)) { - - final JSONObject layoutItem = layoutContent.getJSONObject(isOneSide ? "one_by_two_item" : "two_by_two_item"); - if (layoutItem.has("media")) - discoverItemModels.add(makeDiscoverModel(layoutItem.getJSONObject("media"))); - - if (layoutContent.has("fill_items")) { - final JSONArray fillItems = layoutContent.getJSONArray("fill_items"); - for (int j = 0; j < fillItems.length(); ++j) - discoverItemModels.add(makeDiscoverModel(fillItems.getJSONObject(j).getJSONObject("media"))); - } - } - } - } - } - - discoverItemModels.trimToSize(); - urlConnection.disconnect(); - - // hack to fetch 50+ items - if (this.isFirst) { - final int size = discoverItemModels.size(); - if (size > 50) this.isFirst = false; - discoverItemModels = fetchItems(discoverItemModels, "&max_id=" + (lastId++)); - } - } else { - urlConnection.disconnect(); - } - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_DISCOVER_FETCHER, "fetchItems", - new Pair<>("maxId", maxId), - new Pair<>("lastId", lastId), - new Pair<>("isFirst", isFirst), - new Pair<>("nextMaxId", nextMaxId)); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - - return discoverItemModels; - } - - @NonNull - private DiscoverItemModel makeDiscoverModel(@NonNull final JSONObject media) throws Exception { - final JSONObject user = media.getJSONObject(Constants.EXTRAS_USER); - final String username = user.getString(Constants.EXTRAS_USERNAME); - // final ProfileModel userProfileModel = new ProfileModel(user.getBoolean("is_private"), - // user.getBoolean("is_verified"), - // String.valueOf(user.get("pk")), - // username, - // user.getString("full_name"), - // null, - // user.getString("profile_pic_url"), null, - // 0, 0, 0); - - // final String comment; - // if (!media.has("caption")) comment = null; - // else { - // final Object caption = media.get("caption"); - // comment = caption instanceof JSONObject ? ((JSONObject) caption).getString("text") : null; - // } - - final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(media.getInt("media_type")); - - final ResponseBodyUtils.ThumbnailDetails thumbnailUrl = ResponseBodyUtils.getThumbnailUrl(media, mediaType); - final DiscoverItemModel model = new DiscoverItemModel(mediaType, - media.getString("pk"), - media.getString("code"), - thumbnailUrl != null ? thumbnailUrl.url : null); - - final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); - - // to check if file exists - File customDir = null; - if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) { - final String customPath = settingsHelper.getString(FOLDER_PATH); - if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) - ? "/" + username - : "")); - } - - DownloadUtils.checkExistence(downloadDir, customDir, mediaType == MediaItemType.MEDIA_TYPE_SLIDER, model); - - return model; - } - - @Override - protected void onPreExecute() { - if (fetchListener != null) fetchListener.doBefore(); - } - - @Override - protected void onPostExecute(final DiscoverItemModel[] discoverItemModels) { - if (fetchListener != null) fetchListener.onResult(discoverItemModels); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java deleted file mode 100755 index 4ae5ad35..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java +++ /dev/null @@ -1,270 +0,0 @@ -package awais.instagrabber.asyncs; - -import android.os.AsyncTask; -import android.util.Log; - -import androidx.annotation.NonNull; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.FeedModel; -import awais.instagrabber.models.PostChild; -import awais.instagrabber.models.ProfileModel; -import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.NetworkUtils; -import awais.instagrabber.utils.ResponseBodyUtils; -import awais.instagrabber.utils.TextUtils; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.Utils.logCollector; - -public final class FeedFetcher extends AsyncTask> { - private static final String TAG = "FeedFetcher"; - - private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts - private final String endCursor; - private final FetchListener> fetchListener; - - public FeedFetcher(final FetchListener> fetchListener) { - this.endCursor = ""; - this.fetchListener = fetchListener; - } - - public FeedFetcher(final String endCursor, final FetchListener> fetchListener) { - this.endCursor = endCursor == null ? "" : endCursor; - this.fetchListener = fetchListener; - } - - @Override - protected final List doInBackground(final Void... voids) { - final List result = new ArrayList<>(); - HttpURLConnection urlConnection = null; - try { - // - // stories: 04334405dbdef91f2c4e207b84c204d7 && https://i.instagram.com/api/v1/feed/reels_tray/ - // https://www.instagram.com/graphql/query/?query_hash=04334405dbdef91f2c4e207b84c204d7&variables={"only_stories":true,"stories_prefetch":false,"stories_video_dash_manifest":false} - // /////////////////////////////////////////////// - // feed: - // https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables= - // {"cached_feed_item_ids":[],"fetch_media_item_count":12,"fetch_media_item_cursor":"","fetch_comment_count":4,"fetch_like":3,"has_stories":false,"has_threaded_comments":true} - // only used: fetch_media_item_cursor, fetch_media_item_count: 100 (max 50), has_threaded_comments = true - // ////////////////////////////////////////////// - // more unknowns: https://github.com/qsniyg/rssit/blob/master/rssit/generators/instagram.py - // - - final String url = "https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=" + - "{\"fetch_media_item_count\":" + maxItemsToLoad + ",\"has_threaded_comments\":true,\"fetch_media_item_cursor\":\"" + endCursor + "\"}"; - urlConnection = (HttpURLConnection) new URL(url).openConnection(); - - if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { - final String json = NetworkUtils.readFromConnection(urlConnection); - // Log.d(TAG, json); - final JSONObject timelineFeed = new JSONObject(json).getJSONObject("data") - .getJSONObject(Constants.EXTRAS_USER) - .getJSONObject("edge_web_feed_timeline"); - - final String endCursor; - final boolean hasNextPage; - - final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); - if (pageInfo.has("has_next_page")) { - hasNextPage = pageInfo.getBoolean("has_next_page"); - endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; - } else { - hasNextPage = false; - endCursor = null; - } - - final JSONArray feedItems = timelineFeed.getJSONArray("edges"); - - for (int i = 0; i < feedItems.length(); ++i) { - final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node"); - final String mediaType = feedItem.optString("__typename"); - if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) - continue; - - final boolean isVideo = feedItem.optBoolean("is_video"); - final long videoViews = feedItem.optLong("video_view_count", 0); - - final String displayUrl = feedItem.optString("display_url"); - if (TextUtils.isEmpty(displayUrl)) continue; - final String resourceUrl; - - if (isVideo) { - resourceUrl = feedItem.getString("video_url"); - } else { - resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl; - } - - ProfileModel profileModel = null; - if (feedItem.has("owner")) { - final JSONObject owner = feedItem.getJSONObject("owner"); - profileModel = new ProfileModel( - owner.optBoolean("is_private"), - false, // if you can see it then you def follow - owner.optBoolean("is_verified"), - owner.getString(Constants.EXTRAS_ID), - owner.getString(Constants.EXTRAS_USERNAME), - owner.optString("full_name"), - null, - null, - owner.getString("profile_pic_url"), - null, - 0, - 0, - 0, - false, - false, - false, - false); - } - JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); - final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; - tempJsonObject = feedItem.optJSONObject("edge_media_to_caption"); - final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; - String captionText = null; - if (captions != null && captions.length() > 0) { - if ((tempJsonObject = captions.optJSONObject(0)) != null && - (tempJsonObject = tempJsonObject.optJSONObject("node")) != null) { - captionText = tempJsonObject.getString("text"); - } - } - final JSONObject location = feedItem.optJSONObject("location"); - // Log.d(TAG, "location: " + (location == null ? null : location.toString())); - String locationId = null; - String locationName = null; - if (location != null) { - locationName = location.optString("name"); - if (location.has("id")) { - locationId = location.getString("id"); - } else if (location.has("pk")) { - locationId = location.getString("pk"); - } - // Log.d(TAG, "locationId: " + locationId); - } - int height = 0; - int width = 0; - final JSONObject dimensions = feedItem.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = feedItem.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final FeedModel.Builder feedModelBuilder = new FeedModel.Builder() - .setProfileModel(profileModel) - .setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setViewCount(videoViews) - .setPostId(feedItem.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(resourceUrl) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl) - .setShortCode(feedItem.getString(Constants.EXTRAS_SHORTCODE)) - .setPostCaption(captionText) - .setCommentsCount(commentsCount) - .setTimestamp(feedItem.optLong("taken_at_timestamp", -1)) - .setLiked(feedItem.getBoolean("viewer_has_liked")) - .setBookmarked(feedItem.getBoolean("viewer_has_saved")) - .setLikesCount(feedItem.getJSONObject("edge_media_preview_like") - .getLong("count")) - .setLocationName(locationName) - .setLocationId(locationId) - .setImageHeight(height) - .setImageWidth(width); - - final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children"); - - if (isSlider) { - feedModelBuilder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER); - final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children"); - if (sidecar != null) { - final JSONArray children = sidecar.optJSONArray("edges"); - if (children != null) { - final List sliderItems = getSliderItems(children); - feedModelBuilder.setSliderItems(sliderItems); - } - } - } - final FeedModel feedModel = feedModelBuilder.build(); - result.add(feedModel); - } - if (!result.isEmpty() && result.get(result.size() - 1) != null) { - result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor); - } - } - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_FEED_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) { - Log.e(TAG, "", e); - } - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - - return result; - } - - @NonNull - private List getSliderItems(final JSONArray children) throws JSONException { - final List sliderItems = new ArrayList<>(); - for (int j = 0; j < children.length(); ++j) { - final JSONObject childNode = children.optJSONObject(j).getJSONObject("node"); - final boolean isChildVideo = childNode.optBoolean("is_video"); - int height = 0; - int width = 0; - final JSONObject dimensions = childNode.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = childNode.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final PostChild sliderItem = new PostChild.Builder() - .setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setPostId(childNode.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(isChildVideo ? childNode.getString("video_url") - : childNode.getString("display_url")) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl - : childNode.getString("display_url")) - .setVideoViews(childNode.optLong("video_view_count", -1)) - .setHeight(height) - .setWidth(width) - .build(); - // Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem); - sliderItems.add(sliderItem); - } - return sliderItems; - } - - @Override - protected void onPreExecute() { - if (fetchListener != null) fetchListener.doBefore(); - } - - @Override - protected void onPostExecute(final List postModels) { - if (fetchListener != null) fetchListener.onResult(postModels); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java deleted file mode 100755 index ad0d8710..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java +++ /dev/null @@ -1,182 +0,0 @@ -package awais.instagrabber.asyncs; - -import android.os.AsyncTask; -import android.os.Environment; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.File; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.models.enums.PostItemType; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.NetworkUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; -import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; -import static awais.instagrabber.utils.Utils.logCollector; - -public final class PostsFetcher extends AsyncTask> { - private static final String TAG = "PostsFetcher"; - private final PostItemType type; - private final String endCursor; - private final String id; - private final FetchListener> fetchListener; - private String username = null; - - public PostsFetcher(final String id, - final PostItemType type, - final String endCursor, - final FetchListener> fetchListener) { - this.id = id; - this.type = type; - this.endCursor = endCursor == null ? "" : endCursor; - this.fetchListener = fetchListener; - } - - public PostsFetcher setUsername(final String username) { - this.username = username; - return this; - } - - @Override - protected List doInBackground(final Void... voids) { - // final boolean isHashTag = id.charAt(0) == '#'; - // final boolean isSaved = id.charAt(0) == '$'; - // final boolean isTagged = id.charAt(0) == '%'; - // final boolean isLocation = id.contains("/"); - - final String url; - switch (type) { - case HASHTAG: - url = "https://www.instagram.com/graphql/query/?query_hash=9b498c08113f1e09617a1703c22b2f32&variables=" + - "{\"tag_name\":\"" + id.toLowerCase() + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; - break; - case LOCATION: - url = "https://www.instagram.com/graphql/query/?query_hash=36bd0f2bf5911908de389b8ceaa3be6d&variables=" + - "{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; - break; - case SAVED: - url = "https://www.instagram.com/graphql/query/?query_hash=8c86fed24fa03a8a2eea2a70a80c7b6b&variables=" + - "{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; - break; - case TAGGED: - url = "https://www.instagram.com/graphql/query/?query_hash=31fe64d9463cbbe58319dced405c6206&variables=" + - "{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; - break; - default: - url = "https://www.instagram.com/graphql/query/?query_hash=18a7b935ab438c4514b1f742d8fa07a7&variables=" + - "{\"id\":\"" + id + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; - } - List result = new ArrayList<>(); - try { - final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); - conn.setUseCaches(false); - conn.connect(); - - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - // to check if file exists - final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); - File customDir = null; - if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { - final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) - ? ("/" + username) - : "")); - if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath); - } - - final boolean isHashtag = type == PostItemType.HASHTAG; - final boolean isLocation = type == PostItemType.LOCATION; - final boolean isSaved = type == PostItemType.SAVED; - final boolean isTagged = type == PostItemType.TAGGED; - final JSONObject mediaPosts = new JSONObject(NetworkUtils.readFromConnection(conn)) - .getJSONObject("data") - .getJSONObject(isHashtag - ? Constants.EXTRAS_HASHTAG - : (isLocation ? Constants.EXTRAS_LOCATION - : Constants.EXTRAS_USER)) - .getJSONObject(isHashtag ? "edge_hashtag_to_media" : - isLocation ? "edge_location_to_media" : isSaved ? "edge_saved_media" - : isTagged ? "edge_user_to_photos_of_you" - : "edge_owner_to_timeline_media"); - - final String endCursor; - final boolean hasNextPage; - - final JSONObject pageInfo = mediaPosts.getJSONObject("page_info"); - if (pageInfo.has("has_next_page")) { - hasNextPage = pageInfo.getBoolean("has_next_page"); - endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; - } else { - hasNextPage = false; - endCursor = null; - } - - final JSONArray edges = mediaPosts.getJSONArray("edges"); - for (int i = 0; i < edges.length(); ++i) { - final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); - final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); - - final boolean isSlider = mediaNode.has("__typename") && mediaNode.getString("__typename").equals("GraphSidecar"); - final boolean isVideo = mediaNode.getBoolean("is_video"); - - final MediaItemType itemType; - if (isSlider) itemType = MediaItemType.MEDIA_TYPE_SLIDER; - else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; - else itemType = MediaItemType.MEDIA_TYPE_IMAGE; - - final PostModel model = new PostModel( - itemType, - mediaNode.getString(Constants.EXTRAS_ID), - mediaNode.getString("display_url"), - mediaNode.getString("thumbnail_src"), - mediaNode.getString(Constants.EXTRAS_SHORTCODE), - captions.length() > 0 ? captions.getJSONObject(0) - .getJSONObject("node") - .getString("text") - : null, - mediaNode.getLong("taken_at_timestamp"), - mediaNode.optBoolean("viewer_has_liked"), - mediaNode.optBoolean("viewer_has_saved") - // , mediaNode.isNull("edge_liked_by") ? 0 : mediaNode.getJSONObject("edge_liked_by").getLong("count") - ); - result.add(model); - DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model); - } - - if (!result.isEmpty() && result.get(result.size() - 1) != null) - result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor); - } - conn.disconnect(); - } catch (Exception e) { - if (logCollector != null) { - logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); - } - if (BuildConfig.DEBUG) { - Log.e(TAG, "Error fetching posts", e); - } - } - return result; - } - - @Override - protected void onPostExecute(final List postModels) { - if (fetchListener != null) fetchListener.onResult(postModels); - } -} diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java index 113914ab..a3e909ce 100644 --- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java +++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java @@ -45,6 +45,7 @@ public class PostsRecyclerView extends RecyclerView { private RecyclerLazyLoaderAtBottom lazyLoader; private FeedAdapterV2.FeedItemCallback feedItemCallback; private boolean shouldScrollToTop; + private FeedAdapterV2.SelectionModeCallback selectionModeCallback; private final List fetchStatusChangeListeners = new ArrayList<>(); @@ -112,6 +113,11 @@ public class PostsRecyclerView extends RecyclerView { return this; } + public PostsRecyclerView setSelectionModeCallback(@NonNull final FeedAdapterV2.SelectionModeCallback selectionModeCallback) { + this.selectionModeCallback = selectionModeCallback; + return this; + } + public PostsRecyclerView setLayoutPreferences(final PostsLayoutPreferences layoutPreferences) { this.layoutPreferences = layoutPreferences; if (initCalled) { @@ -154,7 +160,7 @@ public class PostsRecyclerView extends RecyclerView { } private void initAdapter() { - feedAdapter = new FeedAdapterV2(layoutPreferences, feedItemCallback); + feedAdapter = new FeedAdapterV2(layoutPreferences, feedItemCallback, selectionModeCallback); feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); setAdapter(feedAdapter); } @@ -241,6 +247,10 @@ public class PostsRecyclerView extends RecyclerView { return layoutPreferences; } + public void endSelection() { + feedAdapter.endSelection(); + } + public interface FetchStatusChangeListener { void onFetchStatusChange(boolean fetching); } diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 1e81025d..05015e3a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -1,6 +1,7 @@ package awais.instagrabber.fragments; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; @@ -19,6 +20,7 @@ import android.view.ViewGroup; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -31,9 +33,11 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; +import com.google.common.collect.ImmutableList; import java.util.Date; import java.util.List; +import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; @@ -68,6 +72,7 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "HashTagFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; public static final String ARG_HASHTAG = "hashtag"; @@ -84,14 +89,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe private boolean isLoggedIn; private TagsService tagsService; private boolean storiesFetching; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { - setEnabled(false); - remove(); - // if (postsAdapter == null) return; - // postsAdapter.clearSelection(); + binding.posts.endSelection(); } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( @@ -99,55 +103,26 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe new PrimaryActionModeCallback.CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { - onBackPressedCallback.handleOnBackPressed(); + binding.posts.endSelection(); } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { - // if (postsAdapter == null || hashtag == null) { - // return false; - // } - // final Context context = getContext(); - // if (context == null) return false; - // DownloadUtils.batchDownload(context, - // hashtag, - // DownloadMethod.DOWNLOAD_MAIN, - // postsAdapter.getSelectedModels()); - // checkAndResetAction(); + if (HashTagFragment.this.selectedFeedModels == null) return false; + final Context context = getContext(); + if (context == null) return false; + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(HashTagFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); return true; } return false; } }); - // private final FetchListener> postsFetchListener = new FetchListener>() { - // @Override - // public void onResult(final List result) { - // binding.swipeRefreshLayout.setRefreshing(false); - // if (result == null) return; - // binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); - // final List postModels = postsViewModel.getList().getValue(); - // List finalList = postModels == null || postModels.isEmpty() - // ? new ArrayList<>() - // : new ArrayList<>(postModels); - // if (isPullToRefresh) { - // finalList = result; - // isPullToRefresh = false; - // } else { - // finalList.addAll(result); - // } - // finalList.addAll(result); - // postsViewModel.getList().postValue(finalList); - // PostModel model = null; - // if (!result.isEmpty()) { - // model = result.get(result.size() - 1); - // } - // if (model == null) return; - // endCursor = model.getEndCursor(); - // hasNextPage = model.hasNextPage(); - // model.setPageCursor(false, null); - // } - // }; private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { @Override public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) { @@ -177,6 +152,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe showDownloadDialog(feedModel); return; } + downloadFeedModel = feedModel; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @@ -233,6 +209,41 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe fragment.show(getChildFragmentManager(), "post_view"); } }; + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + HashTagFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -289,6 +300,24 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe return super.onOptionsItemSelected(item); } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + downloadFeedModel = null; + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.posts.endSelection(); + } + } + private void init() { if (getArguments() == null) return; final String cookie = settingsHelper.getString(Constants.COOKIE); @@ -324,63 +353,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_HASHTAG_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); - // postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); - // final Context context = getContext(); - // if (context == null) return; - // final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); - // binding.mainPosts.setLayoutManager(layoutManager); - // binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); - // postsAdapter = new PostsAdapter((postModel, position) -> { - // if (postsAdapter.isSelecting()) { - // if (actionMode == null) return; - // final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size()); - // actionMode.setTitle(title); - // return; - // } - // if (checkAndResetAction()) return; - // final List postModels = postsViewModel.getList().getValue(); - // if (postModels == null || postModels.size() == 0) return; - // if (postModels.get(0) == null) return; - // final String postId = postModels.get(0).getPostId(); - // final boolean isId = postId != null && isLoggedIn; - // final String[] idsOrShortCodes = new String[postModels.size()]; - // for (int i = 0; i < postModels.size(); i++) { - // idsOrShortCodes[i] = isId ? postModels.get(i).getPostId() - // : postModels.get(i).getShortCode(); - // } - // final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment( - // position, - // idsOrShortCodes, - // isId); - // NavHostFragment.findNavController(this).navigate(action); - // - // }, (model, position) -> { - // if (!postsAdapter.isSelecting()) { - // checkAndResetAction(); - // return true; - // } - // if (onBackPressedCallback.isEnabled()) { - // return true; - // } - // final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); - // onBackPressedCallback.setEnabled(true); - // actionMode = fragmentActivity.startActionMode(multiSelectAction); - // final String title = getString(R.string.number_selected, 1); - // actionMode.setTitle(title); - // onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); - // return true; - // }); - // postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList); - // binding.mainPosts.setAdapter(postsAdapter); - // final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { - // if (!hasNextPage || getContext() == null) return; - // binding.swipeRefreshLayout.setRefreshing(true); - // fetchPosts(); - // endCursor = null; - // }); - // binding.mainPosts.addOnScrollListener(lazyLoader); } private void setHashtagDetails() { @@ -603,21 +578,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe navController.navigate(R.id.action_global_profileFragment, bundle); } - private boolean checkAndResetAction() { - if (!onBackPressedCallback.isEnabled() && actionMode == null) { - return false; - } - if (onBackPressedCallback.isEnabled()) { - onBackPressedCallback.setEnabled(false); - onBackPressedCallback.remove(); - } - if (actionMode != null) { - actionMode.finish(); - actionMode = null; - } - return true; - } - private void showPostsLayoutPreferences() { final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment( Constants.PREF_HASHTAG_POSTS_LAYOUT, diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index a6a18370..f0ac0554 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -2,6 +2,7 @@ package awais.instagrabber.fragments; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; @@ -22,6 +23,7 @@ import android.widget.TextView; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -34,9 +36,11 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; +import com.google.common.collect.ImmutableList; import java.util.Date; import java.util.List; +import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; @@ -70,6 +74,7 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private MainActivity fragmentActivity; private FragmentLocationBinding binding; @@ -83,72 +88,39 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR private AsyncTask currentlyExecuting; private boolean isLoggedIn; private boolean storiesFetching; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { - // if (postsAdapter == null) { - // setEnabled(false); - // remove(); - // return; - // } - // postsAdapter.clearSelection(); - setEnabled(false); - remove(); + binding.posts.endSelection(); } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { - onBackPressedCallback.handleOnBackPressed(); + binding.posts.endSelection(); } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { - // if (postsAdapter == null || locationId == null) { - // return false; - // } - // final Context context = getContext(); - // if (context == null) return false; - // DownloadUtils.batchDownload(context, - // locationId, - // DownloadMethod.DOWNLOAD_MAIN, - // postsAdapter.getSelectedModels()); - // checkAndResetAction(); - return true; + if (LocationFragment.this.selectedFeedModels == null) return false; + final Context context = getContext(); + if (context == null) return false; + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(LocationFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } }); - // private final FetchListener> postsFetchListener = new FetchListener>() { - // @Override - // public void onResult(final List result) { - // binding.swipeRefreshLayout.setRefreshing(false); - // if (result == null) return; - // binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); - // final List postModels = postsViewModel.getList().getValue(); - // List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() - // : new ArrayList<>(postModels); - // if (isPullToRefresh) { - // finalList = result; - // isPullToRefresh = false; - // } else { - // finalList.addAll(result); - // } - // postsViewModel.getList().postValue(finalList); - // PostModel model = null; - // if (!result.isEmpty()) { - // model = result.get(result.size() - 1); - // } - // if (model == null) return; - // endCursor = model.getEndCursor(); - // hasNextPage = model.hasNextPage(); - // model.setPageCursor(false, null); - // } - // }; private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { @Override public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) { @@ -234,6 +206,41 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR fragment.show(getChildFragmentManager(), "post_view"); } }; + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + LocationFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -291,13 +298,23 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR return super.onOptionsItemSelected(item); } - // @Override - // public void onDestroy() { - // super.onDestroy(); - // if (postsViewModel != null) { - // postsViewModel.getList().postValue(Collections.emptyList()); - // } - // } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + downloadFeedModel = null; + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.posts.endSelection(); + } + } private void init() { if (getArguments() == null) return; @@ -318,63 +335,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); - // postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); - // final Context context = getContext(); - // if (context == null) return; - // final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); - // binding.mainPosts.setLayoutManager(layoutManager); - // binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); - // postsAdapter = new PostsAdapter((postModel, position) -> { - // if (postsAdapter.isSelecting()) { - // if (actionMode == null) return; - // final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size()); - // actionMode.setTitle(title); - // return; - // } - // if (checkAndResetAction()) return; - // final List postModels = postsViewModel.getList().getValue(); - // if (postModels == null || postModels.size() == 0) return; - // if (postModels.get(0) == null) return; - // final String postId = postModels.get(0).getPostId(); - // final boolean isId = postId != null && isLoggedIn; - // final String[] idsOrShortCodes = new String[postModels.size()]; - // for (int i = 0; i < postModels.size(); i++) { - // idsOrShortCodes[i] = isId ? postModels.get(i).getPostId() - // : postModels.get(i).getShortCode(); - // } - // final NavDirections action = LocationFragmentDirections.actionGlobalPostViewFragment( - // position, - // idsOrShortCodes, - // isId); - // NavHostFragment.findNavController(this).navigate(action); - // }, (model, position) -> { - // if (!postsAdapter.isSelecting()) { - // checkAndResetAction(); - // return true; - // } - // if (onBackPressedCallback.isEnabled()) { - // return true; - // } - // final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity - // .getOnBackPressedDispatcher(); - // onBackPressedCallback.setEnabled(true); - // actionMode = fragmentActivity.startActionMode(multiSelectAction); - // final String title = getString(R.string.number_selected, 1); - // actionMode.setTitle(title); - // onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); - // return true; - // }); - // postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList); - // binding.mainPosts.setAdapter(postsAdapter); - // final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { - // if (!hasNextPage) return; - // binding.swipeRefreshLayout.setRefreshing(true); - // fetchPosts(); - // endCursor = null; - // }); - // binding.mainPosts.addOnScrollListener(lazyLoader); } private void fetchLocationModel() { @@ -519,12 +482,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } } - // private void fetchPosts() { - // stopCurrentExecutor(); - // currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener) - // .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - // } - private void stopCurrentExecutor() { if (currentlyExecuting != null) { try { @@ -597,21 +554,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR navController.navigate(R.id.action_global_profileFragment, bundle); } - private boolean checkAndResetAction() { - if (!onBackPressedCallback.isEnabled() && actionMode == null) { - return false; - } - if (onBackPressedCallback.isEnabled()) { - onBackPressedCallback.setEnabled(false); - onBackPressedCallback.remove(); - } - if (actionMode != null) { - actionMode.finish(); - actionMode = null; - } - return true; - } - private void showPostsLayoutPreferences() { final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment( Constants.PREF_LOCATION_POSTS_LAYOUT, diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java index 112e0fd4..f446cacb 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java @@ -13,6 +13,7 @@ import android.view.View; import android.view.ViewGroup; import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -24,7 +25,9 @@ import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import java.util.ArrayList; +import com.google.common.collect.ImmutableList; + +import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.adapters.FeedAdapterV2; @@ -34,7 +37,6 @@ import awais.instagrabber.databinding.FragmentSavedBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.fragments.main.ProfileFragmentDirections; import awais.instagrabber.models.FeedModel; -import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; @@ -47,6 +49,7 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private FragmentSavedBinding binding; private String username; @@ -56,15 +59,13 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL private boolean shouldRefresh = true; private PostItemType type; private String profileId; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; - private final ArrayList selectedItems = new ArrayList<>(); private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { - setEnabled(false); - remove(); - // if (postsAdapter == null) return; - // postsAdapter.clearSelection(); + binding.posts.endSelection(); } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( @@ -72,23 +73,21 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL new PrimaryActionModeCallback.CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { - onBackPressedCallback.handleOnBackPressed(); + binding.posts.endSelection(); } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { - // if (postsAdapter == null || username == null) { - // return false; - // } - // final Context context = getContext(); - // if (context == null) return false; - // DownloadUtils.batchDownload(context, - // username, - // DownloadMethod.DOWNLOAD_SAVED, - // postsAdapter.getSelectedModels()); - // checkAndResetAction(); - return true; + if (SavedViewerFragment.this.selectedFeedModels == null) return false; + final Context context = getContext(); + if (context == null) return false; + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); } return false; } @@ -178,6 +177,41 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL fragment.show(getChildFragmentManager(), "post_view"); } }; + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + SavedViewerFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -286,6 +320,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(getPostsLayoutPreferenceKey()))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); } @@ -306,10 +341,18 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // final Context context = getContext(); - // if (context == null) return; - // DownloadUtils.batchDownload(context, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + downloadFeedModel = null; + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.posts.endSelection(); } } @@ -392,19 +435,4 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200)); fragment.show(getChildFragmentManager(), "posts_layout_preferences"); } - - private boolean checkAndResetAction() { - if (!onBackPressedCallback.isEnabled() && actionMode == null) { - return false; - } - if (onBackPressedCallback.isEnabled()) { - onBackPressedCallback.setEnabled(false); - onBackPressedCallback.remove(); - } - if (actionMode != null) { - actionMode.finish(); - actionMode = null; - } - return true; - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java index c7130647..92af3d4d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java @@ -2,6 +2,7 @@ package awais.instagrabber.fragments; import android.animation.ArgbEvaluator; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Animatable; @@ -9,6 +10,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Handler; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -16,6 +18,8 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.PermissionChecker; @@ -33,11 +37,15 @@ import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.controller.BaseControllerListener; import com.facebook.drawee.interfaces.DraweeController; import com.facebook.imagepipeline.image.ImageInfo; +import com.google.common.collect.ImmutableList; + +import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.asyncs.DiscoverPostFetchService; +import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; import awais.instagrabber.databinding.FragmentTopicPostsBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; @@ -56,12 +64,47 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; + private MainActivity fragmentActivity; private FragmentTopicPostsBinding binding; private NestedCoordinatorLayout root; private boolean shouldRefresh = true; private TopicCluster topicCluster; + private ActionMode actionMode; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + binding.posts.endSelection(); + } + }; + private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( + R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { + @Override + public void onDestroy(final ActionMode mode) { + binding.posts.endSelection(); + } + + @Override + public boolean onActionItemClicked(final ActionMode mode, + final MenuItem item) { + if (item.getItemId() == R.id.action_download) { + if (TopicPostsFragment.this.selectedFeedModels == null) return false; + final Context context = getContext(); + if (context == null) return false; + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels)); + binding.posts.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + } + return false; + } + }); private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { @Override public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) { @@ -147,6 +190,41 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O fragment.show(getChildFragmentManager(), "post_view"); } }; + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + TopicPostsFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -220,6 +298,24 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O resetToolbar(); } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + downloadFeedModel = null; + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.posts.endSelection(); + } + } + private void resetToolbar() { fragmentActivity.resetToolbar(); } @@ -303,6 +399,7 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_TOPIC_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index 055c36c1..bb2e693e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -1,9 +1,11 @@ package awais.instagrabber.fragments.main; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.util.Log; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -11,6 +13,8 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; @@ -24,13 +28,17 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.google.common.collect.ImmutableList; + import java.util.List; +import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedStoriesAdapter; import awais.instagrabber.asyncs.FeedPostFetchService; +import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentFeedBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.fragments.PostViewV2Fragment; @@ -51,8 +59,7 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "FeedFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; - // private static final double MAX_VIDEO_HEIGHT = 0.9 * Utils.displayMetrics.heightPixels; - // private static final int RESIZED_VIDEO_HEIGHT = (int) (0.8 * Utils.displayMetrics.heightPixels); + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private MainActivity fragmentActivity; private CoordinatorLayout root; @@ -61,6 +68,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre private boolean shouldRefresh = true; private FeedStoriesViewModel feedStoriesViewModel; private boolean storiesFetching; + private ActionMode actionMode; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { @Override @@ -91,6 +101,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre showDownloadDialog(feedModel); return; } + downloadFeedModel = feedModel; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @@ -147,6 +158,72 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre fragment.show(getChildFragmentManager(), "post_view"); } }; + private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { + @Override + public void handleOnBackPressed() { + binding.feedRecyclerView.endSelection(); + } + }; + private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( + R.menu.multi_select_download_menu, + new PrimaryActionModeCallback.CallbacksHelper() { + @Override + public void onDestroy(final ActionMode mode) { + binding.feedRecyclerView.endSelection(); + } + + @Override + public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { + if (item.getItemId() == R.id.action_download) { + if (FeedFragment.this.selectedFeedModels == null) return false; + final Context context = getContext(); + if (context == null) return false; + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(FeedFragment.this.selectedFeedModels)); + binding.feedRecyclerView.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); + return true; + } + return false; + } + }); + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + FeedFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; private void navigateToProfile(final String username) { final NavController navController = NavHostFragment.findNavController(this); @@ -183,7 +260,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre setupFeedStories(); setupFeed(); shouldRefresh = false; - // showPostsLayoutPreferences(); } @Override @@ -223,6 +299,23 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre fetchStories(); } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.feedRecyclerView.endSelection(); + } + } + private void setupFeed() { binding.feedRecyclerView.setViewModelStoreOwner(this) .setLifeCycleOwner(this) @@ -230,6 +323,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.feedSwipeRefreshLayout.setRefreshing(true); // if (shouldAutoPlay) { @@ -276,7 +370,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre }); } - private void showDownloadDialog(final FeedModel feedModel) { + private void showDownloadDialog(@NonNull final FeedModel feedModel) { final Context context = getContext(); if (context == null) return; DownloadUtils.download(context, feedModel); 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 32486499..87e917e6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -2,6 +2,7 @@ package awais.instagrabber.fragments.main; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.PackageManager; import android.graphics.Typeface; import android.graphics.drawable.Animatable; import android.os.AsyncTask; @@ -24,6 +25,7 @@ import android.widget.TextView; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -46,17 +48,18 @@ import com.facebook.drawee.controller.ControllerListener; import com.facebook.imagepipeline.image.ImageInfo; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; +import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Set; import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.HighlightsAdapter; -import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.asyncs.HighlightsFetcher; import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.ProfilePostFetchService; @@ -75,7 +78,6 @@ import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.StoryModel; -import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; @@ -98,6 +100,7 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "ProfileFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; + private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private MainActivity fragmentActivity; private CoordinatorLayout root; @@ -106,7 +109,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private String cookie; private String username; private ProfileModel profileModel; - private PostsAdapter postsAdapter; private ActionMode actionMode; private Handler usernameSettingHandler; private FriendshipService friendshipService; @@ -118,6 +120,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private MenuItem blockMenuItem; private MenuItem restrictMenuItem; private boolean highlightsFetching; + private boolean postsSetupDone = false; + private Set selectedFeedModels; + private FeedModel downloadFeedModel; private final Runnable usernameSettingRunnable = () -> { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); @@ -131,10 +136,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { - setEnabled(false); - remove(); - if (postsAdapter == null) return; - postsAdapter.clearSelection(); + binding.postsRecyclerView.endSelection(); } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( @@ -142,22 +144,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe new CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { - onBackPressedCallback.handleOnBackPressed(); + binding.postsRecyclerView.endSelection(); } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { - if (postsAdapter == null || username == null) { - return false; - } + if (ProfileFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; - DownloadUtils.batchDownload(context, - username, - DownloadMethod.DOWNLOAD_MAIN, - postsAdapter.getSelectedModels()); - checkAndResetAction(); + if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { + DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels)); + binding.postsRecyclerView.endSelection(); + return true; + } + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); return true; } return false; @@ -210,6 +211,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe showDownloadDialog(feedModel); return; } + downloadFeedModel = feedModel; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @@ -266,7 +268,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe fragment.show(getChildFragmentManager(), "post_view"); } }; - private boolean postsSetupDone = false; + private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { + + @Override + public void onSelectionStart() { + if (!onBackPressedCallback.isEnabled()) { + final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); + onBackPressedCallback.setEnabled(true); + onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); + } + if (actionMode == null) { + actionMode = fragmentActivity.startActionMode(multiSelectAction); + } + } + + @Override + public void onSelectionChange(final Set selectedFeedModels) { + final String title = getString(R.string.number_selected, selectedFeedModels.size()); + if (actionMode != null) { + actionMode.setTitle(title); + } + ProfileFragment.this.selectedFeedModels = selectedFeedModels; + } + + @Override + public void onSelectionEnd() { + if (onBackPressedCallback.isEnabled()) { + onBackPressedCallback.setEnabled(false); + onBackPressedCallback.remove(); + } + if (actionMode != null) { + actionMode.finish(); + actionMode = null; + } + } + }; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -409,18 +445,32 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onDestroy() { super.onDestroy(); - postsAdapter = null; if (usernameSettingHandler != null) { usernameSettingHandler.removeCallbacks(usernameSettingRunnable); } - // if (postsViewModel != null) { - // postsViewModel.getList().postValue(Collections.emptyList()); - // } if (highlightsViewModel != null) { highlightsViewModel.getList().postValue(Collections.emptyList()); } } + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { + if (downloadFeedModel == null) return; + showDownloadDialog(downloadFeedModel); + downloadFeedModel = null; + return; + } + if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { + final Context context = getContext(); + if (context == null) return; + DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); + binding.postsRecyclerView.endSelection(); + } + } + private void init() { if (getArguments() != null) { final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments()); @@ -836,8 +886,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(), username, profileModel.getHdProfilePic()); final FragmentTransaction ft = fragmentManager.beginTransaction(); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - .add(fragment, "profilePicDialog") - .commit(); + .add(fragment, "profilePicDialog") + .commit(); } } @@ -855,6 +905,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) + .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); postsSetupDone = true; @@ -928,21 +979,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe // .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - private boolean checkAndResetAction() { - if (!onBackPressedCallback.isEnabled() && actionMode == null) { - return false; - } - if (onBackPressedCallback.isEnabled()) { - onBackPressedCallback.setEnabled(false); - onBackPressedCallback.remove(); - } - if (actionMode != null) { - actionMode.finish(); - actionMode = null; - } - return true; - } - private void navigateToProfile(final String username) { final NavController navController = NavHostFragment.findNavController(this); final Bundle bundle = new Bundle(); diff --git a/app/src/main/java/awais/instagrabber/models/BasePostModel.java b/app/src/main/java/awais/instagrabber/models/BasePostModel.java index 8c124926..38f5f8fd 100755 --- a/app/src/main/java/awais/instagrabber/models/BasePostModel.java +++ b/app/src/main/java/awais/instagrabber/models/BasePostModel.java @@ -19,7 +19,6 @@ public abstract class BasePostModel implements Serializable, Selectable { protected boolean isSelected; protected boolean isDownloaded; protected long timestamp; - protected int position; boolean liked; boolean saved; @@ -55,10 +54,6 @@ public abstract class BasePostModel implements Serializable, Selectable { return timestamp; } - public int getPosition() { - return this.position; - } - public boolean isSelected() { return isSelected; } @@ -75,10 +70,6 @@ public abstract class BasePostModel implements Serializable, Selectable { this.postId = postId; } - public void setPosition(final int position) { - this.position = position; - } - public void setSelected(final boolean selected) { this.isSelected = selected; } diff --git a/app/src/main/java/awais/instagrabber/models/FeedModel.java b/app/src/main/java/awais/instagrabber/models/FeedModel.java index b41323fe..d9178640 100755 --- a/app/src/main/java/awais/instagrabber/models/FeedModel.java +++ b/app/src/main/java/awais/instagrabber/models/FeedModel.java @@ -9,13 +9,11 @@ public final class FeedModel extends PostModel { private final long commentsCount; private long likesCount; private final long viewCount; - private boolean captionExpanded = false; - private boolean mentionClicked = false; - private List sliderItems; - private int imageWidth; - private int imageHeight; - private String locationName; - private String locationId; + private final List sliderItems; + private final int imageWidth; + private final int imageHeight; + private final String locationName; + private final String locationId; public static class Builder { @@ -184,39 +182,10 @@ public final class FeedModel extends PostModel { return likesCount; } - public boolean isCaptionExpanded() { - return captionExpanded; - } - - public boolean isMentionClicked() { - return !mentionClicked; - } - - // public void setMentionClicked(final boolean mentionClicked) { - // this.mentionClicked = mentionClicked; - // } - // - // public void setSliderItems(final ViewerPostModel[] sliderItems) { - // this.sliderItems = sliderItems; - // setItemType(MediaItemType.MEDIA_TYPE_SLIDER); - // } - - public void toggleCaption() { - captionExpanded = !captionExpanded; - } - public int getImageWidth() { return imageWidth; } - // public void setImageWidth(final int imageWidth) { - // this.imageWidth = imageWidth; - // } - - // public void setImageHeight(final int imageHeight) { - // this.imageHeight = imageHeight; - // } - public int getImageHeight() { return imageHeight; } @@ -225,18 +194,10 @@ public final class FeedModel extends PostModel { return locationName; } - // public void setLocationName(final String locationName) { - // this.locationName = locationName; - // } - public String getLocationId() { return locationId; } - // public void setLocationId(final String locationId) { - // this.locationId = locationId; - // } - public void setLiked(final boolean liked) { this.liked = liked; } @@ -257,8 +218,6 @@ public final class FeedModel extends PostModel { ", thumbnailUrl=" + thumbnailUrl + ", commentsCount=" + commentsCount + ", viewCount=" + viewCount + - ", captionExpanded=" + captionExpanded + - ", mentionClicked=" + mentionClicked + // ", sliderItems=" + sliderItems + ", imageWidth=" + imageWidth + ", imageHeight=" + imageHeight + diff --git a/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java new file mode 100644 index 00000000..fa6b5d78 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java @@ -0,0 +1,64 @@ +package awais.instagrabber.services; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationManagerCompat; + +import java.io.File; + +public class DeleteImageIntentService extends IntentService { + private final static String TAG = "DeleteImageIntent"; + private static final int DELETE_IMAGE_SERVICE_REQUEST_CODE = 9010; + + public static final String EXTRA_IMAGE_PATH = "extra_image_path"; + public static final String EXTRA_NOTIFICATION_ID = "extra_notification_id"; + public static final String DELETE_IMAGE_SERVICE = "delete_image_service"; + + public DeleteImageIntentService() { + super(DELETE_IMAGE_SERVICE); + } + + @Override + public void onCreate() { + super.onCreate(); + startService(new Intent(this, DeleteImageIntentService.class)); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + if (intent != null && Intent.ACTION_DELETE.equals(intent.getAction()) && intent.hasExtra(EXTRA_IMAGE_PATH)) { + final String path = intent.getStringExtra(EXTRA_IMAGE_PATH); + final File file = new File(path); + boolean deleted; + if (file.exists()) { + deleted = file.delete(); + if (!deleted) { + Log.w(TAG, "onHandleIntent: file not delete!"); + } + } else { + deleted = true; + } + if (deleted) { + final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + NotificationManagerCompat.from(this).cancel(notificationId); + } + } + } + + @NonNull + public static PendingIntent pendingIntent(@NonNull final Context context, + @NonNull final String imagePath, + final int notificationId) { + final Intent intent = new Intent(context, DeleteImageIntentService.class); + intent.setAction(Intent.ACTION_DELETE); + intent.putExtra(EXTRA_IMAGE_PATH, imagePath); + intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); + return PendingIntent.getService(context, DELETE_IMAGE_SERVICE_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index b9bea336..f9c37f80 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -319,37 +319,42 @@ public final class DownloadUtils { public static void download(@NonNull final Context context, @NonNull final FeedModel feedModel, final int position) { - final File downloadDir = getDownloadDir(context, "@" + feedModel.getProfileModel().getUsername()); - if (downloadDir == null) return; - switch (feedModel.getItemType()) { - case MEDIA_TYPE_IMAGE: - case MEDIA_TYPE_VIDEO: { - final String url = feedModel.getDisplayUrl(); - final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url); - download(context, url, file.getAbsolutePath()); - break; - } - case MEDIA_TYPE_SLIDER: - final List sliderItems = feedModel.getSliderItems(); - final Map map = new HashMap<>(); - for (int i = 0; i < sliderItems.size(); i++) { - final PostChild child = sliderItems.get(i); - final String url = child.getDisplayUrl(); - final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url); - map.put(url, file.getAbsolutePath()); - } - download(context, map); - break; - default: - } - + download(context, Collections.singletonList(feedModel), position); } - private static void download(final Context context, - final String url, - final String filePath) { - if (context == null || url == null || filePath == null) return; - download(context, Collections.singletonMap(url, filePath)); + public static void download(@NonNull final Context context, + @NonNull final List feedModels) { + download(context, feedModels, -1); + } + + private static void download(@NonNull final Context context, + @NonNull final List feedModels, + final int childPositionIfSingle) { + final Map map = new HashMap<>(); + for (final FeedModel feedModel : feedModels) { + final File downloadDir = getDownloadDir(context, "@" + feedModel.getProfileModel().getUsername()); + if (downloadDir == null) return; + switch (feedModel.getItemType()) { + case MEDIA_TYPE_IMAGE: + case MEDIA_TYPE_VIDEO: { + final String url = feedModel.getDisplayUrl(); + final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url); + map.put(url, file.getAbsolutePath()); + break; + } + case MEDIA_TYPE_SLIDER: + final List sliderItems = feedModel.getSliderItems(); + for (int i = 0; i < sliderItems.size(); i++) { + final PostChild child = sliderItems.get(i); + final String url = child.getDisplayUrl(); + final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url); + map.put(url, file.getAbsolutePath()); + } + break; + default: + } + } + download(context, map); } private static void download(final Context context, final Map urlFilePathMap) { diff --git a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java index 9785a1ec..7d63e4c6 100644 --- a/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java +++ b/app/src/main/java/awais/instagrabber/workers/DownloadWorker.java @@ -35,15 +35,15 @@ import java.net.URL; import java.net.URLConnection; import java.util.Collection; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; -import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.services.DeleteImageIntentService; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -236,85 +236,85 @@ public class DownloadWorker extends Worker { private void showSummary(final Map urlToFilePathMap) { final Context context = getApplicationContext(); final Collection filePaths = urlToFilePathMap.values(); - final List notifications = filePaths - .stream() - .map(filePath -> { - final File file = new File(filePath); - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); - MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); - final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); - final ContentResolver contentResolver = context.getContentResolver(); - Bitmap bitmap = null; - if (Utils.isImage(uri, contentResolver)) { - try (final InputStream inputStream = contentResolver.openInputStream(uri)) { - bitmap = BitmapFactory.decodeStream(inputStream); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } + final List notifications = new LinkedList<>(); + final List notificationIds = new LinkedList<>(); + int count = 1; + for (final String filePath : filePaths) { + final File file = new File(filePath); + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); + MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); + final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); + final ContentResolver contentResolver = context.getContentResolver(); + Bitmap bitmap = null; + if (Utils.isImage(uri, contentResolver)) { + try (final InputStream inputStream = contentResolver.openInputStream(uri)) { + bitmap = BitmapFactory.decodeStream(inputStream); + } catch (final Exception e) { + if (logCollector != null) + logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + } + if (bitmap == null) { + final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + try { + retriever.setDataSource(context, uri); + } catch (final Exception e) { + retriever.setDataSource(file.getAbsolutePath()); } - if (bitmap == null) { - final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + bitmap = retriever.getFrameAtTime(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) try { - try { - retriever.setDataSource(context, uri); - } catch (final Exception e) { - retriever.setDataSource(file.getAbsolutePath()); - } - bitmap = retriever.getFrameAtTime(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - try { - retriever.close(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); - } + retriever.close(); } catch (final Exception e) { - if (BuildConfig.DEBUG) Log.e(TAG, "", e); if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); + logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); } - } - final String downloadComplete = context.getString(R.string.downloader_complete); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_FROM_BACKGROUND - | Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - .putExtra(Intent.EXTRA_STREAM, uri); - final PendingIntent pendingIntent = PendingIntent.getActivity( - context, - DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, - intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT - ); - final Intent deleteIntent = new Intent(getApplicationContext(), MainActivity.class) - .setAction(Constants.ACTION_SHOW_ACTIVITY) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); - final PendingIntent deleteItemIntent = PendingIntent - .getActivity(getApplicationContext(), DELETE_IMAGE_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); - final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_download) - .setContentText(null) - .setContentTitle(downloadComplete) - .setWhen(System.currentTimeMillis()) - .setOnlyAlertOnce(true) - .setAutoCancel(true) - .setGroup(NOTIF_GROUP_NAME + "_" + getId()) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - .setContentIntent(pendingIntent) - .addAction(R.drawable.ic_delete, context.getString(R.string.delete), deleteItemIntent); - if (bitmap != null) { - builder.setLargeIcon(bitmap) - .setStyle(new NotificationCompat.BigPictureStyle() - .bigPicture(bitmap) - .bigLargeIcon(null)) - .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); - } - return builder; - }) - .collect(Collectors.toList()); + } catch (final Exception e) { + if (BuildConfig.DEBUG) Log.e(TAG, "", e); + if (logCollector != null) + logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); + } + } + final String downloadComplete = context.getString(R.string.downloader_complete); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_FROM_BACKGROUND + | Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + .putExtra(Intent.EXTRA_STREAM, uri); + final PendingIntent pendingIntent = PendingIntent.getActivity( + context, + DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT + ); + final int notificationId = getNotificationId() + count; + notificationIds.add(notificationId); + count++; + final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_download) + .setContentText(null) + .setContentTitle(downloadComplete) + .setWhen(System.currentTimeMillis()) + .setOnlyAlertOnce(true) + .setAutoCancel(true) + .setGroup(NOTIF_GROUP_NAME + "_" + getId()) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + .setContentIntent(pendingIntent) + .addAction(R.drawable.ic_delete, + context.getString(R.string.delete), + DeleteImageIntentService.pendingIntent(context, filePath, notificationId)); + if (bitmap != null) { + builder.setLargeIcon(bitmap) + .setStyle(new NotificationCompat.BigPictureStyle() + .bigPicture(bitmap) + .bigLargeIcon(null)) + .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); + } + notifications.add(builder); + } Notification summaryNotification = null; if (urlToFilePathMap.size() != 1) { final String text = "Downloaded " + urlToFilePathMap.size() + " items"; @@ -327,20 +327,18 @@ public class DownloadWorker extends Worker { .setGroupSummary(true) .build(); } - int count = 1; - for (final NotificationCompat.Builder builder : notifications) { + for (int i = 0; i < notifications.size(); i++) { + final NotificationCompat.Builder builder = notifications.get(i); // only make sound and vibrate for the last notification - if (count != notifications.size()) { + if (i != notifications.size() - 1) { builder.setSound(null) .setVibrate(null); } - notificationManager.notify(getNotificationId() + count, builder.build()); - count++; + notificationManager.notify(notificationIds.get(i), builder.build()); } if (summaryNotification != null) { notificationManager.notify(getNotificationId() + count, summaryNotification); } - } public static class DownloadRequest { diff --git a/app/src/main/res/drawable/ic_baseline_check_circle_24.xml b/app/src/main/res/drawable/ic_baseline_check_circle_24.xml new file mode 100644 index 00000000..5e111ca7 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_check_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/item_feed_grid.xml b/app/src/main/res/layout/item_feed_grid.xml index 0ed1d34b..d456d916 100644 --- a/app/src/main/res/layout/item_feed_grid.xml +++ b/app/src/main/res/layout/item_feed_grid.xml @@ -92,17 +92,17 @@ android:id="@+id/selectedView" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#8A000000" + android:background="@color/black_a50" android:visibility="gone" - tools:visibility="gone"> + tools:visibility="visible"> \ No newline at end of file diff --git a/app/src/main/res/layout/item_post.xml b/app/src/main/res/layout/item_post.xml index 1df80ac9..2a420115 100755 --- a/app/src/main/res/layout/item_post.xml +++ b/app/src/main/res/layout/item_post.xml @@ -47,7 +47,7 @@ android:layout_height="match_parent" android:background="#8A000000" android:visibility="gone" - tools:visibility="gone"> + tools:visibility="visible">