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