Browse Source
Add multi selection mode to all new post view fragments. Fix Delete action in download notification.
renovate/org.robolectric-robolectric-4.x
Add multi selection mode to all new post view fragments. Fix Delete action in download notification.
renovate/org.robolectric-robolectric-4.x
Ammar Githam
4 years ago
24 changed files with 795 additions and 1222 deletions
-
2app/src/main/AndroidManifest.xml
-
57app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
-
95app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java
-
25app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
-
202app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
-
270app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
-
182app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
-
12app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
-
186app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
-
200app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
104app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
-
97app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
-
102app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
112app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
9app/src/main/java/awais/instagrabber/models/BasePostModel.java
-
51app/src/main/java/awais/instagrabber/models/FeedModel.java
-
64app/src/main/java/awais/instagrabber/services/DeleteImageIntentService.java
-
61app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
-
162app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
-
10app/src/main/res/drawable/ic_baseline_check_circle_24.xml
-
8app/src/main/res/layout/item_feed_grid.xml
-
2app/src/main/res/layout/item_post.xml
@ -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()); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue