mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-23 05:16:58 +00:00
Add multi selection mode to all new post view fragments. Fix Delete action in download notification.
This commit is contained in:
parent
81ce8ece94
commit
895cf15623
@ -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>
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 +
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
10
app/src/main/res/drawable/ic_baseline_check_circle_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_check_circle_24.xml
Normal 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>
|
@ -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>
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user