Add multi selection mode to all new post view fragments. Fix Delete action in download notification.

This commit is contained in:
Ammar Githam 2020-11-03 17:23:20 +09:00
parent 81ce8ece94
commit 895cf15623
24 changed files with 796 additions and 1223 deletions

View File

@ -138,6 +138,8 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<service android:name=".services.ActivityCheckerService" />
<service android:name=".services.DeleteImageIntentService" />
</application>
</manifest>

View File

@ -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<DiscoverItemModel, DiscoverViewHolder> {
private static final DiffUtil.ItemCallback<DiscoverItemModel> diffCallback = new DiffUtil.ItemCallback<DiscoverItemModel>() {
@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<DiscoverItemModel> clickListener,
final OnItemLongClickListener<DiscoverItemModel> 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());
}
}
}

View File

@ -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<FeedModel, RecyclerView.ViewHolder> {
private static final String TAG = "FeedAdapterV2";
private PostsLayoutPreferences layoutPreferences;
private final FeedItemCallback feedItemCallback;
private final SelectionModeCallback selectionModeCallback;
private final Set<Integer> selectedPositions = new HashSet<>();
private final Set<FeedModel> 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<FeedModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<FeedModel>() {
@ -48,12 +49,56 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
return oldItem.getPostId().equals(newItem.getPostId());
}
};
private final AdapterSelectionCallback adapterSelectionCallback = new AdapterSelectionCallback() {
@Override
public boolean onPostLongClick(final int position, final FeedModel feedModel) {
if (!selectionModeActive) {
selectionModeActive = true;
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionStart();
}
}
selectedPositions.add(position);
selectedFeedModels.add(feedModel);
notifyItemChanged(position);
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionChange(selectedFeedModels);
}
return true;
}
@Override
public void onPostClick(final int position, final FeedModel feedModel) {
if (!selectionModeActive) return;
if (selectedPositions.contains(position)) {
selectedPositions.remove(position);
selectedFeedModels.remove(feedModel);
} else {
selectedPositions.add(position);
selectedFeedModels.add(feedModel);
}
notifyItemChanged(position);
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionChange(selectedFeedModels);
}
if (selectedPositions.isEmpty()) {
selectionModeActive = false;
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionEnd();
}
}
}
};
public FeedAdapterV2(@NonNull final PostsLayoutPreferences layoutPreferences,
final FeedItemCallback feedItemCallback) {
final FeedItemCallback feedItemCallback,
final SelectionModeCallback selectionModeCallback) {
super(DIFF_CALLBACK);
this.layoutPreferences = layoutPreferences;
this.feedItemCallback = feedItemCallback;
this.selectionModeCallback = selectionModeCallback;
}
@NonNull
@ -97,7 +142,6 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, final int position) {
final FeedModel feedModel = getItem(position);
if (feedModel == null) return;
feedModel.setPosition(position);
switch (layoutPreferences.getType()) {
case LINEAR:
((FeedItemViewHolder) viewHolder).bind(feedModel);
@ -105,7 +149,13 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
case GRID:
case STAGGERED_GRID:
default:
((FeedGridItemViewHolder) viewHolder).bind(feedModel, layoutPreferences, feedItemCallback);
((FeedGridItemViewHolder) viewHolder).bind(position,
feedModel,
layoutPreferences,
feedItemCallback,
adapterSelectionCallback,
selectionModeActive,
selectedPositions.contains(position));
}
}
@ -118,6 +168,17 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
this.layoutPreferences = layoutPreferences;
}
public void endSelection() {
if (!selectionModeActive) return;
selectionModeActive = false;
selectedPositions.clear();
selectedFeedModels.clear();
notifyDataSetChanged();
if (selectionModeCallback != null) {
selectionModeCallback.onSelectionEnd();
}
}
// @Override
// public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
// super.onViewAttachedToWindow(holder);
@ -163,4 +224,18 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
void onSliderClick(FeedModel feedModel, int position);
}
public interface AdapterSelectionCallback {
boolean onPostLongClick(final int position, FeedModel feedModel);
void onPostClick(final int position, FeedModel feedModel);
}
public interface SelectionModeCallback {
void onSelectionStart();
void onSelectionChange(final Set<FeedModel> selectedFeedModels);
void onSelectionEnd();
}
}

View File

@ -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();

View File

@ -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);

View File

@ -25,7 +25,7 @@ public final class PostViewHolder extends RecyclerView.ViewHolder {
final OnItemClickListener<PostModel> clickListener,
final OnItemLongClickListener<PostModel> 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));

View File

@ -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<Void, Void, DiscoverItemModel[]> {
private final String cluster, maxId, rankToken;
private final FetchListener<DiscoverItemModel[]> 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<DiscoverItemModel[]> 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<DiscoverItemModel> 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<DiscoverItemModel> fetchItems(ArrayList<DiscoverItemModel> 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);
}
}

View File

@ -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<Void, Void, List<FeedModel>> {
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<List<FeedModel>> fetchListener;
public FeedFetcher(final FetchListener<List<FeedModel>> fetchListener) {
this.endCursor = "";
this.fetchListener = fetchListener;
}
public FeedFetcher(final String endCursor, final FetchListener<List<FeedModel>> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
@Override
protected final List<FeedModel> doInBackground(final Void... voids) {
final List<FeedModel> 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":"<end_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<PostChild> 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<PostChild> getSliderItems(final JSONArray children) throws JSONException {
final List<PostChild> 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<FeedModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -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<Void, Void, List<PostModel>> {
private static final String TAG = "PostsFetcher";
private final PostItemType type;
private final String endCursor;
private final String id;
private final FetchListener<List<PostModel>> fetchListener;
private String username = null;
public PostsFetcher(final String id,
final PostItemType type,
final String endCursor,
final FetchListener<List<PostModel>> 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<PostModel> 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<PostModel> 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<PostModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -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<FetchStatusChangeListener> 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);
}

View File

@ -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<FeedModel> 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<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
// @Override
// public void onResult(final List<PostModel> result) {
// binding.swipeRefreshLayout.setRefreshing(false);
// if (result == null) return;
// binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
// final List<PostModel> postModels = postsViewModel.getList().getValue();
// List<PostModel> 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<FeedModel> 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<PostModel> 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,

View File

@ -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<FeedModel> 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<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
// @Override
// public void onResult(final List<PostModel> result) {
// binding.swipeRefreshLayout.setRefreshing(false);
// if (result == null) return;
// binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
// final List<PostModel> postModels = postsViewModel.getList().getValue();
// List<PostModel> 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<FeedModel> 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<PostModel> 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,

View File

@ -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<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private final ArrayList<PostModel> 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<FeedModel> 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;
}
}

View File

@ -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<FeedModel> 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<FeedModel> 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);
}

View File

@ -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<FeedModel> 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<FeedModel> 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);

View File

@ -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<FeedModel> 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<FeedModel> 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();

View File

@ -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;
}

View File

@ -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<PostChild> sliderItems;
private int imageWidth;
private int imageHeight;
private String locationName;
private String locationId;
private final List<PostChild> 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 +

View File

@ -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);
}
}

View File

@ -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<PostChild> sliderItems = feedModel.getSliderItems();
final Map<String, String> 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<FeedModel> feedModels) {
download(context, feedModels, -1);
}
private static void download(@NonNull final Context context,
@NonNull final List<FeedModel> feedModels,
final int childPositionIfSingle) {
final Map<String, String> 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<PostChild> 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<String, String> urlFilePathMap) {

View File

@ -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<String, String> urlToFilePathMap) {
final Context context = getApplicationContext();
final Collection<String> filePaths = urlToFilePathMap.values();
final List<NotificationCompat.Builder> 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<NotificationCompat.Builder> notifications = new LinkedList<>();
final List<Integer> 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 {

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

View File

@ -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">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="end|top"
android:layout_margin="8dp"
app:srcCompat="@drawable/ic_check_24"
app:tint="@color/blue_300"
app:srcCompat="@drawable/ic_baseline_check_circle_24"
app:tint="@color/blue_400"
tools:visibility="visible" />
</FrameLayout>
</FrameLayout>

View File

@ -47,7 +47,7 @@
android:layout_height="match_parent"
android:background="#8A000000"
android:visibility="gone"
tools:visibility="gone">
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="28dp"