mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-31 03:25:34 +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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user