Browse Source

Tabs! Fragments! WIP!

renovate/org.robolectric-robolectric-4.x
Ammar Githam 5 years ago
parent
commit
3f6c74d671
  1. 29
      app/build.gradle
  2. 234
      app/src/main/java/awais/instagrabber/MainHelper.java
  3. 633
      app/src/main/java/awais/instagrabber/activities/MainActivity.java
  4. 607
      app/src/main/java/awais/instagrabber/activities/MainActivityBackup.java
  5. 10
      app/src/main/java/awais/instagrabber/activities/PostViewer.java
  6. 78
      app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
  7. 172
      app/src/main/java/awais/instagrabber/activities/SavedViewer.java
  8. 936
      app/src/main/java/awais/instagrabber/activities/StoryViewer.java
  9. 77
      app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
  10. 13
      app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
  11. 58
      app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
  12. 17
      app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
  13. 106
      app/src/main/java/awais/instagrabber/adapters/MultiSelectListAdapter.java
  14. 89
      app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
  15. 104
      app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
  16. 10
      app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java
  17. 37
      app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java
  18. 42
      app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
  19. 7
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java
  20. 16
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java
  21. 4
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java
  22. 7
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
  23. 81
      app/src/main/java/awais/instagrabber/asyncs/CommentAction.java
  24. 8
      app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
  25. 88
      app/src/main/java/awais/instagrabber/asyncs/QuizAction.java
  26. 87
      app/src/main/java/awais/instagrabber/asyncs/RespondAction.java
  27. 51
      app/src/main/java/awais/instagrabber/asyncs/SeenAction.java
  28. 69
      app/src/main/java/awais/instagrabber/asyncs/VoteAction.java
  29. 51
      app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java
  30. 71
      app/src/main/java/awais/instagrabber/customviews/PrimaryActionModeCallback.java
  31. 2
      app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java
  32. 33
      app/src/main/java/awais/instagrabber/customviews/helpers/CustomHideBottomViewOnScrollBehavior.java
  33. 154
      app/src/main/java/awais/instagrabber/customviews/helpers/NestedCoordinatorLayout.java
  34. 5
      app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
  35. 46
      app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
  36. 673
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
  37. 12
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
  38. 31
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
  39. 241
      app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java
  40. 345
      app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
  41. 57
      app/src/main/java/awais/instagrabber/fragments/main/ProfileActionListener.java
  42. 532
      app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
  43. 19
      app/src/main/java/awais/instagrabber/fragments/main/viewmodels/DiscoverItemViewModel.java
  44. 19
      app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedStoriesViewModel.java
  45. 20
      app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedViewModel.java
  46. 19
      app/src/main/java/awais/instagrabber/fragments/main/viewmodels/ProfilePostsViewModel.java
  47. 19
      app/src/main/java/awais/instagrabber/fragments/main/viewmodels/StoriesViewModel.java
  48. 17
      app/src/main/java/awais/instagrabber/models/BasePostModel.java
  49. 2
      app/src/main/java/awais/instagrabber/models/PostModel.java
  50. 4
      app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java
  51. 18
      app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java
  52. 24
      app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java
  53. 24
      app/src/main/java/awais/instagrabber/services/BaseService.java
  54. 4
      app/src/main/java/awais/instagrabber/services/FeedService.java
  55. 35
      app/src/main/java/awais/instagrabber/services/ProfileService.java
  56. 7
      app/src/main/java/awais/instagrabber/services/ServiceCallback.java
  57. 277
      app/src/main/java/awais/instagrabber/services/StoriesService.java
  58. 207
      app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java
  59. 24
      app/src/main/java/awais/instagrabber/utils/Utils.java
  60. BIN
      app/src/main/res/drawable-night/comments.png
  61. BIN
      app/src/main/res/drawable-night/mute.png
  62. BIN
      app/src/main/res/drawable-night/video_views.png
  63. BIN
      app/src/main/res/drawable-night/vol.png
  64. BIN
      app/src/main/res/drawable/comments.png
  65. 10
      app/src/main/res/drawable/ic_close_24.xml
  66. 10
      app/src/main/res/drawable/ic_more_horiz_24.xml
  67. 10
      app/src/main/res/drawable/ic_open_in_new_24.xml
  68. 10
      app/src/main/res/drawable/ic_outline_comments_24.xml
  69. 10
      app/src/main/res/drawable/ic_outline_views_24.xml
  70. 9
      app/src/main/res/drawable/ic_star_24.xml
  71. 10
      app/src/main/res/drawable/ic_volume_off_24.xml
  72. 10
      app/src/main/res/drawable/ic_volume_up_24.xml
  73. BIN
      app/src/main/res/drawable/mute.png
  74. BIN
      app/src/main/res/drawable/video_views.png
  75. BIN
      app/src/main/res/drawable/vol.png
  76. 107
      app/src/main/res/layout/activity_main.xml
  77. 104
      app/src/main/res/layout/activity_mainbackup.xml
  78. 41
      app/src/main/res/layout/activity_story_viewer.xml
  79. 17
      app/src/main/res/layout/fragment_direct_messages_inbox.xml
  80. 4
      app/src/main/res/layout/fragment_direct_messages_thread.xml
  81. 43
      app/src/main/res/layout/fragment_discover.xml
  82. 41
      app/src/main/res/layout/fragment_feed.xml
  83. 304
      app/src/main/res/layout/fragment_profile.xml
  84. 116
      app/src/main/res/layout/item_feed_bottom.xml
  85. 5
      app/src/main/res/layout/item_feed_photo.xml
  86. 28
      app/src/main/res/layout/item_feed_top.xml
  87. 13
      app/src/main/res/layout/item_highlight.xml
  88. 22
      app/src/main/res/layout/item_post.xml
  89. 16
      app/src/main/res/layout/item_story.xml
  90. 30
      app/src/main/res/menu/main_bottom_navigation_menu.xml
  91. 18
      app/src/main/res/menu/main_menu.xml
  92. 11
      app/src/main/res/menu/multi_select_download_menu.xml
  93. 17
      app/src/main/res/menu/story_menu.xml
  94. 10
      app/src/main/res/navigation/direct_messages_nav_graph.xml
  95. 11
      app/src/main/res/navigation/discover_nav_graph.xml
  96. 47
      app/src/main/res/navigation/feed_nav_graph.xml
  97. 12
      app/src/main/res/navigation/profile_nav_graph.xml
  98. 2
      app/src/main/res/values/dimens.xml
  99. 5
      app/src/main/res/values/strings.xml
  100. 17
      app/src/main/res/values/styles.xml

29
app/build.gradle

@ -37,21 +37,28 @@ android {
} }
dependencies { dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true }
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation('com.google.android.material:material:1.3.0-alpha02@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01') { transitive true }
def appcompat_version = "1.2.0"
def nav_version = "2.3.0" def nav_version = "2.3.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
// For loading and tinting drawables on older versions of the platform
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.0"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'com.github.chrisbanes:PhotoView:v2.0.0'
implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation 'com.facebook.fresco:fresco:2.3.0'
implementation('org.jsoup:jsoup:1.13.1') { transitive true }
implementation('com.github.bumptech.glide:glide:4.11.0') { transitive true }
implementation('com.github.chrisbanes:PhotoView:v2.0.0@aar') { transitive true }
implementation('com.google.android.exoplayer:exoplayer:2.11.1@aar') { transitive true }
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.facebook.fresco:fresco:2.3.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
} }

234
app/src/main/java/awais/instagrabber/MainHelper.java

@ -51,13 +51,11 @@ import java.util.Map;
import awais.instagrabber.activities.CommentsViewer; import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.activities.FollowViewer; import awais.instagrabber.activities.FollowViewer;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.MainActivityBackup;
import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.SavedViewer; import awais.instagrabber.activities.SavedViewer;
import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.DiscoverAdapter; import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.adapters.FeedAdapter; import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.DiscoverFetcher; import awais.instagrabber.asyncs.DiscoverFetcher;
@ -111,8 +109,14 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private static AsyncTask<?, ?, ?> currentlyExecuting; private static AsyncTask<?, ?, ?> currentlyExecuting;
private AsyncTask<Void, Void, FeedStoryModel[]> prevStoriesFetcher; private AsyncTask<Void, Void, FeedStoryModel[]> prevStoriesFetcher;
private FeedStoryModel[] stories; private FeedStoryModel[] stories;
private boolean hasNextPage = false, feedHasNextPage = false, discoverHasMore = false;
private String endCursor = null, feedEndCursor = null, discoverEndMaxId = null, topic = null, rankToken = null;
private boolean hasNextPage = false;
private boolean feedHasNextPage = false;
private boolean discoverHasMore = false;
private String endCursor = null;
private String feedEndCursor = null;
private String discoverEndMaxId = null;
private String topic = null;
private String rankToken = null;
private String[] topicIds = null; private String[] topicIds = null;
private final boolean autoloadPosts; private final boolean autoloadPosts;
@ -293,7 +297,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
@Override @Override
public void onResult(final FeedStoryModel[] result) { public void onResult(final FeedStoryModel[] result) {
feedStoriesAdapter.setData(result);
// feedStoriesAdapter.setData(result);
if (result != null && result.length > 0) { if (result != null && result.length > 0) {
mainActivity.mainBinding.feedView.feedStories.setVisibility(View.VISIBLE); mainActivity.mainBinding.feedView.feedStories.setVisibility(View.VISIBLE);
stories = result; stories = result;
@ -305,32 +309,32 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
public void onClick(final RamboTextView view, final String text, final boolean isHashtag) { public void onClick(final RamboTextView view, final String text, final boolean isHashtag) {
new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> { .setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (MainActivity.scanHack != null) MainActivity.scanHack.onResult(text);
if (MainActivityBackup.scanHack != null) MainActivityBackup.scanHack.onResult(text);
}).show(); }).show();
} }
}; };
private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof FeedStoryModel) {
final FeedStoryModel feedStoryModel = (FeedStoryModel) tag;
final int index = indexOfIntArray(stories, feedStoryModel);
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
if (result != null && result.length > 0)
mainActivity.startActivity(new Intent(mainActivity, StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, result)
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
.putExtra(Constants.FEED, stories)
.putExtra(Constants.FEED_ORDER, index)
);
else
Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
private MainActivity mainActivity;
// private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(null, new View.OnClickListener() {
// @Override
// public void onClick(final View v) {
// final Object tag = v.getTag();
// if (tag instanceof FeedStoryModel) {
// final FeedStoryModel feedStoryModel = (FeedStoryModel) tag;
// final int index = indexOfIntArray(stories, feedStoryModel);
// new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
// if (result != null && result.length > 0)
// mainActivity.startActivity(new Intent(mainActivity, StoryViewer.class)
// .putExtra(Constants.EXTRAS_STORIES, result)
// .putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
// .putExtra(Constants.FEED, stories)
// .putExtra(Constants.FEED_ORDER, index)
// );
// else
// Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
// }
// });
private MainActivityBackup mainActivity;
private Resources resources; private Resources resources;
private final View collapsingToolbar; private final View collapsingToolbar;
private final RecyclerLazyLoader lazyLoader; private final RecyclerLazyLoader lazyLoader;
@ -345,7 +349,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private RequestManager glide; private RequestManager glide;
private VideoAwareRecyclerScroller videoAwareRecyclerScroller; private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
public MainHelper(@NonNull final MainActivity mainActivity) {
public MainHelper(@NonNull final MainActivityBackup mainActivity) {
stopCurrentExecutor(); stopCurrentExecutor();
this.mainActivity = mainActivity; this.mainActivity = mainActivity;
@ -522,26 +526,27 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(mainActivity, Utils.convertDpToPx(110)); final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(mainActivity, Utils.convertDpToPx(110));
mainActivity.mainBinding.profileView.mainPosts.setLayoutManager(layoutManager); mainActivity.mainBinding.profileView.mainPosts.setLayoutManager(layoutManager);
mainActivity.mainBinding.profileView.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); mainActivity.mainBinding.profileView.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
mainActivity.mainBinding.profileView.mainPosts.setAdapter(postsAdapter = new PostsAdapter(mainActivity.allItems, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else mainActivity.startActivity(new Intent(mainActivity, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
.putExtra(Constants.EXTRAS_POST, postModel)
.putExtra(Constants.EXTRAS_USER, mainActivity.userQuery)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
}
}, v -> { // long click listener
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
return true;
}));
// mainActivity.mainBinding.profileView.mainPosts.setAdapter(postsAdapter = new PostsAdapter(/*mainActivity.allItems,*/ v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// final PostModel postModel = (PostModel) tag;
//
// if (postsAdapter.isSelecting) toggleSelection(postModel);
// else mainActivity.startActivity(new Intent(mainActivity, PostViewer.class)
// .putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
// .putExtra(Constants.EXTRAS_POST, postModel)
// .putExtra(Constants.EXTRAS_USER, mainActivity.userQuery)
// .putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
// }
// }, v -> { // long click listener
// // final Object tag = v.getTag();
// // if (tag instanceof PostModel) {
// // postsAdapter.isSelecting = true;
// // toggleSelection((PostModel) tag);
// // }
// // return true;
// return false;
// }));
this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if ((!autoloadPosts || isHashtag) && hasNextPage) { if ((!autoloadPosts || isHashtag) && hasNextPage) {
@ -643,18 +648,18 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private void setupFeed() { private void setupFeed() {
mainActivity.mainBinding.feedView.feedStories.setLayoutManager(new LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false)); mainActivity.mainBinding.feedView.feedStories.setLayoutManager(new LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false));
mainActivity.mainBinding.feedView.feedStories.setAdapter(feedStoriesAdapter);
// mainActivity.mainBinding.feedView.feedStories.setAdapter(feedStoriesAdapter);
refreshFeedStories(); refreshFeedStories();
final LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity); final LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity);
mainActivity.mainBinding.feedView.feedPosts.setHasFixedSize(true); mainActivity.mainBinding.feedView.feedPosts.setHasFixedSize(true);
mainActivity.mainBinding.feedView.feedPosts.setLayoutManager(layoutManager); mainActivity.mainBinding.feedView.feedPosts.setLayoutManager(layoutManager);
mainActivity.mainBinding.feedView.feedPosts.setAdapter(feedAdapter = new FeedAdapter(glide, clickListener, (view, text, isHashtag) ->
mainActivity.mainBinding.feedView.feedPosts.setAdapter(feedAdapter = new FeedAdapter(clickListener, (view, text, isHashtag) ->
new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> { .setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (MainActivity.scanHack != null) {
if (MainActivityBackup.scanHack != null) {
mainActivity.mainBinding.drawerLayout.closeDrawers(); mainActivity.mainBinding.drawerLayout.closeDrawers();
MainActivity.scanHack.onResult(text);
MainActivityBackup.scanHack.onResult(text);
} }
}).show())); }).show()));
@ -727,25 +732,25 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
new DiscoverFetcher(topic, null, rankToken, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new DiscoverFetcher(topic, null, rankToken, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}); });
mainActivity.mainBinding.discoverPosts.setAdapter(discoverAdapter = new DiscoverAdapter(mainActivity.discoverItems, v -> {
final Object tag = v.getTag();
if (tag instanceof DiscoverItemModel) {
final DiscoverItemModel itemModel = (DiscoverItemModel) tag;
if (discoverAdapter.isSelecting) toggleDiscoverSelection(itemModel);
else mainActivity.startActivity(new Intent(mainActivity, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, itemModel.getPosition())
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.DISCOVER_ITEMS)
.putExtra(Constants.EXTRAS_POST, new PostModel(itemModel.getShortCode(), false)));
}
}, v -> {
final Object tag = v.getTag();
if (tag instanceof DiscoverItemModel) {
discoverAdapter.isSelecting = true;
toggleDiscoverSelection((DiscoverItemModel) tag);
}
return true;
}));
// mainActivity.mainBinding.discoverPosts.setAdapter(discoverAdapter = new DiscoverAdapter(mainActivity.discoverItems, v -> {
// final Object tag = v.getTag();
// if (tag instanceof DiscoverItemModel) {
// final DiscoverItemModel itemModel = (DiscoverItemModel) tag;
//
// if (discoverAdapter.isSelecting) toggleDiscoverSelection(itemModel);
// else mainActivity.startActivity(new Intent(mainActivity, PostViewer.class)
// .putExtra(Constants.EXTRAS_INDEX, itemModel.getPosition())
// .putExtra(Constants.EXTRAS_TYPE, ItemGetType.DISCOVER_ITEMS)
// .putExtra(Constants.EXTRAS_POST, new PostModel(itemModel.getShortCode(), false)));
// }
// }, v -> {
// final Object tag = v.getTag();
// if (tag instanceof DiscoverItemModel) {
// discoverAdapter.isSelecting = true;
// toggleDiscoverSelection((DiscoverItemModel) tag);
// }
// return true;
// }));
mainActivity.mainBinding.discoverPosts.addOnScrollListener(discoverLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { mainActivity.mainBinding.discoverPosts.addOnScrollListener(discoverLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (discoverHasMore) { if (discoverHasMore) {
@ -811,7 +816,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.allItems.clear(); mainActivity.allItems.clear();
mainActivity.selectedItems.clear(); mainActivity.selectedItems.clear();
if (postsAdapter != null) { if (postsAdapter != null) {
postsAdapter.isSelecting = false;
// postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged(); postsAdapter.notifyDataSetChanged();
} }
mainActivity.mainBinding.profileView.appBarLayout.setExpanded(true, true); mainActivity.mainBinding.profileView.appBarLayout.setExpanded(true, true);
@ -1279,12 +1284,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
} }
private void notifyAdapter(final PostModel postModel) { private void notifyAdapter(final PostModel postModel) {
if (mainActivity.selectedItems.size() < 1) postsAdapter.isSelecting = false;
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (mainActivity.downloadAction != null)
mainActivity.downloadAction.setVisible(postsAdapter.isSelecting);
// if (mainActivity.selectedItems.size() < 1) postsAdapter.isSelecting = false;
// if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
// else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
//
// if (mainActivity.downloadAction != null)
// mainActivity.downloadAction.setVisible(postsAdapter.isSelecting);
} }
private void toggleDiscoverSelection(final DiscoverItemModel itemModel) { private void toggleDiscoverSelection(final DiscoverItemModel itemModel) {
@ -1297,34 +1302,35 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
} }
private void notifyDiscoverAdapter(final DiscoverItemModel itemModel) { private void notifyDiscoverAdapter(final DiscoverItemModel itemModel) {
if (mainActivity.selectedDiscoverItems.size() < 1) discoverAdapter.isSelecting = false;
if (itemModel.getPosition() < 0) discoverAdapter.notifyDataSetChanged();
else discoverAdapter.notifyItemChanged(itemModel.getPosition(), itemModel);
if (mainActivity.downloadAction != null)
mainActivity.downloadAction.setVisible(discoverAdapter.isSelecting);
// if (mainActivity.selectedDiscoverItems.size() < 1) discoverAdapter.isSelecting = false;
// if (itemModel.getPosition() < 0) discoverAdapter.notifyDataSetChanged();
// else discoverAdapter.notifyItemChanged(itemModel.getPosition(), itemModel);
//
// if (mainActivity.downloadAction != null)
// mainActivity.downloadAction.setVisible(discoverAdapter.isSelecting);
} }
public boolean isSelectionCleared() { public boolean isSelectionCleared() {
if (postsAdapter != null && postsAdapter.isSelecting) {
for (final PostModel postModel : mainActivity.selectedItems)
postModel.setSelected(false);
mainActivity.selectedItems.clear();
postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(false);
return false;
} else if (discoverAdapter != null && discoverAdapter.isSelecting) {
for (final DiscoverItemModel itemModel : mainActivity.selectedDiscoverItems)
itemModel.setSelected(false);
mainActivity.selectedDiscoverItems.clear();
discoverAdapter.isSelecting = false;
discoverAdapter.notifyDataSetChanged();
if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(false);
// if (postsAdapter != null && postsAdapter.isSelecting()) {
// for (final PostModel postModel : mainActivity.selectedItems)
// postModel.setSelected(false);
// mainActivity.selectedItems.clear();
// // postsAdapter.isSelecting = false;
// postsAdapter.notifyDataSetChanged();
// if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(false);
// return false;
// } else if (discoverAdapter != null && discoverAdapter.isSelecting) {
// for (final DiscoverItemModel itemModel : mainActivity.selectedDiscoverItems)
// itemModel.setSelected(false);
// mainActivity.selectedDiscoverItems.clear();
// discoverAdapter.isSelecting = false;
// discoverAdapter.notifyDataSetChanged();
// if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(false);
// return false;
// }
// return true;
return false; return false;
} }
return true;
}
public void deselectSelection(final BasePostModel postModel) { public void deselectSelection(final BasePostModel postModel) {
if (postModel instanceof PostModel) { if (postModel instanceof PostModel) {
@ -1364,7 +1370,8 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private final View.OnClickListener profileActionListener = new View.OnClickListener() { private final View.OnClickListener profileActionListener = new View.OnClickListener() {
@Override @Override
public void onClick(final View v) { public void onClick(final View v) {
final boolean iamme = (isLoggedIn && mainActivity.profileModel != null) && Utils.getUserIdFromCookie(cookie).equals(mainActivity.profileModel.getId());
final String userIdFromCookie = Utils.getUserIdFromCookie(MainHelper.this.cookie);
final boolean isSelf = (isLoggedIn && mainActivity.profileModel != null) && userIdFromCookie != null && userIdFromCookie.equals(mainActivity.profileModel.getId());
if (!isLoggedIn && Utils.dataBox.getFavorite(mainActivity.userQuery) != null && v == mainActivity.mainBinding.profileView.btnFollow) { if (!isLoggedIn && Utils.dataBox.getFavorite(mainActivity.userQuery) != null && v == mainActivity.mainBinding.profileView.btnFollow) {
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(mainActivity.userQuery, Utils.dataBox.delFavorite(new DataBox.FavoriteModel(mainActivity.userQuery,
Long.parseLong(Utils.dataBox.getFavorite(mainActivity.userQuery).split("/")[1]), Long.parseLong(Utils.dataBox.getFavorite(mainActivity.userQuery).split("/")[1]),
@ -1378,7 +1385,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
new ProfileAction().execute("follow"); new ProfileAction().execute("follow");
} else if (v == mainActivity.mainBinding.profileView.btnRestrict && isLoggedIn) { } else if (v == mainActivity.mainBinding.profileView.btnRestrict && isLoggedIn) {
new ProfileAction().execute("restrict"); new ProfileAction().execute("restrict");
} else if (v == mainActivity.mainBinding.profileView.btnSaved && !iamme) {
} else if (v == mainActivity.mainBinding.profileView.btnSaved && !isSelf) {
new ProfileAction().execute("block"); new ProfileAction().execute("block");
} else if (v == mainActivity.mainBinding.profileView.btnFollowTag) { } else if (v == mainActivity.mainBinding.profileView.btnFollowTag) {
new ProfileAction().execute("followtag"); new ProfileAction().execute("followtag");
@ -1407,17 +1414,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
protected Void doInBackground(String... rawAction) { protected Void doInBackground(String... rawAction) {
action = rawAction[0]; action = rawAction[0];
final String url = "https://www.instagram.com/web/" +
((action == "followtag" && mainActivity.hashtagModel != null) ? ("tags/" +
(mainActivity.hashtagModel.getFollowing() == true ? "unfollow/" : "follow/") + mainActivity.hashtagModel.getName() + "/") : (
((action == "restrict" && mainActivity.profileModel != null) ? "restrict_action" : ("friendships/" + mainActivity.profileModel.getId())) + "/" +
((action == "follow" && mainActivity.profileModel != null) ?
((mainActivity.profileModel.getFollowing() == true ||
(mainActivity.profileModel.getFollowing() == false && mainActivity.profileModel.getRequested() == true))
? "unfollow/" : "follow/") :
((action == "restrict" && mainActivity.profileModel != null) ?
(mainActivity.profileModel.getRestricted() == true ? "unrestrict/" : "restrict/") :
(mainActivity.profileModel.getBlocked() == true ? "unblock/" : "block/")))));
final String url = "https://www.instagram.com/web/" + (action.equals("followtag") && mainActivity.hashtagModel != null ? "tags/" + (mainActivity.hashtagModel.getFollowing() ? "unfollow/" : "follow/") + mainActivity.hashtagModel.getName() + "/" : (action.equals("restrict") && mainActivity.profileModel != null ? "restrict_action" : "friendships/" + mainActivity.profileModel.getId()) + "/" + (action.equals("follow") ?
mainActivity.profileModel.getFollowing() || mainActivity.profileModel.getRequested()
? "unfollow/" : "follow/" :
action.equals("restrict") ?
mainActivity.profileModel.getRestricted() ? "unrestrict/" : "restrict/" :
mainActivity.profileModel.getBlocked() ? "unblock/" : "block/"));
try { try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST"); urlConnection.setRequestMethod("POST");

633
app/src/main/java/awais/instagrabber/activities/MainActivity.java

@ -1,603 +1,132 @@
package awais.instagrabber.activities; package awais.instagrabber.activities;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.MatrixCursor;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.core.app.NotificationCompat;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.lifecycle.LiveData;
import androidx.navigation.NavController;
import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Stack;
import awais.instagrabber.MainHelper;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.GetActivityAsyncTask;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.MouseDrawer;
import awais.instagrabber.customviews.helpers.CustomHideBottomViewOnScrollBehavior;
import awais.instagrabber.databinding.ActivityMainBinding; import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.dialogs.AboutDialog;
import awais.instagrabber.dialogs.QuickAccessDialog;
import awais.instagrabber.dialogs.SettingsDialog;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.ItemGetter;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.CHANNEL_ID;
import static awais.instagrabber.utils.Utils.notificationManager;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public final class MainActivity extends BaseLanguageActivity {
private static final int INITIAL_DELAY_MILLIS = 200;
private static final int DELAY_MILLIS = 60000;
public static FetchListener<String> scanHack;
public static ItemGetter itemGetter;
public class MainActivity extends BaseLanguageActivity {
private static final String TAG = "MainActivity";
public final ArrayList<PostModel> allItems = new ArrayList<>();
public final ArrayList<FeedModel> feedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> discoverItems = new ArrayList<>();
public final ArrayList<PostModel> selectedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> selectedDiscoverItems = new ArrayList<>();
private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData;
public final HighlightsAdapter highlightsAdapter = new HighlightsAdapter(null, new View.OnClickListener() {
@Override @Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof HighlightModel) {
final HighlightModel highlightModel = (HighlightModel) tag;
new iStoryStatusFetcher(highlightModel.getId(), null, false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
if (result != null && result.length > 0)
startActivity(new Intent(MainActivity.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result)
);
else
Toast.makeText(MainActivity.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
private SuggestionsAdapter suggestionAdapter;
private MenuItem searchAction;
public ActivityMainBinding mainBinding;
public SearchView searchView;
public MenuItem downloadAction, settingsAction, dmsAction, notifAction;
public StoryModel[] storyModels;
public String userQuery = null, cookie, uid = null;
public MainHelper mainHelper;
public ProfileModel profileModel;
public HashtagModel hashtagModel;
public LocationModel locationModel;
private AutoCompleteTextView searchAutoComplete;
private ArrayAdapter<String> profileDialogAdapter;
private DialogInterface.OnClickListener profileDialogListener;
private Stack<String> queriesStack;
private DataBox.CookieModel cookieModel;
private Runnable runnable;
private Handler handler;
private boolean isLoggedIn;
@Override
protected void onCreate(@Nullable final Bundle bundle) {
super.onCreate(bundle);
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this);
cookie = settingsHelper.getString(Constants.COOKIE);
uid = Utils.getUserIdFromCookie(cookie);
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE);
Utils.setupCookies(cookie); Utils.setupCookies(cookie);
setContentView(binding.getRoot());
MainHelper.stopCurrentExecutor();
mainHelper = new MainHelper(this);
if (bundle == null) {
queriesStack = new Stack<>();
userQuery = null;
} else {
setStack(bundle);
userQuery = bundle.getString("query");
}
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
itemGetter = itemGetType -> {
if (itemGetType == ItemGetType.MAIN_ITEMS) return allItems;
if (itemGetType == ItemGetType.DISCOVER_ITEMS) return discoverItems;
if (itemGetType == ItemGetType.FEED_ITEMS) return feedItems;
return null;
};
scanHack = result -> {
if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer();
addToStack();
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@" + result);
mainHelper.onRefresh();
}
};
// searches for your userid and returns username
if (uid != null) {
final FetchListener<String> fetchListener = username -> {
if (!Utils.isEmpty(username)) {
// if (!BuildConfig.DEBUG) {
userQuery = username;
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing())
mainHelper.onRefresh();
// }
// adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid);
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername()))
Utils.dataBox.addUserCookie(new DataBox.CookieModel(uid, username, cookie));
}
};
boolean found = false;
cookieModel = Utils.dataBox.getCookie(uid);
if (cookieModel != null) {
final String username = cookieModel.getUsername();
if (username != null) {
found = true;
fetchListener.onResult("@" + username);
}
}
if (!found) // if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
suggestionAdapter = new SuggestionsAdapter(this, v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
addToStack();
userQuery = tag.toString();
mainHelper.onRefresh();
}
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
}
});
final Resources resources = getResources();
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
new String[]{resources.getString(R.string.view_pfp), resources.getString(R.string.show_stories)});
profileDialogListener = (dialog, which) -> {
final Intent intent;
if (which == 0 || storyModels == null || storyModels.length < 1) {
intent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
} else
intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(intent);
};
final View.OnClickListener onClickListener = v -> {
if (v == mainBinding.profileView.mainBiography) {
Utils.copyText(this, mainBinding.profileView.mainBiography.getText().toString());
} else if (v == mainBinding.profileView.locationBiography) {
Utils.copyText(this, mainBinding.profileView.locationBiography.getText().toString());
} else if (v == mainBinding.profileView.mainProfileImage || v == mainBinding.profileView.mainHashtagImage || v == mainBinding.profileView.mainLocationImage) {
if (storyModels == null || storyModels.length <= 0) {
profileDialogListener.onClick(null, 0);
} else {
// because sometimes configuration changes made this crash on some phones
new AlertDialog.Builder(this).setAdapter(profileDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null).show();
}
}
};
mainBinding.profileView.mainBiography.setOnClickListener(onClickListener);
mainBinding.profileView.locationBiography.setOnClickListener(onClickListener);
mainBinding.profileView.mainProfileImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainHashtagImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainLocationImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainBiography.setEnabled(false);
mainBinding.profileView.mainProfileImage.setEnabled(false);
mainBinding.profileView.mainHashtagImage.setEnabled(false);
mainBinding.profileView.mainLocationImage.setEnabled(false);
final boolean isQueryNull = userQuery == null;
if (isQueryNull) {
allItems.clear();
mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_info);
mainBinding.profileView.privatePage2.setTextSize(20);
mainBinding.profileView.privatePage2.setText(isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null)
mainHelper.onRefresh();
mainHelper.onIntent(getIntent());
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
handler = new Handler();
runnable = () -> {
final GetActivityAsyncTask activityAsyncTask = new GetActivityAsyncTask(uid, cookie, result -> {
if (result == null || notificationManager == null) {
return;
}
final List<String> list = new ArrayList<>();
if (result.getRelationshipsCount() != 0) {
list.add(getString(R.string.activity_count_relationship, result.getRelationshipsCount()));
}
if (result.getUserTagsCount() != 0) {
list.add(getString(R.string.activity_count_usertags, result.getUserTagsCount()));
}
if (result.getCommentsCount() != 0) {
list.add(getString(R.string.activity_count_comments, result.getCommentsCount()));
}
if (result.getCommentLikesCount() != 0) {
list.add(getString(R.string.activity_count_commentlikes, result.getCommentLikesCount()));
}
if (result.getLikesCount() != 0) {
list.add(getString(R.string.activity_count_likes, result.getLikesCount()));
}
if (list.isEmpty()) {
return;
}
final String join = TextUtils.join(", ", list);
final String notificationString = getString(R.string.activity_count_prefix) + " " + join + ".";
final Intent intent = new Intent(getApplicationContext(), NotificationsViewer.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
final Notification notification = new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.drawable.ic_notif)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setContentText(notificationString)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.build();
notificationManager.cancel(1800000000);
notificationManager.notify(1800000000, notification);
});
activityAsyncTask.execute();
if (!Utils.isEmpty(cookie) && Utils.settingsHelper.getBoolean(Constants.CHECK_ACTIVITY))
activityAsyncTask.execute();
handler.postDelayed(runnable, DELAY_MILLIS);
};
handler.postDelayed(runnable, INITIAL_DELAY_MILLIS);
}
private void downloadSelectedItems() {
if (selectedItems.size() > 0) {
Utils.batchDownload(this, userQuery, DownloadMethod.DOWNLOAD_MAIN, selectedItems);
} else if (selectedDiscoverItems.size() > 0) {
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_DISCOVER, selectedDiscoverItems);
}
if (savedInstanceState == null) {
setupBottomNavigationBar();
} }
@Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
mainHelper.onIntent(intent);
setupScrollingListener();
} }
@Override
public void onSaveInstanceState(@NonNull final Bundle outState, @NonNull final PersistableBundle outPersistentState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
public void onRestoreInstanceState(@Nullable final Bundle savedInstanceState, @Nullable final PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
if (savedInstanceState != null) {
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
}
}
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
private void setupScrollingListener() {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();
layoutParams.setBehavior(new CustomHideBottomViewOnScrollBehavior());
binding.bottomNavView.requestLayout();
} }
@Override @Override
public boolean onCreateOptionsMenu(final Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
final FragmentManager fragmentManager = getSupportFragmentManager();
final MenuItem quickAccessAction = menu.findItem(R.id.action_quickaccess).setVisible(true);
final MenuItem.OnMenuItemClickListener clickListener = item -> {
if (item == downloadAction)
downloadSelectedItems();
else if (item == dmsAction)
startActivity(new Intent(this, DirectMessagesActivity.class));
else if (item == notifAction)
startActivity(new Intent(this, NotificationsViewer.class));
else if (item == settingsAction)
new SettingsDialog().show(fragmentManager, "settings");
else if (item == quickAccessAction)
new QuickAccessDialog()
.setQuery(userQuery, locationModel != null ? locationModel.getName() : userQuery)
.show(fragmentManager, "quickAccess");
else
new AboutDialog().show(fragmentManager, "about");
return true;
};
quickAccessAction.setOnMenuItemClickListener(clickListener);
menu.findItem(R.id.action_about).setVisible(true).setOnMenuItemClickListener(clickListener);
dmsAction = menu.findItem(R.id.action_dms).setOnMenuItemClickListener(clickListener);
notifAction = menu.findItem(R.id.action_notif).setOnMenuItemClickListener(clickListener);
settingsAction = menu.findItem(R.id.action_settings).setVisible(true).setOnMenuItemClickListener(clickListener);
downloadAction = menu.findItem(R.id.action_download).setOnMenuItemClickListener(clickListener);
if (!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE))) {
notifAction.setVisible(true);
dmsAction.setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
searchAction = menu.findItem(R.id.action_search);
searchView = (SearchView) searchAction.getActionView();
final View searchText = searchView.findViewById(R.id.search_src_text);
if (searchText instanceof AutoCompleteTextView)
searchAutoComplete = (AutoCompleteTextView) searchText;
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> {
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@" + cookieModel.getUsername())) ? "" : userQuery, false);
menu.findItem(R.id.action_about).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_dms).setVisible(false);
menu.findItem(R.id.action_quickaccess).setVisible(false);
menu.findItem(R.id.action_notif).setVisible(false);
});
searchAction.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
menu.findItem(R.id.action_about).setVisible(true);
menu.findItem(R.id.action_settings).setVisible(true);
menu.findItem(R.id.action_dms).setVisible(!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE)));
menu.findItem(R.id.action_quickaccess).setVisible(true);
menu.findItem(R.id.action_notif).setVisible(true);
return true;
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
private boolean searchUser, searchHash;
private AsyncTask<?, ?, ?> prevSuggestionAsync;
private final String[] COLUMNS = {BaseColumns._ID, Constants.EXTRAS_USERNAME, Constants.EXTRAS_NAME,
Constants.EXTRAS_TYPE, "pfp", "verified"};
private final FetchListener<SuggestionModel[]> fetchListener = new FetchListener<SuggestionModel[]>() {
@Override
public void doBefore() {
suggestionAdapter.changeCursor(null);
}
@Override
public void onResult(final SuggestionModel[] result) {
final MatrixCursor cursor;
if (result == null) cursor = null;
else {
cursor = new MatrixCursor(COLUMNS, 0);
for (int i = 0; i < result.length; i++) {
final SuggestionModel suggestionModel = result[i];
if (suggestionModel != null) {
final SuggestionType suggestionType = suggestionModel.getSuggestionType();
final Object[] objects = {i,
(suggestionType == SuggestionType.TYPE_LOCATION) ? suggestionModel.getName() : suggestionModel.getUsername(),
(suggestionType == SuggestionType.TYPE_LOCATION) ? suggestionModel.getUsername() : suggestionModel.getName(),
suggestionType, suggestionModel.getProfilePic(), suggestionModel.isVerified()};
if (!searchHash && !searchUser) cursor.addRow(objects);
else {
final boolean isCurrHash = suggestionType == SuggestionType.TYPE_HASHTAG;
if (searchHash && isCurrHash || !searchHash && !isCurrHash)
cursor.addRow(objects);
}
}
}
}
suggestionAdapter.changeCursor(cursor);
}
};
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {
}
}
@Override
public boolean onQueryTextSubmit(final String query) {
cancelSuggestionsAsync();
menu.findItem(R.id.action_about).setVisible(true);
menu.findItem(R.id.action_settings).setVisible(true);
closeAnyOpenDrawer();
addToStack();
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
mainHelper.onRefresh();
return false;
}
@Override
public boolean onQueryTextChange(final String newText) {
cancelSuggestionsAsync();
if (!Utils.isEmpty(newText)) {
searchUser = newText.charAt(0) == '@';
searchHash = newText.charAt(0) == '#';
if (newText.length() == 1 && (searchHash || searchUser)) {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(2);
} else {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(1);
prevSuggestionAsync = new SuggestionsFetcher(fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
searchUser || searchHash ? newText.substring(1) : newText);
}
}
getMenuInflater().inflate(R.menu.main_menu, menu);
return true; return true;
} }
});
return true;
}
@Override
public void onBackPressed() {
if (closeAnyOpenDrawer()) return;
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
private void setupBottomNavigationBar() {
final List<Integer> navList = new ArrayList<>(Arrays.asList(
R.navigation.direct_messages_nav_graph,
R.navigation.feed_nav_graph,
R.navigation.profile_nav_graph,
R.navigation.discover_nav_graph
));
binding.bottomNavView.setSelectedItemId(R.id.feed_nav_graph);
final LiveData<NavController> navControllerLiveData = setupWithNavController(
binding.bottomNavView,
navList,
getSupportFragmentManager(),
R.id.main_nav_host,
getIntent(),
1);
navControllerLiveData.observe(this, this::setupNavigation);
currentNavControllerLiveData = navControllerLiveData;
}
private void setupNavigation(final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
binding.appBarLayout.setExpanded(true, true);
final int destinationId = destination.getId();
final List<Integer> showBottomView = Arrays.asList(
R.id.directMessagesInboxFragment,
R.id.feedFragment,
R.id.profileFragment,
R.id.discoverFragment);
if (showBottomView.contains(destinationId)) {
setScrollingBehaviour();
binding.bottomNavView.setVisibility(View.VISIBLE);
return; return;
} }
if (!mainHelper.isSelectionCleared()) return;
final GridLayoutManager layoutManager = (GridLayoutManager) mainBinding.profileView.mainPosts.getLayoutManager();
if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() >= layoutManager.getSpanCount()) {
mainBinding.profileView.mainPosts.smoothScrollToPosition(0);
mainBinding.profileView.appBarLayout.setExpanded(true, true);
return;
removeScrollingBehaviour();
binding.bottomNavView.setVisibility(View.GONE);
});
} }
if (queriesStack != null && queriesStack.size() > 0) {
userQuery = queriesStack.pop();
if (userQuery != null) {
mainHelper.onRefresh();
return;
}
} else {
finish();
}
private void setScrollingBehaviour() {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams();
layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
binding.mainNavHost.requestLayout();
} }
@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)
downloadSelectedItems();
private void removeScrollingBehaviour() {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams();
layoutParams.setBehavior(null);
binding.mainNavHost.requestLayout();
} }
@Override @Override
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 9629 && (resultCode == 1692 || resultCode == RESULT_CANCELED))
finish();
else if (requestCode == 6007)
Utils.showImportExportDialog(this);
// else if (requestCode == 6969 && mainHelper.currentFeedPlayer != null)
// mainHelper.currentFeedPlayer.setPlayWhenReady(true);
}
@Override
protected void onPause() {
if (mainHelper != null) mainHelper.onPause();
if (handler != null && runnable != null) {
handler.removeCallbacks(runnable);
}
super.onPause();
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
setupBottomNavigationBar();
} }
@Override @Override
protected void onResume() {
if (mainHelper != null) mainHelper.onResume();
if (handler != null && runnable != null) {
handler.postDelayed(runnable, INITIAL_DELAY_MILLIS);
}
super.onResume();
}
private void setStack(final Bundle bundle) {
final Object stack = bundle != null ? bundle.get("stack") : null;
if (stack instanceof Stack) //noinspection unchecked
queriesStack = (Stack<String>) stack;
}
public void addToStack() {
if (userQuery != null) {
if (queriesStack == null) queriesStack = new Stack<>();
queriesStack.add(userQuery);
}
}
private boolean closeAnyOpenDrawer() {
final int childCount = mainBinding.drawerLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mainBinding.drawerLayout.getChildAt(i);
final MouseDrawer.LayoutParams childLp = (MouseDrawer.LayoutParams) child.getLayoutParams();
if ((childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENED) == 1 ||
(childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENING) == 2 ||
childLp.onScreen >= 0.6 || childLp.isPeeking) {
mainBinding.drawerLayout.closeDrawer(child);
return true;
}
public boolean onSupportNavigateUp() {
if (currentNavControllerLiveData != null && currentNavControllerLiveData.getValue() != null) {
return currentNavControllerLiveData.getValue().navigateUp();
} }
return false; return false;
} }

607
app/src/main/java/awais/instagrabber/activities/MainActivityBackup.java

@ -0,0 +1,607 @@
package awais.instagrabber.activities;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.MatrixCursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.core.app.NotificationCompat;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.GridLayoutManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import awais.instagrabber.MainHelper;
import awais.instagrabber.R;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.GetActivityAsyncTask;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.MouseDrawer;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.databinding.ActivityMainbackupBinding;
import awais.instagrabber.dialogs.AboutDialog;
import awais.instagrabber.dialogs.QuickAccessDialog;
import awais.instagrabber.dialogs.SettingsDialog;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.ItemGetter;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.CHANNEL_ID;
import static awais.instagrabber.utils.Utils.notificationManager;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class MainActivityBackup extends BaseLanguageActivity {
private static final int INITIAL_DELAY_MILLIS = 200;
private static final int DELAY_MILLIS = 60000;
public static FetchListener<String> scanHack;
public static ItemGetter itemGetter;
public final ArrayList<PostModel> allItems = new ArrayList<>();
public final ArrayList<FeedModel> feedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> discoverItems = new ArrayList<>();
public final ArrayList<PostModel> selectedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> selectedDiscoverItems = new ArrayList<>();
public final HighlightsAdapter highlightsAdapter = new HighlightsAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof HighlightModel) {
final HighlightModel highlightModel = (HighlightModel) tag;
new iStoryStatusFetcher(highlightModel.getId(), null, false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
if (result != null && result.length > 0) {
// startActivity(new Intent(MainActivityBackup.this, StoryViewer.class)
// .putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
// .putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
// .putExtra(Constants.EXTRAS_STORIES, result)
// );
}
else
Toast.makeText(MainActivityBackup.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
private SuggestionsAdapter suggestionAdapter;
private MenuItem searchAction;
public @NonNull ActivityMainbackupBinding mainBinding;
public SearchView searchView;
public MenuItem downloadAction, settingsAction, dmsAction, notifAction;
public StoryModel[] storyModels;
public String userQuery = null, cookie, uid = null;
public MainHelper mainHelper;
public ProfileModel profileModel;
public HashtagModel hashtagModel;
public LocationModel locationModel;
private AutoCompleteTextView searchAutoComplete;
private ArrayAdapter<String> profileDialogAdapter;
private DialogInterface.OnClickListener profileDialogListener;
private Stack<String> queriesStack;
private DataBox.CookieModel cookieModel;
private Runnable runnable;
private Handler handler;
private boolean isLoggedIn;
@Override
protected void onCreate(@Nullable final Bundle bundle) {
super.onCreate(bundle);
mainBinding = ActivityMainbackupBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this);
cookie = settingsHelper.getString(Constants.COOKIE);
uid = Utils.getUserIdFromCookie(cookie);
Utils.setupCookies(cookie);
MainHelper.stopCurrentExecutor();
mainHelper = new MainHelper(this);
if (bundle == null) {
queriesStack = new Stack<>();
userQuery = null;
} else {
setStack(bundle);
userQuery = bundle.getString("query");
}
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
itemGetter = itemGetType -> {
if (itemGetType == ItemGetType.MAIN_ITEMS) return allItems;
if (itemGetType == ItemGetType.DISCOVER_ITEMS) return discoverItems;
if (itemGetType == ItemGetType.FEED_ITEMS) return feedItems;
return null;
};
scanHack = result -> {
if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer();
addToStack();
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@" + result);
mainHelper.onRefresh();
}
};
// searches for your userid and returns username
if (uid != null) {
final FetchListener<String> fetchListener = username -> {
if (!Utils.isEmpty(username)) {
// if (!BuildConfig.DEBUG) {
userQuery = username;
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing())
mainHelper.onRefresh();
// }
// adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid);
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername()))
Utils.dataBox.addUserCookie(new DataBox.CookieModel(uid, username, cookie));
}
};
boolean found = false;
cookieModel = Utils.dataBox.getCookie(uid);
if (cookieModel != null) {
final String username = cookieModel.getUsername();
if (username != null) {
found = true;
fetchListener.onResult("@" + username);
}
}
if (!found) // if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
suggestionAdapter = new SuggestionsAdapter(this, v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
addToStack();
userQuery = tag.toString();
mainHelper.onRefresh();
}
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
}
});
final Resources resources = getResources();
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
new String[]{resources.getString(R.string.view_pfp), resources.getString(R.string.show_stories)});
profileDialogListener = (dialog, which) -> {
final Intent intent;
if (which == 0 || storyModels == null || storyModels.length < 1) {
intent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
} else {
// intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
// .putExtra(Constants.EXTRAS_STORIES, storyModels)
// .putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
}
// startActivity(intent);
};
final View.OnClickListener onClickListener = v -> {
if (v == mainBinding.profileView.mainBiography) {
Utils.copyText(this, mainBinding.profileView.mainBiography.getText().toString());
} else if (v == mainBinding.profileView.locationBiography) {
Utils.copyText(this, mainBinding.profileView.locationBiography.getText().toString());
} else if (v == mainBinding.profileView.mainProfileImage || v == mainBinding.profileView.mainHashtagImage || v == mainBinding.profileView.mainLocationImage) {
if (storyModels == null || storyModels.length <= 0) {
profileDialogListener.onClick(null, 0);
} else {
// because sometimes configuration changes made this crash on some phones
new AlertDialog.Builder(this).setAdapter(profileDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null).show();
}
}
};
mainBinding.profileView.mainBiography.setOnClickListener(onClickListener);
mainBinding.profileView.locationBiography.setOnClickListener(onClickListener);
mainBinding.profileView.mainProfileImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainHashtagImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainLocationImage.setOnClickListener(onClickListener);
mainBinding.profileView.mainBiography.setEnabled(false);
mainBinding.profileView.mainProfileImage.setEnabled(false);
mainBinding.profileView.mainHashtagImage.setEnabled(false);
mainBinding.profileView.mainLocationImage.setEnabled(false);
final boolean isQueryNull = userQuery == null;
if (isQueryNull) {
allItems.clear();
mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_info);
mainBinding.profileView.privatePage2.setTextSize(20);
mainBinding.profileView.privatePage2.setText(isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null)
mainHelper.onRefresh();
mainHelper.onIntent(getIntent());
handler = new Handler();
runnable = () -> {
final GetActivityAsyncTask activityAsyncTask = new GetActivityAsyncTask(uid, cookie, result -> {
if (result == null || notificationManager == null) {
return;
}
final List<String> list = new ArrayList<>();
if (result.getRelationshipsCount() != 0) {
list.add(getString(R.string.activity_count_relationship, result.getRelationshipsCount()));
}
if (result.getUserTagsCount() != 0) {
list.add(getString(R.string.activity_count_usertags, result.getUserTagsCount()));
}
if (result.getCommentsCount() != 0) {
list.add(getString(R.string.activity_count_comments, result.getCommentsCount()));
}
if (result.getCommentLikesCount() != 0) {
list.add(getString(R.string.activity_count_commentlikes, result.getCommentLikesCount()));
}
if (result.getLikesCount() != 0) {
list.add(getString(R.string.activity_count_likes, result.getLikesCount()));
}
if (list.isEmpty()) {
return;
}
final String join = TextUtils.join(", ", list);
final String notificationString = getString(R.string.activity_count_prefix) + " " + join + ".";
final Intent intent = new Intent(getApplicationContext(), NotificationsViewer.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
final Notification notification = new NotificationCompat.Builder(MainActivityBackup.this, CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.drawable.ic_notif)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setContentText(notificationString)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.build();
notificationManager.cancel(1800000000);
notificationManager.notify(1800000000, notification);
});
activityAsyncTask.execute();
if (!Utils.isEmpty(cookie) && Utils.settingsHelper.getBoolean(Constants.CHECK_ACTIVITY))
activityAsyncTask.execute();
handler.postDelayed(runnable, DELAY_MILLIS);
};
handler.postDelayed(runnable, INITIAL_DELAY_MILLIS);
}
private void downloadSelectedItems() {
if (selectedItems.size() > 0) {
Utils.batchDownload(this, userQuery, DownloadMethod.DOWNLOAD_MAIN, selectedItems);
} else if (selectedDiscoverItems.size() > 0) {
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_DISCOVER, selectedDiscoverItems);
}
}
@Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
mainHelper.onIntent(intent);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState, @NonNull final PersistableBundle outPersistentState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
public void onRestoreInstanceState(@Nullable final Bundle savedInstanceState, @Nullable final PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
if (savedInstanceState != null) {
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
}
}
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
final FragmentManager fragmentManager = getSupportFragmentManager();
final MenuItem quickAccessAction = menu.findItem(R.id.action_quickaccess).setVisible(true);
final MenuItem.OnMenuItemClickListener clickListener = item -> {
if (item == downloadAction)
downloadSelectedItems();
else if (item == dmsAction)
startActivity(new Intent(this, DirectMessagesActivity.class));
else if (item == notifAction)
startActivity(new Intent(this, NotificationsViewer.class));
else if (item == settingsAction)
new SettingsDialog().show(fragmentManager, "settings");
else if (item == quickAccessAction)
new QuickAccessDialog()
.setQuery(userQuery, locationModel != null ? locationModel.getName() : userQuery)
.show(fragmentManager, "quickAccess");
else
new AboutDialog().show(fragmentManager, "about");
return true;
};
quickAccessAction.setOnMenuItemClickListener(clickListener);
menu.findItem(R.id.action_about).setVisible(true).setOnMenuItemClickListener(clickListener);
dmsAction = menu.findItem(R.id.action_dms).setOnMenuItemClickListener(clickListener);
notifAction = menu.findItem(R.id.action_notif).setOnMenuItemClickListener(clickListener);
settingsAction = menu.findItem(R.id.action_settings).setVisible(true).setOnMenuItemClickListener(clickListener);
downloadAction = menu.findItem(R.id.action_download).setOnMenuItemClickListener(clickListener);
if (!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE))) {
notifAction.setVisible(true);
dmsAction.setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
searchAction = menu.findItem(R.id.action_search);
searchView = (SearchView) searchAction.getActionView();
final View searchText = searchView.findViewById(R.id.search_src_text);
if (searchText instanceof AutoCompleteTextView)
searchAutoComplete = (AutoCompleteTextView) searchText;
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> {
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@" + cookieModel.getUsername())) ? "" : userQuery, false);
menu.findItem(R.id.action_about).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_dms).setVisible(false);
menu.findItem(R.id.action_quickaccess).setVisible(false);
menu.findItem(R.id.action_notif).setVisible(false);
});
searchAction.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
menu.findItem(R.id.action_about).setVisible(true);
menu.findItem(R.id.action_settings).setVisible(true);
menu.findItem(R.id.action_dms).setVisible(!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE)));
menu.findItem(R.id.action_quickaccess).setVisible(true);
menu.findItem(R.id.action_notif).setVisible(true);
return true;
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
private boolean searchUser, searchHash;
private AsyncTask<?, ?, ?> prevSuggestionAsync;
private final String[] COLUMNS = {BaseColumns._ID, Constants.EXTRAS_USERNAME, Constants.EXTRAS_NAME,
Constants.EXTRAS_TYPE, "pfp", "verified"};
private final FetchListener<SuggestionModel[]> fetchListener = new FetchListener<SuggestionModel[]>() {
@Override
public void doBefore() {
suggestionAdapter.changeCursor(null);
}
@Override
public void onResult(final SuggestionModel[] result) {
final MatrixCursor cursor;
if (result == null) cursor = null;
else {
cursor = new MatrixCursor(COLUMNS, 0);
for (int i = 0; i < result.length; i++) {
final SuggestionModel suggestionModel = result[i];
if (suggestionModel != null) {
final SuggestionType suggestionType = suggestionModel.getSuggestionType();
final Object[] objects = {i,
(suggestionType == SuggestionType.TYPE_LOCATION) ? suggestionModel.getName() : suggestionModel.getUsername(),
(suggestionType == SuggestionType.TYPE_LOCATION) ? suggestionModel.getUsername() : suggestionModel.getName(),
suggestionType, suggestionModel.getProfilePic(), suggestionModel.isVerified()};
if (!searchHash && !searchUser) cursor.addRow(objects);
else {
final boolean isCurrHash = suggestionType == SuggestionType.TYPE_HASHTAG;
if (searchHash && isCurrHash || !searchHash && !isCurrHash)
cursor.addRow(objects);
}
}
}
}
suggestionAdapter.changeCursor(cursor);
}
};
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {
}
}
@Override
public boolean onQueryTextSubmit(final String query) {
cancelSuggestionsAsync();
menu.findItem(R.id.action_about).setVisible(true);
menu.findItem(R.id.action_settings).setVisible(true);
closeAnyOpenDrawer();
addToStack();
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
mainHelper.onRefresh();
return false;
}
@Override
public boolean onQueryTextChange(final String newText) {
cancelSuggestionsAsync();
if (!Utils.isEmpty(newText)) {
searchUser = newText.charAt(0) == '@';
searchHash = newText.charAt(0) == '#';
if (newText.length() == 1 && (searchHash || searchUser)) {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(2);
} else {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(1);
prevSuggestionAsync = new SuggestionsFetcher(fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
searchUser || searchHash ? newText.substring(1) : newText);
}
}
return true;
}
});
return true;
}
@Override
public void onBackPressed() {
if (closeAnyOpenDrawer()) return;
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
return;
}
if (!mainHelper.isSelectionCleared()) return;
final GridLayoutManager layoutManager = (GridLayoutManager) mainBinding.profileView.mainPosts.getLayoutManager();
if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() >= layoutManager.getSpanCount()) {
mainBinding.profileView.mainPosts.smoothScrollToPosition(0);
mainBinding.profileView.appBarLayout.setExpanded(true, true);
return;
}
if (queriesStack != null && queriesStack.size() > 0) {
userQuery = queriesStack.pop();
if (userQuery != null) {
mainHelper.onRefresh();
return;
}
} else {
finish();
}
}
@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)
downloadSelectedItems();
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 9629 && (resultCode == 1692 || resultCode == RESULT_CANCELED))
finish();
else if (requestCode == 6007)
Utils.showImportExportDialog(this);
// else if (requestCode == 6969 && mainHelper.currentFeedPlayer != null)
// mainHelper.currentFeedPlayer.setPlayWhenReady(true);
}
@Override
protected void onPause() {
if (mainHelper != null) mainHelper.onPause();
if (handler != null && runnable != null) {
handler.removeCallbacks(runnable);
}
super.onPause();
}
@Override
protected void onResume() {
if (mainHelper != null) mainHelper.onResume();
if (handler != null && runnable != null) {
handler.postDelayed(runnable, INITIAL_DELAY_MILLIS);
}
super.onResume();
}
private void setStack(final Bundle bundle) {
final Object stack = bundle != null ? bundle.get("stack") : null;
if (stack instanceof Stack) //noinspection unchecked
queriesStack = (Stack<String>) stack;
}
public void addToStack() {
if (userQuery != null) {
if (queriesStack == null) queriesStack = new Stack<>();
queriesStack.add(userQuery);
}
}
private boolean closeAnyOpenDrawer() {
final int childCount = mainBinding.drawerLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mainBinding.drawerLayout.getChildAt(i);
final MouseDrawer.LayoutParams childLp = (MouseDrawer.LayoutParams) child.getLayoutParams();
if ((childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENED) == 1 ||
(childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENING) == 2 ||
childLp.onScreen >= 0.6 || childLp.isPeeking) {
mainBinding.drawerLayout.closeDrawer(child);
return true;
}
}
return false;
}
}

10
app/src/main/java/awais/instagrabber/activities/PostViewer.java

@ -161,7 +161,7 @@ public final class PostViewer extends BaseLanguageActivity {
if (player != null) { if (player != null) {
final float intVol = player.getVolume() == 0f ? 1f : 0f; final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol); player.setVolume(intVol);
viewerBinding.bottomPanel.btnMute.setImageResource(intVol == 0f ? R.drawable.mute : R.drawable.vol);
viewerBinding.bottomPanel.btnMute.setImageResource(intVol == 0f ? R.drawable.ic_volume_off_24 : R.drawable.ic_volume_up_24);
Utils.sessionVolumeFull = intVol == 1f; Utils.sessionVolumeFull = intVol == 1f;
} }
} else if (v == viewerBinding.btnLike) { } else if (v == viewerBinding.btnLike) {
@ -307,7 +307,7 @@ public final class PostViewer extends BaseLanguageActivity {
isFromShare = postModel.getPosition() == -1 || postIdNull; isFromShare = postModel.getPosition() == -1 || postIdNull;
viewerCaptionParent = (View) viewerBinding.bottomPanel.viewerCaption.getParent(); viewerCaptionParent = (View) viewerBinding.bottomPanel.viewerCaption.getParent();
viewsContainer = (View) viewerBinding.bottomPanel.tvVideoViews.getParent();
viewsContainer = (View) viewerBinding.bottomPanel.videoViewsContainer;
viewerBinding.mediaList.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)); viewerBinding.mediaList.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
viewerBinding.mediaList.setAdapter(mediaAdapter); viewerBinding.mediaList.setAdapter(mediaAdapter);
@ -320,8 +320,8 @@ public final class PostViewer extends BaseLanguageActivity {
if (itemGetType == ItemGetType.SAVED_ITEMS && SavedViewer.itemGetter != null) { if (itemGetType == ItemGetType.SAVED_ITEMS && SavedViewer.itemGetter != null) {
itemGetterItems = SavedViewer.itemGetter.get(itemGetType); itemGetterItems = SavedViewer.itemGetter.get(itemGetType);
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.SAVED_ITEMS && isFromShare); isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.SAVED_ITEMS && isFromShare);
} else if (itemGetType != null && MainActivity.itemGetter != null) {
itemGetterItems = MainActivity.itemGetter.get(itemGetType);
} else if (itemGetType != null && MainActivityBackup.itemGetter != null) {
itemGetterItems = MainActivityBackup.itemGetter.get(itemGetType);
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare); isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare);
} else { } else {
itemGetterItems = null; itemGetterItems = null;
@ -449,7 +449,7 @@ public final class PostViewer extends BaseLanguageActivity {
player.prepare(mediaSource); player.prepare(mediaSource);
player.setVolume(vol); player.setVolume(vol);
viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener); viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener);
} }

78
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java

@ -62,7 +62,6 @@ import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -140,13 +139,13 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
final HighlightModel highlightModel = (HighlightModel) tag; final HighlightModel highlightModel = (HighlightModel) tag;
new iStoryStatusFetcher(highlightModel.getId(), null, false, false, new iStoryStatusFetcher(highlightModel.getId(), null, false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> { (!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
if (result != null && result.length > 0)
startActivity(new Intent(ProfileViewer.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result)
);
else
if (result != null && result.length > 0) {
// startActivity(new Intent(ProfileViewer.this, StoryViewer.class)
// .putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
// .putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
// .putExtra(Constants.EXTRAS_STORIES, result)
// );
} else
Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@ -188,11 +187,12 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
newintent = new Intent(this, ProfilePicViewer.class).putExtra( newintent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)), ((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel))); ((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
} else
newintent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(newintent);
}
// else
// newintent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
// .putExtra(Constants.EXTRAS_STORIES, storyModels)
// .putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
// startActivity(newintent);
}; };
profileBinding.profileView.swipeRefreshLayout.setOnRefreshListener(this); profileBinding.profileView.swipeRefreshLayout.setOnRefreshListener(this);
@ -213,26 +213,26 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(ProfileViewer.this, Utils.convertDpToPx(110)); final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(ProfileViewer.this, Utils.convertDpToPx(110));
profileBinding.profileView.mainPosts.setLayoutManager(layoutManager); profileBinding.profileView.mainPosts.setLayoutManager(layoutManager);
profileBinding.profileView.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); profileBinding.profileView.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
profileBinding.profileView.mainPosts.setAdapter(postsAdapter = new PostsAdapter(allItems, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else startActivity(new Intent(ProfileViewer.this, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
.putExtra(Constants.EXTRAS_POST, postModel)
.putExtra(Constants.EXTRAS_USER, userQuery)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
}
}, v -> { // long click listener
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
return true;
}));
// profileBinding.profileView.mainPosts.setAdapter(postsAdapter = new PostsAdapter(allItems, v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// final PostModel postModel = (PostModel) tag;
//
// if (postsAdapter.isSelecting) toggleSelection(postModel);
// else startActivity(new Intent(ProfileViewer.this, PostViewer.class)
// .putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
// .putExtra(Constants.EXTRAS_POST, postModel)
// .putExtra(Constants.EXTRAS_USER, userQuery)
// .putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
// }
// }, v -> { // long click listener
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// postsAdapter.isSelecting = true;
// toggleSelection((PostModel) tag);
// }
// return true;
// }));
this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if ((!autoloadPosts || isHashtag) && hasNextPage) { if ((!autoloadPosts || isHashtag) && hasNextPage) {
@ -279,7 +279,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
allItems.clear(); allItems.clear();
selectedItems.clear(); selectedItems.clear();
if (postsAdapter != null) { if (postsAdapter != null) {
postsAdapter.isSelecting = false;
// postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged(); postsAdapter.notifyDataSetChanged();
} }
profileBinding.profileView.appBarLayout.setExpanded(true, true); profileBinding.profileView.appBarLayout.setExpanded(true, true);
@ -776,11 +776,11 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
} }
private void notifyAdapter(final PostModel postModel) { private void notifyAdapter(final PostModel postModel) {
if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (downloadAction != null) downloadAction.setVisible(postsAdapter.isSelecting);
// if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
// if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
// else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
//
// if (downloadAction != null) downloadAction.setVisible(postsAdapter.isSelecting);
} }
@Override @Override

172
app/src/main/java/awais/instagrabber/activities/SavedViewer.java

@ -6,30 +6,36 @@ import android.content.res.Resources;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.i.iLikedFetcher; import awais.instagrabber.asyncs.i.iLikedFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager; import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivitySavedBinding; import awais.instagrabber.databinding.ActivitySavedBinding;
import awais.instagrabber.fragments.main.viewmodels.ProfilePostsViewModel;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.ItemGetter; import awais.instagrabber.interfaces.ItemGetter;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType; import awais.instagrabber.models.enums.ItemGetType;
@ -48,22 +54,61 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
//private CommentModel commentModel; //private CommentModel commentModel;
private ActivitySavedBinding savedBinding; private ActivitySavedBinding savedBinding;
private String action, username, endCursor; private String action, username, endCursor;
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private RecyclerLazyLoader lazyLoader; private RecyclerLazyLoader lazyLoader;
private Resources resources; private Resources resources;
private ArrayList<PostModel> selectedItems = new ArrayList<>(); private ArrayList<PostModel> selectedItems = new ArrayList<>();
private final ArrayList<PostModel> allItems = new ArrayList<>();
private MenuItem downloadAction;
private ActionMode actionMode;
private ProfilePostsViewModel profilePostsViewModel;
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (postsAdapter == null) {
remove();
return;
}
postsAdapter.clearSelection();
remove();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu,
new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) {
return false;
}
Utils.batchDownload(SavedViewer.this,
username,
DownloadMethod.DOWNLOAD_MAIN,
postsAdapter.getSelectedModels());
checkAndResetAction();
return true;
}
return false;
}
});
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
@Override @Override
public void onResult(final PostModel[] result) { public void onResult(final PostModel[] result) {
final int oldSize = allItems.size();
if (result != null) { if (result != null) {
allItems.addAll(Arrays.asList(result));
postsAdapter.notifyItemRangeInserted(oldSize, result.length);
final List<PostModel> current = profilePostsViewModel.getList().getValue();
final List<PostModel> resultList = Arrays.asList(result);
if (current == null) {
profilePostsViewModel.getList().postValue(resultList);
} else {
final List<PostModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(resultList);
profilePostsViewModel.getList().postValue(currentCopy);
}
savedBinding.mainPosts.post(() -> { savedBinding.mainPosts.post(() -> {
savedBinding.mainPosts.setNestedScrollingEnabled(true); savedBinding.mainPosts.setNestedScrollingEnabled(true);
savedBinding.mainPosts.setVisibility(View.VISIBLE); savedBinding.mainPosts.setVisibility(View.VISIBLE);
@ -85,13 +130,12 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
} }
model.setPageCursor(false, null); model.setPageCursor(false, null);
} }
}
else {
} else {
savedBinding.swipeRefreshLayout.setRefreshing(false); savedBinding.swipeRefreshLayout.setRefreshing(false);
if (oldSize == 0) {
Toast.makeText(getApplicationContext(), R.string.empty_list, Toast.LENGTH_SHORT).show();
finish();
}
// if (oldSize == 0) {
// Toast.makeText(getApplicationContext(), R.string.empty_list, Toast.LENGTH_SHORT).show();
// finish();
// }
} }
} }
}; };
@ -117,26 +161,36 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
return; return;
} }
savedBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(allItems, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else startActivity(new Intent(this, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
profilePostsViewModel = new ViewModelProvider(this).get(ProfilePostsViewModel.class);
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;
startActivity(new Intent(this, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, position)
.putExtra(Constants.EXTRAS_POST, postModel) .putExtra(Constants.EXTRAS_POST, postModel)
.putExtra(Constants.EXTRAS_USER, username) .putExtra(Constants.EXTRAS_USER, username)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.SAVED_ITEMS)); .putExtra(Constants.EXTRAS_TYPE, ItemGetType.SAVED_ITEMS));
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
} }
}, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
final OnBackPressedDispatcher onBackPressedDispatcher = getOnBackPressedDispatcher();
if (onBackPressedDispatcher.hasEnabledCallbacks()) return true;
actionMode = startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(onBackPressedCallback);
return true; return true;
}));
});
savedBinding.mainPosts.setAdapter(postsAdapter);
profilePostsViewModel.getList().observe(this, postsAdapter::submitList);
savedBinding.swipeRefreshLayout.setRefreshing(true); savedBinding.swipeRefreshLayout.setRefreshing(true);
setSupportActionBar(savedBinding.toolbar.toolbar); setSupportActionBar(savedBinding.toolbar.toolbar);
savedBinding.toolbar.toolbar.setTitle((action.charAt(0) == '$' ? R.string.saved : savedBinding.toolbar.toolbar.setTitle((action.charAt(0) == '$' ? R.string.saved :
@ -157,19 +211,22 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
savedBinding.mainPosts.addOnScrollListener(lazyLoader); savedBinding.mainPosts.addOnScrollListener(lazyLoader);
itemGetter = itemGetType -> { itemGetter = itemGetType -> {
if (itemGetType == ItemGetType.SAVED_ITEMS) return allItems;
if (itemGetType == ItemGetType.SAVED_ITEMS)
return profilePostsViewModel.getList().getValue();
return null; return null;
}; };
if (action.charAt(0) == '^') new iLikedFetcher(postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else new PostsFetcher(action, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (action.charAt(0) == '^')
new iLikedFetcher(postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
new PostsFetcher(action, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@Override @Override
public boolean onCreateOptionsMenu(final Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.saved, menu); getMenuInflater().inflate(R.menu.saved, menu);
downloadAction = menu.findItem(R.id.downloadAction);
final MenuItem downloadAction = menu.findItem(R.id.downloadAction);
downloadAction.setVisible(false); downloadAction.setVisible(false);
menu.findItem(R.id.favouriteAction).setVisible(false); menu.findItem(R.id.favouriteAction).setVisible(false);
@ -183,27 +240,21 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
return true; return true;
} }
public void deselectSelection(final BasePostModel postModel) {
if (postModel instanceof PostModel) {
selectedItems.remove(postModel);
postModel.setSelected(false);
if (postsAdapter != null) notifyAdapter((PostModel) postModel);
}
}
@Override @Override
public void onRefresh() { public void onRefresh() {
if (lazyLoader != null) lazyLoader.resetState(); if (lazyLoader != null) lazyLoader.resetState();
stopCurrentExecutor(); stopCurrentExecutor();
allItems.clear();
profilePostsViewModel.getList().postValue(Collections.emptyList());
selectedItems.clear(); selectedItems.clear();
if (postsAdapter != null) { if (postsAdapter != null) {
postsAdapter.isSelecting = false;
// postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged(); postsAdapter.notifyDataSetChanged();
} }
savedBinding.swipeRefreshLayout.setRefreshing(true); savedBinding.swipeRefreshLayout.setRefreshing(true);
if (action.charAt(0) == '^') new iLikedFetcher(postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else new PostsFetcher(action, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (action.charAt(0) == '^')
new iLikedFetcher(postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
new PostsFetcher(action, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@Override @Override
@ -225,26 +276,13 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr
} }
} }
private void toggleSelection(final PostModel postModel) {
if (postModel != null && postsAdapter != null) {
if (postModel.isSelected()) selectedItems.remove(postModel);
else if (selectedItems.size() >= 100) {
Toast.makeText(SavedViewer.this, R.string.downloader_too_many, Toast.LENGTH_SHORT);
return;
}
else selectedItems.add(postModel);
postModel.setSelected(!postModel.isSelected());
notifyAdapter(postModel);
}
}
private void notifyAdapter(final PostModel postModel) {
if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (downloadAction != null) {
downloadAction.setVisible(postsAdapter.isSelecting);
private boolean checkAndResetAction() {
final OnBackPressedDispatcher onBackPressedDispatcher = getOnBackPressedDispatcher();
if (!onBackPressedDispatcher.hasEnabledCallbacks() || actionMode == null) {
return false;
} }
actionMode.finish();
actionMode = null;
return true;
} }
} }

936
app/src/main/java/awais/instagrabber/activities/StoryViewer.java

@ -1,769 +1,167 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.json.JSONObject;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.ActivityStoryViewerBinding;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class StoryViewer extends BaseLanguageActivity {
private final StoriesAdapter storiesAdapter = new StoriesAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof StoryModel) {
currentStory = (StoryModel) tag;
slidePos = currentStory.getPosition();
refreshStory();
}
}
});
private ActivityStoryViewerBinding storyViewerBinding;
private StoryModel[] storyModels;
private GestureDetectorCompat gestureDetector;
private SimpleExoPlayer player;
private SwipeEvent swipeEvent;
private MenuItem menuDownload, menuDm;
private PollModel poll;
private QuestionModel question;
private String[] mentions;
private QuizModel quiz;
private StoryModel currentStory;
private String url, username;
private int slidePos = 0, lastSlidePos = 0;
private final String cookie = settingsHelper.getString(Constants.COOKIE);
private boolean fetching = false;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
storyViewerBinding = ActivityStoryViewerBinding.inflate(getLayoutInflater());
setContentView(storyViewerBinding.getRoot());
setSupportActionBar(storyViewerBinding.toolbar.toolbar);
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_STORIES)
|| (storyModels = (StoryModel[]) intent.getSerializableExtra(Constants.EXTRAS_STORIES)) == null) {
Utils.errorFinish(this);
return;
}
username = intent.getStringExtra(Constants.EXTRAS_USERNAME);
final String highlight = intent.getStringExtra(Constants.EXTRAS_HIGHLIGHT);
final boolean hasUsername = !Utils.isEmpty(username);
final boolean hasHighlight = !Utils.isEmpty(highlight);
if (hasUsername) {
username = username.replace("@", "");
storyViewerBinding.toolbar.toolbar.setTitle(username);
storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> {
searchUsername(username);
});
if (hasHighlight) storyViewerBinding.toolbar.toolbar.setSubtitle(getString(R.string.title_highlight, highlight));
else storyViewerBinding.toolbar.toolbar.setSubtitle(R.string.title_user_story);
}
storyViewerBinding.storiesList.setVisibility(View.GONE);
storyViewerBinding.storiesList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
storyViewerBinding.storiesList.setAdapter(storiesAdapter);
swipeEvent = new SwipeEvent() {
private final int storiesLen = storyModels != null ? storyModels.length : 0;
@Override
public void onSwipe(final boolean isRightSwipe) {
if (storyModels != null && storiesLen > 0) {
if (((slidePos + 1 >= storiesLen && isRightSwipe == false) || (slidePos == 0 && isRightSwipe == true))
&& intent.hasExtra(Constants.FEED)) {
final FeedStoryModel[] storyFeed = (FeedStoryModel[]) intent.getSerializableExtra(Constants.FEED);
final int index = intent.getIntExtra(Constants.FEED_ORDER, 1738);
if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction().execute();
if ((isRightSwipe == true && index == 0) || (isRightSwipe == false && index == storyFeed.length - 1))
Toast.makeText(getApplicationContext(), R.string.no_more_stories, Toast.LENGTH_SHORT).show();
else {
final FeedStoryModel feedStoryModel = isRightSwipe ?
(index == 0 ? null : storyFeed[index - 1]) :
(storyFeed.length == index + 1 ? null : storyFeed[index + 1]);
if (feedStoryModel != null) {
if (fetching) {
Toast.makeText(getApplicationContext(), R.string.be_patient, Toast.LENGTH_SHORT).show();
} else {
fetching = true;
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
if (result != null && result.length > 0) {
final Intent newIntent = new Intent(getApplicationContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, result)
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
.putExtra(Constants.FEED, storyFeed)
.putExtra(Constants.FEED_ORDER, isRightSwipe ? (index - 1) : (index + 1));
newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(newIntent);
} else
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
}
else {
if (isRightSwipe) {
if (--slidePos <= 0) slidePos = 0;
} else if (++slidePos >= storiesLen) slidePos = storiesLen - 1;
currentStory = storyModels[slidePos];
refreshStory();
}
}
}
};
gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent));
viewPost();
}
@SuppressLint("ClickableViewAccessibility")
private void viewPost() {
lastSlidePos = 0;
storyViewerBinding.storiesList.setVisibility(View.GONE);
storiesAdapter.setData(null);
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
storyViewerBinding.playerView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
storyViewerBinding.imageViewer.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> {
final float diffX = e2.getX() - e1.getX();
try {
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
swipeEvent.onSwipe(diffX > 0);
return true;
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "viewPost",
new Pair<>("swipeEvent", swipeEvent),
new Pair<>("diffX", diffX));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return false;
});
storyViewerBinding.spotify.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(tag.toString()));
startActivity(intent);
}
});
storyViewerBinding.viewStoryPost.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) startActivity(new Intent(this, PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(tag.toString(), tag.toString().matches("^[\\d]+$"))));
});
final View.OnClickListener storyActionListener = v -> {
final Object tag = v.getTag();
if (tag instanceof PollModel) {
poll = (PollModel) tag;
if (poll.getMyChoice() > -1)
new AlertDialog.Builder(this).setTitle(R.string.voted_story_poll)
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{
(poll.getMyChoice() == 0 ? "√ " : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
(poll.getMyChoice() == 1 ? "√ " : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), null)
.setPositiveButton(R.string.ok, null)
.show();
else new AlertDialog.Builder(this).setTitle(poll.getQuestion())
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
if (!Utils.isEmpty(cookie)) new VoteAction().execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
else if (tag instanceof QuestionModel) {
question = (QuestionModel) tag;
final EditText input = new EditText(this);
input.setHint(R.string.answer_hint);
new AlertDialog.Builder(this).setTitle(question.getQuestion())
.setView(input)
.setPositiveButton(R.string.ok, (d,w) -> {
new RespondAction().execute(input.getText().toString());
})
.setNegativeButton(R.string.cancel, null)
.show();
}
else if (tag instanceof String[]) {
mentions = (String[]) tag;
new AlertDialog.Builder(this).setTitle(R.string.story_mentions)
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mentions), (d,w) -> {
searchUsername(mentions[w]);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
else if (tag instanceof QuizModel) {
quiz = (QuizModel) quiz;
String[] choices = new String[quiz.getChoices().length];
for (int q = 0; q < choices.length; ++q) {
choices[q] = (quiz.getMyChoice() == q ? "√ " :"") + quiz.getChoices()[q]+ " (" + quiz.getCounts()[q] + ")";
}
new AlertDialog.Builder(this).setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, choices), (d,w) -> {
if (quiz.getMyChoice() == -1 && !Utils.isEmpty(cookie)) new QuizAction().execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
};
storyViewerBinding.poll.setOnClickListener(storyActionListener);
storyViewerBinding.answer.setOnClickListener(storyActionListener);
storyViewerBinding.mention.setOnClickListener(storyActionListener);
storyViewerBinding.quiz.setOnClickListener(storyActionListener);
storiesAdapter.setData(storyModels);
if (storyModels.length > 1) storyViewerBinding.storiesList.setVisibility(View.VISIBLE);
currentStory = storyModels[0];
refreshStory();
}
private void setupVideo() {
storyViewerBinding.playerView.setVisibility(View.VISIBLE);
storyViewerBinding.progressView.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setImageDrawable(null);
player = new SimpleExoPlayer.Builder(this).build();
storyViewerBinding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, "instagram"))
.createMediaSource(Uri.parse(url));
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.VISIBLE);
}
@Override
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
storyViewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) {
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
storyViewerBinding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
storyViewerBinding.playerView.setOnClickListener(v -> {
if (player != null) {
if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0);
player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying());
}
});
}
private void setupImage() {
storyViewerBinding.progressView.setVisibility(View.VISIBLE);
storyViewerBinding.playerView.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setImageDrawable(null);
storyViewerBinding.imageViewer.setVisibility(View.VISIBLE);
storyViewerBinding.imageViewer.setZoomable(true);
storyViewerBinding.imageViewer.setZoomTransitionDuration(420);
storyViewerBinding.imageViewer.setMaximumScale(7.2f);
Glide.with(this).load(url).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
storyViewerBinding.progressView.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
return false;
}
}).into(storyViewerBinding.imageViewer);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_search).setVisible(false);
menuDownload = menu.findItem(R.id.action_download);
menuDm = menu.findItem(R.id.action_dms);
menuDownload.setVisible(true);
menuDm.setVisible(false);
menuDownload.setOnMenuItemClickListener(item -> {
if (ContextCompat.checkSelfPermission(this, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
downloadStory();
else
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020);
return true;
});
menuDm.setOnMenuItemClickListener(item -> {
final EditText input = new EditText(this);
input.setHint(R.string.reply_hint);
new AlertDialog.Builder(this).setTitle(R.string.reply_story)
.setView(input)
.setPositiveButton(R.string.ok, (d,w) -> {
new CommentAction().execute(input.getText().toString());
})
.setNegativeButton(R.string.cancel, null)
.show();
return true;
});
return true;
}
@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) downloadStory();
}
@Override
public void onPause() {
super.onPause();
if (Build.VERSION.SDK_INT < 24) releasePlayer();
}
@Override
public void onStop() {
super.onStop();
if (Build.VERSION.SDK_INT >= 24) releasePlayer();
}
private void downloadStory() {
int error = 0;
if (currentStory != null) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!Utils.isEmpty(customPath)) dir = new File(customPath);
}
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !Utils.isEmpty(username))
dir = new File(dir, username);
if (dir.exists() || dir.mkdirs()) {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp()
+ Utils.getExtensionFromModel(storyUrl, currentStory));
new DownloadAsync(this, storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
: R.string.downloader_error_download_file;
Toast.makeText(this, toastRes, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1) Toast.makeText(this, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2) Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
private void refreshStory() {
if (storyViewerBinding.storiesList.getVisibility() == View.VISIBLE) {
StoryModel item = storiesAdapter.getItemAt(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
storiesAdapter.notifyItemChanged(lastSlidePos, item);
}
item = storiesAdapter.getItemAt(slidePos);
if (item != null) {
item.setCurrentSlide(true);
storiesAdapter.notifyItemChanged(slidePos, item);
}
}
lastSlidePos = slidePos;
final MediaItemType itemType = currentStory.getItemType();
if (menuDownload != null) menuDownload.setVisible(false);
url = itemType == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final String shortCode = currentStory.getTappableShortCode();
storyViewerBinding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
storyViewerBinding.viewStoryPost.setTag(shortCode);
final String spotify = currentStory.getSpotify();
storyViewerBinding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE);
storyViewerBinding.spotify.setTag(spotify);
poll = currentStory.getPoll();
storyViewerBinding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE);
storyViewerBinding.poll.setTag(poll);
question = currentStory.getQuestion();
storyViewerBinding.answer.setVisibility((question != null && !Utils.isEmpty(cookie)) ? View.VISIBLE : View.GONE);
storyViewerBinding.answer.setTag(question);
mentions = currentStory.getMentions();
storyViewerBinding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE);
storyViewerBinding.mention.setTag(mentions);
quiz = currentStory.getQuiz();
storyViewerBinding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
storyViewerBinding.quiz.setTag(quiz);
releasePlayer();
final Intent intent = getIntent();
if (intent.getBooleanExtra(Constants.EXTRAS_HASHTAG, false)) {
storyViewerBinding.toolbar.toolbar.setTitle(currentStory.getUsername() + " (" + intent.getStringExtra(Constants.EXTRAS_USERNAME) + ")");
storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> {
searchUsername(currentStory.getUsername());
});
}
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
if (!intent.hasExtra(Constants.EXTRAS_HIGHLIGHT))
storyViewerBinding.toolbar.toolbar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)));
if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction().execute();
}
private void searchUsername(final String text) {
startActivity(
new Intent(getApplicationContext(), ProfileViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, text)
);
}
private void releasePlayer() {
if (player != null) {
try { player.stop(true); } catch (Exception ignored) { }
try { player.release(); } catch (Exception ignored) { }
player = null;
}
}
public static int indexOfIntArray(Object[] array, Object key) {
int returnvalue = -1;
for (int i = 0; i < array.length; ++i) {
if (key == array[i]) {
returnvalue = i;
break;
}
}
return returnvalue;
}
class VoteAction extends AsyncTask<Integer, Void, Void> {
int ok = -1;
String action;
protected Void doInBackground(Integer... rawchoice) {
int choice = rawchoice[0];
final String url = "https://www.instagram.com/media/"+currentStory.getStoryMediaId().split("_")[0]+"/"+poll.getId()+"/story_poll_vote/";
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "6");
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes("vote="+choice);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = choice;
}
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "vote: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (ok > -1) {
poll.setMyChoice(ok);
Toast.makeText(getApplicationContext(), R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
}
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
}
class QuizAction extends AsyncTask<Integer, Void, Void> {
int ok = -1;
String action;
protected Void doInBackground(Integer... rawchoice) {
int choice = rawchoice[0];
final String url = "https://i.instagram.com/api/v1/media/"+currentStory.getStoryMediaId().split("_")[0]+"/"+quiz.getId()+"/story_quiz_answer/";
try {
JSONObject ogbody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString()
+"\",\"mutation_token\":\"" + UUID.randomUUID().toString()
+"\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+"\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+"\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+"\"}");
ogbody.put("answer", String.valueOf(choice));
String urlParameters = Utils.sign(ogbody.toString());
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
Log.d("austin_debug", "quiz: "+url+" "+cookie+" "+urlParameters);
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = choice;
}
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "quiz: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (ok > -1) {
quiz.setMyChoice(ok);
Toast.makeText(getApplicationContext(), R.string.answered_story, Toast.LENGTH_SHORT).show();
}
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
}
class RespondAction extends AsyncTask<String, Void, Void> {
boolean ok = false;
String action;
protected Void doInBackground(String... rawchoice) {
final String url = "https://i.instagram.com/api/v1/media/"
+currentStory.getStoryMediaId().split("_")[0]+"/"+question.getId()+"/story_question_response/";
try {
JSONObject ogbody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString()
+"\",\"mutation_token\":\"" + UUID.randomUUID().toString()
+"\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+"\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+"\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+"\"}");
String choice = rawchoice[0].replaceAll("\"", ("\\\""));
ogbody.put("response", choice);
String urlParameters = Utils.sign(ogbody.toString());
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "respond: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (ok) {
Toast.makeText(getApplicationContext(), R.string.answered_story, Toast.LENGTH_SHORT).show();
}
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
}
class SeenAction extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... lmao) {
final String url = "https://www.instagram.com/stories/reel/seen";
try {
String urlParameters = "reelMediaId="+currentStory.getStoryMediaId().split("_")[0]
+"&reelMediaOwnerId="+currentStory.getUserId()
+"&reelId="+currentStory.getUserId()
+"&reelMediaTakenAt="+ currentStory.getTimestamp()
+"&viewSeenAt="+ currentStory.getTimestamp();
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
Log.d("austin_debug", urlConnection.getResponseCode() + " " + Utils.readFromConnection(urlConnection));
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "seen: " + ex);
}
return null;
}
}
class CommentAction extends AsyncTask<String, Void, Void> {
protected Void doInBackground(String... rawAction) {
final String action = rawAction[0];
final String url = "https://i.instagram.com/api/v1/direct_v2/create_group_thread/";
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setUseCaches(false);
final String urlParameters = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+"\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+"\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+"\",\"recipient_users\":\"["+currentStory.getUserId() // <- string of array of number (not joking)
+"]\"}");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "" + urlParameters.getBytes().length);
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String threadid = new JSONObject(Utils.readFromConnection(urlConnection)).getString("thread_id");
final DirectThreadBroadcaster.StoryReplyBroadcastOptions options =
new DirectThreadBroadcaster.StoryReplyBroadcastOptions(
action,
currentStory.getStoryMediaId(),
currentStory.getUserId()
);
final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadid);
broadcast.setOnTaskCompleteListener(result -> {
Toast.makeText(getApplicationContext(),
result != null ? R.string.answered_story : R.string.downloader_unknown_error,
Toast.LENGTH_SHORT).show();
});
broadcast.execute(options);
}
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "reply (CT): " + ex);
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
return null;
}
}
}
// package awais.instagrabber.activities;
//
// import android.content.Intent;
// import android.os.AsyncTask;
// import android.os.Bundle;
// import android.view.MenuItem;
// import android.view.View;
// import android.widget.Toast;
//
// import androidx.annotation.Nullable;
// import androidx.core.view.GestureDetectorCompat;
// import androidx.recyclerview.widget.LinearLayoutManager;
//
// import awais.instagrabber.R;
// import awais.instagrabber.adapters.StoriesAdapter;
// import awais.instagrabber.asyncs.SeenAction;
// import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
// import awais.instagrabber.customviews.helpers.SwipeGestureListener;
// import awais.instagrabber.databinding.ActivityStoryViewerBinding;
// import awais.instagrabber.interfaces.SwipeEvent;
// import awais.instagrabber.models.FeedStoryModel;
// import awais.instagrabber.models.StoryModel;
// import awais.instagrabber.models.stickers.PollModel;
// import awais.instagrabber.models.stickers.QuestionModel;
// import awais.instagrabber.models.stickers.QuizModel;
// import awais.instagrabber.utils.Constants;
// import awais.instagrabber.utils.Utils;
//
// import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
// import static awais.instagrabber.utils.Utils.settingsHelper;
//
// public final class StoryViewer extends BaseLanguageActivity {
// private final StoriesAdapter storiesAdapter = new StoriesAdapter(null, new View.OnClickListener() {
// @Override
// public void onClick(final View v) {
// final Object tag = v.getTag();
// if (tag instanceof StoryModel) {
// currentStory = (StoryModel) tag;
// slidePos = currentStory.getPosition();
// refreshStory();
// }
// }
// });
// private ActivityStoryViewerBinding storyViewerBinding;
// private StoryModel[] storyModels;
// private GestureDetectorCompat gestureDetector;
//
// private SwipeEvent swipeEvent;
// private MenuItem menuDownload, menuDm;
// private PollModel poll;
// private QuestionModel question;
// private String[] mentions;
// private QuizModel quiz;
// private StoryModel currentStory;
// private String url, username;
// private int slidePos = 0, lastSlidePos = 0;
// private final String cookie = settingsHelper.getString(Constants.COOKIE);
// private boolean fetching = false;
//
// @Override
// protected void onCreate(@Nullable final Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// storyViewerBinding = ActivityStoryViewerBinding.inflate(getLayoutInflater());
// setContentView(storyViewerBinding.getRoot());
//
// setSupportActionBar(storyViewerBinding.toolbar.toolbar);
//
// final Intent intent = getIntent();
// if (intent == null || !intent.hasExtra(Constants.EXTRAS_STORIES)
// || (storyModels = (StoryModel[]) intent.getSerializableExtra(Constants.EXTRAS_STORIES)) == null) {
// Utils.errorFinish(this);
// return;
// }
//
// username = intent.getStringExtra(Constants.EXTRAS_USERNAME);
// final String highlight = intent.getStringExtra(Constants.EXTRAS_HIGHLIGHT);
// final boolean hasUsername = !Utils.isEmpty(username);
// final boolean hasHighlight = !Utils.isEmpty(highlight);
//
// if (hasUsername) {
// username = username.replace("@", "");
// storyViewerBinding.toolbar.toolbar.setTitle(username);
// storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> {
// searchUsername(username);
// });
// if (hasHighlight) storyViewerBinding.toolbar.toolbar.setSubtitle(getString(R.string.title_highlight, highlight));
// else storyViewerBinding.toolbar.toolbar.setSubtitle(R.string.title_user_story);
// }
//
// storyViewerBinding.storiesList.setVisibility(View.GONE);
// storyViewerBinding.storiesList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
// storyViewerBinding.storiesList.setAdapter(storiesAdapter);
//
// swipeEvent = new SwipeEvent() {
// private final int storiesLen = storyModels != null ? storyModels.length : 0;
//
// @Override
// public void onSwipe(final boolean isRightSwipe) {
// if (storyModels != null && storiesLen > 0) {
// if (((slidePos + 1 >= storiesLen && isRightSwipe == false) || (slidePos == 0 && isRightSwipe == true))
// && intent.hasExtra(Constants.FEED)) {
// final FeedStoryModel[] storyFeed = (FeedStoryModel[]) intent.getSerializableExtra(Constants.FEED);
// final int index = intent.getIntExtra(Constants.FEED_ORDER, 1738);
// if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction(cookie, storyModel).execute();
// if ((isRightSwipe == true && index == 0) || (isRightSwipe == false && index == storyFeed.length - 1))
// Toast.makeText(getApplicationContext(), R.string.no_more_stories, Toast.LENGTH_SHORT).show();
// else {
// final FeedStoryModel feedStoryModel = isRightSwipe ?
// (index == 0 ? null : storyFeed[index - 1]) :
// (storyFeed.length == index + 1 ? null : storyFeed[index + 1]);
// if (feedStoryModel != null) {
// if (fetching) {
// Toast.makeText(getApplicationContext(), R.string.be_patient, Toast.LENGTH_SHORT).show();
// } else {
// fetching = true;
// new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
// if (result != null && result.length > 0) {
// final Intent newIntent = new Intent(getApplicationContext(), StoryViewer.class)
// .putExtra(Constants.EXTRAS_STORIES, result)
// .putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
// .putExtra(Constants.FEED, storyFeed)
// .putExtra(Constants.FEED_ORDER, isRightSwipe ? (index - 1) : (index + 1));
// newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// startActivity(newIntent);
// } else
// Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
// }
// }
// }
// else {
// if (isRightSwipe) {
// if (--slidePos <= 0) slidePos = 0;
// } else if (++slidePos >= storiesLen) slidePos = storiesLen - 1;
// currentStory = storyModels[slidePos];
// refreshStory();
// }
// }
// }
// };
// gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent));
//
// viewPost();
// }
//
// private void searchUsername(final String text) {
// startActivity(
// new Intent(getApplicationContext(), ProfileViewer.class)
// .putExtra(Constants.EXTRAS_USERNAME, text)
// );
// }
//
//
//
// public static int indexOfIntArray(Object[] array, Object key) {
// int returnvalue = -1;
// for (int i = 0; i < array.length; ++i) {
// if (key == array[i]) {
// returnvalue = i;
// break;
// }
// }
// return returnvalue;
// }
//
// }

77
app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java

@ -1,87 +1,56 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import java.util.ArrayList;
import androidx.recyclerview.widget.DiffUtil;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DiscoverViewHolder; import awais.instagrabber.adapters.viewholder.DiscoverViewHolder;
import awais.instagrabber.models.DiscoverItemModel; import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
public final class DiscoverAdapter extends RecyclerView.Adapter<DiscoverViewHolder> {
private final ArrayList<DiscoverItemModel> discoverItemModels;
private final View.OnClickListener clickListener;
private final View.OnLongClickListener longClickListener;
private LayoutInflater layoutInflater;
public boolean isSelecting = false;
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 ArrayList<DiscoverItemModel> discoverItemModels, final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
this.discoverItemModels = discoverItemModels;
this.longClickListener = longClickListener;
this.clickListener = clickListener;
public DiscoverAdapter(final OnItemClickListener<DiscoverItemModel> clickListener,
final OnItemLongClickListener<DiscoverItemModel> longClickListener) {
super(diffCallback, clickListener, longClickListener);
} }
@NonNull @NonNull
@Override @Override
public DiscoverViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { public DiscoverViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
return new DiscoverViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false)); return new DiscoverViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false));
} }
@Override @Override
public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) {
final DiscoverItemModel itemModel = discoverItemModels.get(position);
final DiscoverItemModel itemModel = getItem(position);
if (itemModel != null) { if (itemModel != null) {
itemModel.setPosition(position); itemModel.setPosition(position);
holder.itemView.setTag(itemModel); holder.itemView.setTag(itemModel);
holder.itemView.setOnClickListener(clickListener);
holder.itemView.setOnLongClickListener(longClickListener);
holder.itemView.setOnClickListener(v -> internalOnItemClickListener.onItemClick(itemModel, position));
holder.itemView.setOnLongClickListener(v -> internalOnLongItemClickListener.onItemLongClick(itemModel, position));
final MediaItemType mediaType = itemModel.getItemType(); final MediaItemType mediaType = itemModel.getItemType();
holder.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE : View.GONE);
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.slider : R.drawable.video); holder.typeIcon.setImageResource(mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? R.drawable.slider : R.drawable.video);
holder.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE); holder.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE);
holder.progressView.setVisibility(View.VISIBLE);
Glide.with(layoutInflater.getContext()).load(itemModel.getDisplayUrl()).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
holder.progressView.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
holder.progressView.setVisibility(View.GONE);
return false;
holder.postImage.setImageURI(itemModel.getDisplayUrl());
} }
}).into(holder.postImage);
}
}
@Override
public int getItemCount() {
return discoverItemModels == null ? 0 : discoverItemModels.size();
} }
} }

13
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java

@ -9,9 +9,6 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.ListAdapter;
import com.bumptech.glide.RequestManager;
import com.google.android.exoplayer2.SimpleExoPlayer;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
@ -27,11 +24,8 @@ import awais.instagrabber.utils.Utils;
public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> { public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> {
private static final String TAG = "FeedAdapter"; private static final String TAG = "FeedAdapter";
// private final static String ellipsize = "… more";
private final RequestManager glide;
private final View.OnClickListener clickListener; private final View.OnClickListener clickListener;
private final MentionClickListener mentionClickListener; private final MentionClickListener mentionClickListener;
public SimpleExoPlayer pagerPlayer;
private final View.OnLongClickListener longClickListener = v -> { private final View.OnLongClickListener longClickListener = v -> {
final Object tag; final Object tag;
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel)
@ -51,11 +45,10 @@ public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder
} }
}; };
public FeedAdapter(final RequestManager glide,
final View.OnClickListener clickListener,
public FeedAdapter(final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) { final MentionClickListener mentionClickListener) {
super(diffCallback); super(diffCallback);
this.glide = glide;
// private final static String ellipsize = "… more";
this.clickListener = clickListener; this.clickListener = clickListener;
this.mentionClickListener = mentionClickListener; this.mentionClickListener = mentionClickListener;
} }
@ -78,7 +71,7 @@ public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder
default: default:
case MEDIA_TYPE_IMAGE: { case MEDIA_TYPE_IMAGE: {
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false); final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
return new FeedPhotoViewHolder(binding, glide, mentionClickListener, clickListener, longClickListener);
return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
} }
} }
} }

58
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java

@ -1,55 +1,51 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.HighlightViewHolder; import awais.instagrabber.adapters.viewholder.HighlightViewHolder;
import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.ProfileModel;
public final class FeedStoriesAdapter extends RecyclerView.Adapter<HighlightViewHolder> {
private final View.OnClickListener clickListener;
private LayoutInflater layoutInflater;
private FeedStoryModel[] feedStoryModels;
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, HighlightViewHolder> {
private final OnFeedStoryClickListener listener;
public FeedStoriesAdapter(final FeedStoryModel[] feedStoryModels, final View.OnClickListener clickListener) {
this.feedStoryModels = feedStoryModels;
this.clickListener = clickListener;
private static final DiffUtil.ItemCallback<FeedStoryModel> diffCallback = new DiffUtil.ItemCallback<FeedStoryModel>() {
@Override
public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
}
@Override
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
}
};
public FeedStoriesAdapter(final OnFeedStoryClickListener listener) {
super(diffCallback);
this.listener = listener;
} }
@NonNull @NonNull
@Override @Override
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false));
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemHighlightBinding binding = ItemHighlightBinding.inflate(layoutInflater, parent, false);
return new HighlightViewHolder(binding);
} }
@Override @Override
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) {
final FeedStoryModel feedStoryModel = feedStoryModels[position];
if (feedStoryModel != null) {
holder.itemView.setTag(feedStoryModel);
holder.itemView.setOnClickListener(clickListener);
final ProfileModel profileModel = feedStoryModel.getProfileModel();
holder.title.setText(profileModel.getUsername());
holder.icon.setImageURI(profileModel.getSdProfilePic());
holder.icon.setAlpha(feedStoryModel.getFullyRead() ? 0.5F : 1.0F);
holder.title.setAlpha(feedStoryModel.getFullyRead() ? 0.5F : 1.0F);
}
final FeedStoryModel model = getItem(position);
holder.bind(model, position, listener);
} }
public void setData(final FeedStoryModel[] feedStoryModels) {
this.feedStoryModels = feedStoryModels;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return feedStoryModels == null ? 0 : feedStoryModels.length;
public interface OnFeedStoryClickListener {
void onFeedStoryClick(FeedStoryModel model, int position);
} }
} }

17
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java

@ -27,18 +27,19 @@ public final class HighlightsAdapter extends RecyclerView.Adapter<HighlightViewH
@Override @Override
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false));
// return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false));
return null;
} }
@Override @Override
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) {
final HighlightModel highlightModel = highlightModels[position];
if (highlightModel != null) {
holder.itemView.setTag(highlightModel);
holder.itemView.setOnClickListener(clickListener);
holder.title.setText(highlightModel.getTitle());
Glide.with(holder.itemView).load(highlightModel.getThumbnailUrl()).into(holder.icon);
}
// final HighlightModel highlightModel = highlightModels[position];
// if (highlightModel != null) {
// holder.itemView.setTag(highlightModel);
// holder.itemView.setOnClickListener(clickListener);
// holder.title.setText(highlightModel.getTitle());
// Glide.with(holder.itemView).load(highlightModel.getThumbnailUrl()).into(holder.icon);
// }
} }
public void setData(final HighlightModel[] highlightModels) { public void setData(final HighlightModel[] highlightModels) {

106
app/src/main/java/awais/instagrabber/adapters/MultiSelectListAdapter.java

@ -0,0 +1,106 @@
package awais.instagrabber.adapters;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.AsyncDifferConfig;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public abstract class MultiSelectListAdapter<T extends MultiSelectListAdapter.Selectable, VH extends RecyclerView.ViewHolder> extends ListAdapter<T, VH> {
private boolean isSelecting = false;
private OnItemClickListener<T> clickListener;
private OnItemLongClickListener<T> longClickListener;
private final List<T> selectedItems = new ArrayList<>();
protected final OnItemClickListener<T> internalOnItemClickListener = (item, position) -> {
if (isSelecting) {
toggleSelection(item, position);
}
if (clickListener == null) {
return;
}
clickListener.onItemClick(item, position);
};
protected final OnItemLongClickListener<T> internalOnLongItemClickListener = (item, position) -> {
if (!isSelecting) {
isSelecting = true;
}
toggleSelection(item, position);
if (longClickListener == null) {
return true;
}
return longClickListener.onItemLongClick(item, position);
};
protected MultiSelectListAdapter(@NonNull final DiffUtil.ItemCallback<T> diffCallback,
final OnItemClickListener<T> clickListener,
final OnItemLongClickListener<T> longClickListener) {
super(diffCallback);
this.clickListener = clickListener;
this.longClickListener = longClickListener;
}
protected MultiSelectListAdapter(@NonNull final AsyncDifferConfig<T> config,
final OnItemClickListener<T> clickListener,
final OnItemLongClickListener<T> longClickListener) {
super(config);
this.clickListener = clickListener;
this.longClickListener = longClickListener;
}
private void toggleSelection(final T item, final int position) {
if (item == null) {
return;
}
if (selectedItems.size() >= 100) {
// Toast.makeText(mainActivity, R.string.downloader_too_many, Toast.LENGTH_SHORT);
return;
}
if (item.isSelected()) {
item.setSelected(false);
selectedItems.remove(item);
} else {
item.setSelected(true);
selectedItems.add(item);
}
if (selectedItems.size() == 0) {
isSelecting = false;
}
notifyItemChanged(position);
}
public boolean isSelecting() {
return isSelecting;
}
public List<T> getSelectedModels() {
return selectedItems;
}
public void clearSelection() {
for (final T item : selectedItems) {
item.setSelected(false);
}
selectedItems.clear();
isSelecting = false;
notifyDataSetChanged();
}
public interface Selectable {
boolean isSelected();
void setSelected(boolean selected);
}
public interface OnItemClickListener<T> {
void onItemClick(T item, int position);
}
public interface OnItemLongClickListener<T> {
boolean onItemLongClick(T item, int position);
}
}

89
app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java

@ -1,92 +1,45 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DiffUtil;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.PostViewHolder; import awais.instagrabber.adapters.viewholder.PostViewHolder;
import awais.instagrabber.databinding.ItemPostBinding;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.MediaItemType;
public final class PostsAdapter extends RecyclerView.Adapter<PostViewHolder> {
private final ArrayList<PostModel> postModels;
private final View.OnClickListener clickListener;
private final View.OnLongClickListener longClickListener;
private LayoutInflater layoutInflater;
public boolean isSelecting = false;
public PostsAdapter(final ArrayList<PostModel> postModels, final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
this.postModels = postModels;
this.clickListener = clickListener;
this.longClickListener = longClickListener;
}
public final class PostsAdapter extends MultiSelectListAdapter<PostModel, PostViewHolder> {
@NonNull
private static final DiffUtil.ItemCallback<PostModel> diffCallback = new DiffUtil.ItemCallback<PostModel>() {
@Override @Override
public PostViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new PostViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false));
public boolean areItemsTheSame(@NonNull final PostModel oldItem, @NonNull final PostModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
} }
@Override @Override
public void onBindViewHolder(@NonNull final PostViewHolder holder, final int position) {
final PostModel postModel = postModels.get(position);
if (postModel != null) {
postModel.setPosition(position);
holder.itemView.setTag(postModel);
holder.itemView.setOnClickListener(clickListener);
holder.itemView.setOnLongClickListener(longClickListener);
final MediaItemType itemType = postModel.getItemType();
final boolean isSlider = itemType == MediaItemType.MEDIA_TYPE_SLIDER;
holder.isDownloaded.setVisibility(postModel.isDownloaded() ? View.VISIBLE : View.GONE);
holder.typeIcon.setVisibility(itemType == MediaItemType.MEDIA_TYPE_VIDEO || isSlider ? View.VISIBLE : View.GONE);
holder.typeIcon.setImageResource(isSlider ? R.drawable.slider : R.drawable.video);
holder.selectedView.setVisibility(postModel.isSelected() ? View.VISIBLE : View.GONE);
holder.progressView.setVisibility(View.VISIBLE);
final RequestManager glideRequestManager = Glide.with(holder.postImage);
public boolean areContentsTheSame(@NonNull final PostModel oldItem, @NonNull final PostModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
glideRequestManager.load(postModel.getThumbnailUrl()).listener(new RequestListener<Drawable>() {
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
holder.progressView.setVisibility(View.GONE);
return false;
public PostsAdapter(final OnItemClickListener<PostModel> clickListener,
final OnItemLongClickListener<PostModel> longClickListener) {
super(diffCallback, clickListener, longClickListener);
} }
@NonNull
@Override @Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
holder.progressView.setVisibility(View.GONE);
// glideRequestManager.load(postModel.getDisplayUrl()).into(holder.postImage);
return false;
}
}).into(holder.postImage);
}
public PostViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemPostBinding binding = ItemPostBinding.inflate(layoutInflater, parent, false);
return new PostViewHolder(binding);
} }
@Override @Override
public int getItemCount() {
return postModels == null ? 0 : postModels.size();
public void onBindViewHolder(@NonNull final PostViewHolder holder, final int position) {
final PostModel postModel = getItem(position);
holder.bind(postModel, position, internalOnItemClickListener, internalOnLongItemClickListener);
} }
} }

104
app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java

@ -1,84 +1,80 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.content.Context;
import android.content.res.Resources;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemStoryBinding;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
public final class StoriesAdapter extends RecyclerView.Adapter<StoriesAdapter.StoryViewHolder> {
private final View.OnClickListener clickListener;
private LayoutInflater layoutInflater;
private StoryModel[] storyModels;
private Resources resources;
private int width, height;
public StoriesAdapter(final StoryModel[] storyModels, final View.OnClickListener clickListener) {
this.storyModels = storyModels;
this.clickListener = clickListener;
}
public final class StoriesAdapter extends ListAdapter<StoryModel, StoriesAdapter.StoryViewHolder> {
private final OnItemClickListener onItemClickListener;
@NonNull
private static final DiffUtil.ItemCallback<StoryModel> diffCallback = new DiffUtil.ItemCallback<StoryModel>() {
@Override @Override
public StoryViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final Context context = parent.getContext();
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
if (resources == null) resources = context.getResources();
height = Math.round(resources.getDimension(R.dimen.story_item_height));
width = Math.round(resources.getDimension(R.dimen.story_item_width));
return new StoryViewHolder(layoutInflater.inflate(R.layout.item_story, parent, false));
public boolean areItemsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
} }
@Override @Override
public void onBindViewHolder(@NonNull final StoryViewHolder holder, final int position) {
final StoryModel storyModel = storyModels[position];
if (storyModel != null) {
storyModel.setPosition(position);
holder.itemView.setTag(storyModel);
holder.itemView.setOnClickListener(clickListener);
holder.selectedView.setVisibility(storyModel.isCurrentSlide() ? View.VISIBLE : View.GONE);
Glide.with(holder.itemView).load(storyModel.getStoryUrl())
.apply(new RequestOptions().override(width, height))
.into(holder.icon);
}
public boolean areContentsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
} }
};
public void setData(final StoryModel[] storyModels) {
this.storyModels = storyModels;
notifyDataSetChanged();
public StoriesAdapter(final OnItemClickListener onItemClickListener) {
super(diffCallback);
this.onItemClickListener = onItemClickListener;
} }
public StoryModel getItemAt(final int position) {
return storyModels == null ? null : storyModels[position];
@NonNull
@Override
public StoryViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemStoryBinding binding = ItemStoryBinding.inflate(layoutInflater, parent, false);
return new StoryViewHolder(binding);
} }
@Override @Override
public int getItemCount() {
return storyModels == null ? 0 : storyModels.length;
public void onBindViewHolder(@NonNull final StoryViewHolder holder, final int position) {
final StoryModel storyModel = getItem(position);
holder.bind(storyModel, position, onItemClickListener);
} }
public final static class StoryViewHolder extends RecyclerView.ViewHolder { public final static class StoryViewHolder extends RecyclerView.ViewHolder {
public final ImageView icon, selectedView;
private final ItemStoryBinding binding;
public StoryViewHolder(final ItemStoryBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public StoryViewHolder(@NonNull final View itemView) {
super(itemView);
selectedView = itemView.findViewById(R.id.selectedView);
icon = itemView.findViewById(R.id.icon);
public void bind(final StoryModel model,
final int position,
final OnItemClickListener clickListener) {
if (model == null) return;
model.setPosition(position);
itemView.setTag(model);
itemView.setOnClickListener(v -> {
if (clickListener == null) return;
clickListener.onItemClick(model, position);
});
binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
binding.icon.setImageURI(model.getStoryUrl());
// Glide.with(itemView).load(model.getStoryUrl())
// .apply(new RequestOptions().override(width, height))
// .into(holder.icon);
} }
} }
public interface OnItemClickListener {
void onItemClick(StoryModel storyModel, int position);
}
} }

10
app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java

@ -6,17 +6,21 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import awais.instagrabber.R; import awais.instagrabber.R;
public final class DiscoverViewHolder extends RecyclerView.ViewHolder { public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
public final ImageView postImage, typeIcon;
public final View selectedView, progressView;
public final SimpleDraweeView postImage;
public final ImageView typeIcon;
public final View selectedView;
// public final View progressView;
public DiscoverViewHolder(@NonNull final View itemView) { public DiscoverViewHolder(@NonNull final View itemView) {
super(itemView); super(itemView);
typeIcon = itemView.findViewById(R.id.typeIcon); typeIcon = itemView.findViewById(R.id.typeIcon);
postImage = itemView.findViewById(R.id.postImage); postImage = itemView.findViewById(R.id.postImage);
selectedView = itemView.findViewById(R.id.selectedView); selectedView = itemView.findViewById(R.id.selectedView);
progressView = itemView.findViewById(R.id.progressView);
// progressView = itemView.findViewById(R.id.progressView);
} }
} }

37
app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java

@ -1,22 +1,33 @@
package awais.instagrabber.adapters.viewholder; package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.CircularImageView;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.ProfileModel;
public final class HighlightViewHolder extends RecyclerView.ViewHolder { public final class HighlightViewHolder extends RecyclerView.ViewHolder {
public final CircularImageView icon;
public final TextView title;
public HighlightViewHolder(@NonNull final View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.icon);
title = itemView.findViewById(R.id.title);
private final ItemHighlightBinding binding;
public HighlightViewHolder(final ItemHighlightBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final FeedStoryModel model,
final int position,
final FeedStoriesAdapter.OnFeedStoryClickListener listener) {
if (model == null) return;
binding.getRoot().setOnClickListener(v -> {
if (listener == null) return;
listener.onFeedStoryClick(model, position);
});
final ProfileModel profileModel = model.getProfileModel();
binding.title.setText(profileModel.getUsername());
binding.title.setAlpha(model.getFullyRead() ? 0.5F : 1.0F);
binding.icon.setImageURI(profileModel.getSdProfilePic());
binding.icon.setAlpha(model.getFullyRead() ? 0.5F : 1.0F);
} }
} }

42
app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java

@ -1,23 +1,43 @@
package awais.instagrabber.adapters.viewholder; package awais.instagrabber.adapters.viewholder;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.MultiSelectListAdapter.OnItemClickListener;
import awais.instagrabber.adapters.MultiSelectListAdapter.OnItemLongClickListener;
import awais.instagrabber.databinding.ItemPostBinding;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.MediaItemType;
public final class PostViewHolder extends RecyclerView.ViewHolder { public final class PostViewHolder extends RecyclerView.ViewHolder {
public final ImageView postImage, typeIcon;
public final View selectedView, progressView, isDownloaded;
public PostViewHolder(@NonNull final View itemView) {
super(itemView);
typeIcon = itemView.findViewById(R.id.typeIcon);
postImage = itemView.findViewById(R.id.postImage);
isDownloaded = itemView.findViewById(R.id.isDownloaded);
selectedView = itemView.findViewById(R.id.selectedView);
progressView = itemView.findViewById(R.id.progressView);
private final ItemPostBinding binding;
public PostViewHolder(@NonNull final ItemPostBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final PostModel postModel,
final int position,
final OnItemClickListener<PostModel> clickListener,
final OnItemLongClickListener<PostModel> longClickListener) {
if (postModel == null) return;
postModel.setPosition(position);
itemView.setOnClickListener(v -> clickListener.onItemClick(postModel, position));
itemView.setOnLongClickListener(v -> longClickListener.onItemLongClick(postModel, position));
final MediaItemType itemType = postModel.getItemType();
final boolean isSlider = itemType == MediaItemType.MEDIA_TYPE_SLIDER;
binding.isDownloaded.setVisibility(postModel.isDownloaded() ? View.VISIBLE : View.GONE);
binding.typeIcon.setVisibility(itemType == MediaItemType.MEDIA_TYPE_VIDEO || isSlider ? View.VISIBLE : View.GONE);
binding.typeIcon.setImageResource(isSlider ? R.drawable.slider : R.drawable.video);
binding.selectedView.setVisibility(postModel.isSelected() ? View.VISIBLE : View.GONE);
binding.postImage.setImageURI(postModel.getThumbnailUrl());
} }
} }

7
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java

@ -11,8 +11,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestManager;
import org.json.JSONObject; import org.json.JSONObject;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -31,8 +29,6 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
private final ItemFeedBottomBinding bottomBinding; private final ItemFeedBottomBinding bottomBinding;
private final MentionClickListener mentionClickListener; private final MentionClickListener mentionClickListener;
boolean captionExpanded = false;
public FeedItemViewHolder(@NonNull final View root, public FeedItemViewHolder(@NonNull final View root,
final ItemFeedTopBinding topBinding, final ItemFeedTopBinding topBinding,
final ItemFeedBottomBinding bottomBinding, final ItemFeedBottomBinding bottomBinding,
@ -64,9 +60,6 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
bottomBinding.btnComments.setTag(feedModel); bottomBinding.btnComments.setTag(feedModel);
final ProfileModel profileModel = feedModel.getProfileModel(); final ProfileModel profileModel = feedModel.getProfileModel();
if (profileModel != null) { if (profileModel != null) {
// glide.load(profileModel.getSdProfilePic())
// .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
// .into(topBinding.ivProfilePic);
topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic()); topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
final int titleLen = profileModel.getUsername().length() + 1; final int titleLen = profileModel.getUsername().length() + 1;
final SpannableString spannableString = new SpannableString("@" + profileModel.getUsername()); final SpannableString spannableString = new SpannableString("@" + profileModel.getUsername());

16
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java

@ -1,14 +1,11 @@
package awais.instagrabber.adapters.viewholder.feed; package awais.instagrabber.adapters.viewholder.feed;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri; import android.net.Uri;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.bumptech.glide.RequestManager;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy; import com.facebook.drawee.generic.GenericDraweeHierarchy;
@ -25,22 +22,15 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedPhotoViewHolder"; private static final String TAG = "FeedPhotoViewHolder";
private final ItemFeedPhotoBinding binding; private final ItemFeedPhotoBinding binding;
private final RequestManager glide;
private final ColorDrawable drawable;
// private final PipelineDraweeControllerBuilder controllerBuilder;
// private final CustomTarget<Bitmap> customTarget;
public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding, public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding,
final RequestManager glide,
final MentionClickListener mentionClickListener, final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener, final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) { final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener); super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding; this.binding = binding;
this.glide = glide;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE); binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE); binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
drawable = new ColorDrawable(Color.WHITE);
binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false); binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources()) final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
@ -50,15 +40,13 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
@Override @Override
public void bindItem(final FeedModel feedModel) { public void bindItem(final FeedModel feedModel) {
// glide.clear(customTarget);
if (feedModel == null) { if (feedModel == null) {
return; return;
} }
final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels; final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = Utils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.imageViewer.requestLayout(); binding.imageViewer.requestLayout();
final String thumbnailUrl = feedModel.getThumbnailUrl(); final String thumbnailUrl = feedModel.getThumbnailUrl();
String url = feedModel.getDisplayUrl(); String url = feedModel.getDisplayUrl();

4
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java

@ -151,14 +151,14 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
final SimpleExoPlayer player = (SimpleExoPlayer) tag; final SimpleExoPlayer player = (SimpleExoPlayer) tag;
final float intVol = player.getVolume() == 0f ? 1f : 0f; final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol); player.setVolume(intVol);
binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute);
binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
Utils.sessionVolumeFull = intVol == 1f; Utils.sessionVolumeFull = intVol == 1f;
}; };
final ViewerPostModel firstItem = sliderItems[0]; final ViewerPostModel firstItem = sliderItems[0];
if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE); binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
} }
binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.mute : R.drawable.vol);
binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.ic_volume_off_24 : R.drawable.ic_volume_up_24);
binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener); binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener);
} }

7
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java

@ -74,8 +74,9 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
private void setThumbnail(final FeedModel feedModel) { private void setThumbnail(final FeedModel feedModel) {
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams();
layoutParams.width = feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight();
final int requiredWidth = Utils.displayMetrics.widthPixels;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.thumbnailParent.requestLayout(); binding.thumbnailParent.requestLayout();
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl())) final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl()))
.setProgressiveRenderingEnabled(true) .setProgressiveRenderingEnabled(true)
@ -131,7 +132,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
} }
private void setMuteIcon(final float vol) { private void setMuteIcon(final float vol) {
binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
} }
public FeedModel getCurrentFeedModel() { public FeedModel getCurrentFeedModel() {

81
app/src/main/java/awais/instagrabber/asyncs/CommentAction.java

@ -0,0 +1,81 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class CommentAction extends AsyncTask<String, Void, String> {
private static final String TAG = "CommentAction";
private final String cookie;
private final StoryModel storyModel;
private final OnTaskCompleteListener onTaskCompleteListener;
public CommentAction(final String cookie, final StoryModel storyModel, final OnTaskCompleteListener onTaskCompleteListener) {
this.cookie = cookie;
this.storyModel = storyModel;
this.onTaskCompleteListener = onTaskCompleteListener;
}
protected String doInBackground(String... rawAction) {
final String action = rawAction[0];
final String url = "https://i.instagram.com/api/v1/direct_v2/create_group_thread/";
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setUseCaches(false);
final String urlParameters = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+ "\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+ "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+ "\",\"recipient_users\":\"[" + storyModel.getUserId() // <- string of array of number (not joking)
+ "]\"}");
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (urlParameters != null) {
urlConnection.setRequestProperty("Content-Length", "" + urlParameters.getBytes().length);
}
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return new JSONObject(Utils.readFromConnection(urlConnection)).getString("thread_id");
}
} catch (Throwable ex) {
Log.e(TAG, "reply (CT): " + ex);
// Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(final String threadId) {
if (threadId == null || onTaskCompleteListener == null) {
return;
}
onTaskCompleteListener.onTaskComplete(threadId);
}
public interface OnTaskCompleteListener {
void onTaskComplete(final String threadId);
}
}

8
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java

@ -25,6 +25,7 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> { public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
private static final String TAG = "PostsFetcher";
private final String endCursor; private final String endCursor;
private final String id; private final String id;
private final FetchListener<PostModel[]> fetchListener; private final FetchListener<PostModel[]> fetchListener;
@ -87,7 +88,8 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
if (!Utils.isEmpty(customPath)) customDir = new File(customPath); if (!Utils.isEmpty(customPath)) customDir = new File(customPath);
} }
final JSONObject mediaPosts = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
final JSONObject mediaPosts = new JSONObject(Utils.readFromConnection(conn))
.getJSONObject("data")
.getJSONObject(isHashTag ? Constants.EXTRAS_HASHTAG : .getJSONObject(isHashTag ? Constants.EXTRAS_HASHTAG :
(isLocation ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_USER)) (isLocation ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_USER))
.getJSONObject(isHashTag ? "edge_hashtag_to_media" : .getJSONObject(isHashTag ? "edge_hashtag_to_media" :
@ -131,7 +133,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
Utils.checkExistence(downloadDir, customDir, isSlider, models[i]); Utils.checkExistence(downloadDir, customDir, isSlider, models[i]);
} }
if (models[models.length - 1] != null)
if (models.length != 0 && models[models.length - 1] != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor); models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models; result = models;
@ -141,7 +143,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return result; return result;

88
app/src/main/java/awais/instagrabber/asyncs/QuizAction.java

@ -0,0 +1,88 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class QuizAction extends AsyncTask<Integer, Void, Integer> {
private static final String TAG = "QuizAction";
private final StoryModel storyModel;
private final QuizModel quizModel;
private final String cookie;
private final OnTaskCompleteListener onTaskCompleteListener;
public QuizAction(final StoryModel storyModel,
final QuizModel quizModel,
final String cookie,
final OnTaskCompleteListener onTaskCompleteListener) {
this.storyModel = storyModel;
this.quizModel = quizModel;
this.cookie = cookie;
this.onTaskCompleteListener = onTaskCompleteListener;
}
protected Integer doInBackground(Integer... rawChoice) {
int choice = rawChoice[0];
final String url = "https://i.instagram.com/api/v1/media/" + storyModel.getStoryMediaId().split("_")[0] + "/" + quizModel.getId() + "/story_quiz_answer/";
HttpURLConnection urlConnection = null;
try {
JSONObject ogBody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString()
+ "\",\"mutation_token\":\"" + UUID.randomUUID().toString()
+ "\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+ "\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+ "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+ "\"}");
ogBody.put("answer", String.valueOf(choice));
String urlParameters = Utils.sign(ogBody.toString());
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (urlParameters != null) {
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
}
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
Log.d(TAG, "quiz: " + url + " " + cookie + " " + urlParameters);
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return choice;
}
} catch (Throwable ex) {
Log.e(TAG, "quiz: " + ex);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return -1;
}
@Override
protected void onPostExecute(final Integer choice) {
if (onTaskCompleteListener == null || choice == null) return;
onTaskCompleteListener.onTaskComplete(choice);
}
public interface OnTaskCompleteListener {
void onTaskComplete(final int choice);
}
}

87
app/src/main/java/awais/instagrabber/asyncs/RespondAction.java

@ -0,0 +1,87 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class RespondAction extends AsyncTask<String, Void, Boolean> {
private final StoryModel storyModel;
private final QuestionModel questionModel;
private final String cookie;
private final OnTaskCompleteListener onTaskCompleteListener;
public RespondAction(final StoryModel storyModel,
final QuestionModel questionModel,
final String cookie,
final OnTaskCompleteListener onTaskCompleteListener) {
this.storyModel = storyModel;
this.questionModel = questionModel;
this.cookie = cookie;
this.onTaskCompleteListener = onTaskCompleteListener;
}
protected Boolean doInBackground(String... rawChoice) {
final String url = "https://i.instagram.com/api/v1/media/"
+ storyModel.getStoryMediaId().split("_")[0] + "/" + questionModel.getId() + "/story_question_response/";
HttpURLConnection urlConnection = null;
try {
JSONObject ogbody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString()
+ "\",\"mutation_token\":\"" + UUID.randomUUID().toString()
+ "\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+ "\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+ "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+ "\"}");
String choice = rawChoice[0].replaceAll("\"", ("\\\""));
ogbody.put("response", choice);
String urlParameters = Utils.sign(ogbody.toString());
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return true;
}
} catch (Throwable ex) {
Log.e("austin_debug", "respond: " + ex);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(final Boolean ok) {
if (onTaskCompleteListener == null) return;
onTaskCompleteListener.onTaskComplete(ok);
}
public interface OnTaskCompleteListener {
void onTaskComplete(final boolean result);
}
}

51
app/src/main/java/awais/instagrabber/asyncs/SeenAction.java

@ -0,0 +1,51 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Utils;
public class SeenAction extends AsyncTask<Void, Void, Void> {
private static final String TAG = "SeenAction";
private final String cookie;
private final StoryModel storyModel;
public SeenAction(final String cookie, final StoryModel storyModel) {
this.cookie = cookie;
this.storyModel = storyModel;
}
protected Void doInBackground(Void... voids) {
final String url = "https://www.instagram.com/stories/reel/seen";
try {
String urlParameters = "reelMediaId=" + storyModel.getStoryMediaId().split("_")[0]
+ "&reelMediaOwnerId=" + storyModel.getUserId()
+ "&reelId=" + storyModel.getUserId()
+ "&reelMediaTakenAt=" + storyModel.getTimestamp()
+ "&viewSeenAt=" + storyModel.getTimestamp();
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
urlConnection.connect();
Log.d(TAG, urlConnection.getResponseCode() + " " + Utils.readFromConnection(urlConnection));
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e(TAG, "Error", ex);
}
return null;
}
}

69
app/src/main/java/awais/instagrabber/asyncs/VoteAction.java

@ -0,0 +1,69 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.utils.Constants;
public class VoteAction extends AsyncTask<Integer, Void, Integer> {
private static final String TAG = "VoteAction";
private final StoryModel storyModel;
private final PollModel pollModel;
private final String cookie;
private final OnTaskCompleteListener onTaskCompleteListener;
public VoteAction(final StoryModel storyModel,
final PollModel pollModel,
final String cookie,
final OnTaskCompleteListener onTaskCompleteListener) {
this.storyModel = storyModel;
this.pollModel = pollModel;
this.cookie = cookie;
this.onTaskCompleteListener = onTaskCompleteListener;
}
protected Integer doInBackground(Integer... rawChoice) {
int choice = rawChoice[0];
final String url = "https://www.instagram.com/media/" + storyModel.getStoryMediaId().split("_")[0] + "/" + pollModel.getId() + "/story_poll_vote/";
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "6");
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes("vote=" + choice);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return choice;
}
urlConnection.disconnect();
} catch (Exception ex) {
Log.e(TAG, "Error", ex);
}
return -1;
}
@Override
protected void onPostExecute(final Integer result) {
if (result == null || onTaskCompleteListener == null) return;
onTaskCompleteListener.onTaskComplete(result);
}
public interface OnTaskCompleteListener {
void onTaskComplete(final int choice);
}
}

51
app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java

@ -25,11 +25,18 @@ import static awais.instagrabber.utils.Utils.logCollector;
public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> { public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> {
private final String id; private final String id;
private String username; private String username;
private final boolean isLoc, isHashtag, storiesig, highlight;
private final boolean isLoc;
private final boolean isHashtag;
private final boolean storiesig;
private final boolean highlight;
private final FetchListener<StoryModel[]> fetchListener; private final FetchListener<StoryModel[]> fetchListener;
public iStoryStatusFetcher(final String id, final String username, final boolean isLoc,
final boolean isHashtag, final boolean storiesig, final boolean highlight,
public iStoryStatusFetcher(final String id,
final String username,
final boolean isLoc,
final boolean isHashtag,
final boolean storiesig,
final boolean highlight,
final FetchListener<StoryModel[]> fetchListener) { final FetchListener<StoryModel[]> fetchListener) {
this.id = id; this.id = id;
this.username = username; this.username = username;
@ -43,9 +50,35 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
@Override @Override
protected StoryModel[] doInBackground(final Void... voids) { protected StoryModel[] doInBackground(final Void... voids) {
StoryModel[] result = null; StoryModel[] result = null;
final String url = "https://" + (storiesig ? "storiesig" : "i.instagram") + ".com/api/v1/"
+ (isLoc ? "locations/" : (isHashtag ? "tags/" : (highlight ? "feed/reels_media?user_ids=" : "feed/user/")))
+ id.replace(":", "%3A") + (highlight ? "" : (storiesig ? "/reel_media/" : "/story/"));
final String userId = id.replace(":", "%3A");
final StringBuilder builder = new StringBuilder();
builder.append("https://");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
if (isLoc) {
builder.append("locations/");
}
if (isHashtag) {
builder.append("tags/");
}
if (highlight) {
builder.append("feed/reels_media?user_ids=");
} else {
builder.append("feed/user/");
}
builder.append(userId);
if (!highlight) {
if (storiesig) {
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
}
final String url = builder.toString();
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(true); conn.setInstanceFollowRedirects(true);
@ -55,10 +88,12 @@ public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONObject data = new JSONObject(Utils.readFromConnection(conn)); JSONObject data = new JSONObject(Utils.readFromConnection(conn));
if (!storiesig && !highlight) data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
if (!storiesig && !highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
else if (highlight) data = data.getJSONObject("reels").optJSONObject(id); else if (highlight) data = data.getJSONObject("reels").optJSONObject(id);
if (username == null && !isLoc && !isHashtag) username = data.getJSONObject("user").getString("username");
if (username == null && !isLoc && !isHashtag)
username = data.getJSONObject("user").getString("username");
JSONArray media; JSONArray media;
if (data != null && (media = data.optJSONArray("items")) != null if (data != null && (media = data.optJSONArray("items")) != null

71
app/src/main/java/awais/instagrabber/customviews/PrimaryActionModeCallback.java

@ -0,0 +1,71 @@
package awais.instagrabber.customviews;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
public class PrimaryActionModeCallback implements ActionMode.Callback {
private ActionMode mode;
private int menuRes;
private final Callbacks callbacks;
public PrimaryActionModeCallback(final int menuRes, final Callbacks callbacks) {
this.menuRes = menuRes;
this.callbacks = callbacks;
}
@Override
public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
this.mode = mode;
mode.getMenuInflater().inflate(menuRes, menu);
if (callbacks != null) {
callbacks.onCreate(mode, menu);
}
return true;
}
@Override
public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (callbacks != null) {
return callbacks.onActionItemClicked(mode, item);
}
return false;
}
@Override
public void onDestroyActionMode(final ActionMode mode) {
if (callbacks != null) {
callbacks.onDestroy(mode);
}
this.mode = null;
}
public abstract static class CallbacksHelper implements Callbacks {
public void onCreate(final ActionMode mode, final Menu menu) {
}
@Override
public void onDestroy(final ActionMode mode) {
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
return false;
}
}
public interface Callbacks {
void onCreate(final ActionMode mode, final Menu menu);
void onDestroy(final ActionMode mode);
boolean onActionItemClicked(final ActionMode mode, final MenuItem item);
}
}

2
app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java

@ -33,6 +33,8 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.interfaces.DraweeController; import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeView; import com.facebook.drawee.view.DraweeView;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
/** /**
* DraweeView that has zoomable capabilities. * DraweeView that has zoomable capabilities.

33
app/src/main/java/awais/instagrabber/customviews/helpers/CustomHideBottomViewOnScrollBehavior.java

@ -0,0 +1,33 @@
package awais.instagrabber.customviews.helpers;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
import com.google.android.material.bottomnavigation.BottomNavigationView;
public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScrollBehavior<BottomNavigationView> {
private static final String TAG = "CustomHideBottomView";
@Override
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final BottomNavigationView child,
@NonNull final View directTargetChild,
@NonNull final View target,
final int nestedScrollAxes,
final int type) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
@Override
public void onNestedPreScroll(@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final BottomNavigationView child, @NonNull final View target, final int dx, final int dy, @NonNull final int[] consumed, final int type) {
if (dy > 0) {
slideDown(child);
} else if (dy < 0) {
slideUp(child);
}
}
}

154
app/src/main/java/awais/instagrabber/customviews/helpers/NestedCoordinatorLayout.java

@ -0,0 +1,154 @@
package awais.instagrabber.customviews.helpers;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.NestedScrollingChild;
import androidx.core.view.NestedScrollingChildHelper;
public class NestedCoordinatorLayout extends CoordinatorLayout implements NestedScrollingChild {
private NestedScrollingChildHelper mChildHelper;
public NestedCoordinatorLayout(Context context) {
super(context);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed, int type) {
int[][] tConsumed = new int[2][2];
super.onNestedPreScroll(target, dx, dy, consumed, type);
dispatchNestedPreScroll(dx, dy, tConsumed[1], null);
consumed[0] = tConsumed[0][0] + tConsumed[1][0];
consumed[1] = tConsumed[0][1] + tConsumed[1][1];
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
}
@Override
public void onStopNestedScroll(View target, int type) {
/* Disable the scrolling behavior of our own children */
super.onStopNestedScroll(target, type);
/* Disable the scrolling behavior of the parent's other children */
stopNestedScroll();
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes, int type) {
/* Enable the scrolling behavior of our own children */
boolean tHandled = super.onStartNestedScroll(child, target, nestedScrollAxes, type);
/* Enable the scrolling behavior of the parent's other children */
return startNestedScroll(nestedScrollAxes) || tHandled;
}
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
/* Enable the scrolling behavior of our own children */
boolean tHandled = super.onStartNestedScroll(child, target, nestedScrollAxes);
/* Enable the scrolling behavior of the parent's other children */
return startNestedScroll(nestedScrollAxes) || tHandled;
}
@Override
public void onStopNestedScroll(View target) {
/* Disable the scrolling behavior of our own children */
super.onStopNestedScroll(target);
/* Disable the scrolling behavior of the parent's other children */
stopNestedScroll();
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
int[][] tConsumed = new int[2][2];
super.onNestedPreScroll(target, dx, dy, tConsumed[0]);
dispatchNestedPreScroll(dx, dy, tConsumed[1], null);
consumed[0] = tConsumed[0][0] + tConsumed[1][0];
consumed[1] = tConsumed[0][1] + tConsumed[1][1];
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, null);
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
boolean tHandled = super.onNestedPreFling(target, velocityX, velocityY);
return dispatchNestedPreFling(velocityX, velocityY) || tHandled;
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
boolean tHandled = super.onNestedFling(target, velocityX, velocityY, consumed);
return dispatchNestedFling(velocityX, velocityY, consumed) || tHandled;
}
@Override
public boolean isNestedScrollingEnabled() {
return mChildHelper.isNestedScrollingEnabled();
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
}

5
app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java

@ -22,6 +22,7 @@ import java.util.ArrayList;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.MainActivityBackup;
import awais.instagrabber.adapters.SimpleAdapter; import awais.instagrabber.adapters.SimpleAdapter;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DataBox;
@ -112,8 +113,8 @@ public final class QuickAccessDialog extends BottomSheetDialogFragment implement
else Utils.showImportExportDialog(v.getContext()); else Utils.showImportExportDialog(v.getContext());
} else if (tag instanceof DataBox.FavoriteModel) { } else if (tag instanceof DataBox.FavoriteModel) {
if (MainActivity.scanHack != null) {
MainActivity.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery());
if (MainActivityBackup.scanHack != null) {
MainActivityBackup.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery());
dismiss(); dismiss();
} }

46
app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java

@ -62,24 +62,24 @@ public final class MultiDirectDialog extends BaseLanguageActivity {
postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp(), postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp(),
postModel.getLike(), postModel.getBookmark(), postModel.getLikes())); postModel.getLike(), postModel.getBookmark(), postModel.getLikes()));
postsAdapter = new PostsAdapter(models, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else {
Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(postModel));
finish();
}
}
}, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
return true;
});
// postsAdapter = new PostsAdapter(models, v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// final PostModel postModel = (PostModel) tag;
// if (postsAdapter.isSelecting) toggleSelection(postModel);
// else {
// Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(postModel));
// finish();
// }
// }
// }, v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// postsAdapter.isSelecting = true;
// toggleSelection((PostModel) tag);
// }
// return true;
// });
recyclerView.setAdapter(postsAdapter); recyclerView.setAdapter(postsAdapter);
} }
@ -109,10 +109,10 @@ public final class MultiDirectDialog extends BaseLanguageActivity {
} }
private void notifyAdapter(final PostModel postModel) { private void notifyAdapter(final PostModel postModel) {
if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (btnDownload != null) btnDownload.setVisible(postsAdapter.isSelecting);
// if (selectedItems.size() < 1) postsAdapter.isSelecting = false;
// if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
// else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
//
// if (btnDownload != null) btnDownload.setVisible(postsAdapter.isSelecting);
} }
} }

673
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java

@ -0,0 +1,673 @@
package awais.instagrabber.fragments;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
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.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.CommentAction;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction;
import awais.instagrabber.asyncs.VoteAction;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.ActivityStoryViewerBinding;
import awais.instagrabber.fragments.main.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.fragments.main.viewmodels.StoriesViewModel;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.services.ServiceCallback;
import awais.instagrabber.services.StoriesService;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class StoryViewerFragment extends Fragment {
private static final String TAG = "StoryViewerFragment";
private AppCompatActivity fragmentActivity;
private View root;
private ActivityStoryViewerBinding binding;
private String currentStoryUsername;
private StoriesAdapter storiesAdapter;
private SwipeEvent swipeEvent;
private GestureDetectorCompat gestureDetector;
private StoriesService storiesService;
private List<FeedStoryModel> feedStoryModels;
private StoryModel currentStory;
private int slidePos;
private int lastSlidePos;
private String url;
private PollModel poll;
private QuestionModel question;
private String[] mentions;
private QuizModel quiz;
private MenuItem menuDownload;
private MenuItem menuDm;
private SimpleExoPlayer player;
private boolean isHashtag;
private String highlight;
private boolean fetching = false;
private int currentFeedStoryIndex;
private StoriesViewModel storiesViewModel;
private String currentStoryMediaId;
private final String cookie = settingsHelper.getString(Constants.COOKIE);
private StoryViewerFragmentArgs fragmentArgs;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) requireActivity();
storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
if (root != null) {
return root;
}
binding = ActivityStoryViewerBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater menuInflater) {
menuInflater.inflate(R.menu.story_menu, menu);
menuDownload = menu.findItem(R.id.action_download);
menuDm = menu.findItem(R.id.action_dms);
menuDownload.setVisible(false);
menuDm.setVisible(false);
}
@Override
public void onPrepareOptionsMenu(@NonNull final Menu menu) {
// hide menu items from activity
final MenuItem item = menu.findItem(R.id.favourites);
if (item != null) {
item.setVisible(false);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_download:
if (ContextCompat.checkSelfPermission(requireContext(), Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
downloadStory();
else
ActivityCompat.requestPermissions(requireActivity(), Utils.PERMS, 8020);
return true;
case R.id.action_dms:
final EditText input = new EditText(requireContext());
input.setHint(R.string.reply_hint);
new AlertDialog.Builder(requireContext())
.setTitle(R.string.reply_story)
.setView(input)
.setPositiveButton(R.string.ok, (d, w) -> new CommentAction(cookie, currentStory, threadId -> {
try {
final DirectThreadBroadcaster.StoryReplyBroadcastOptions options = new DirectThreadBroadcaster.StoryReplyBroadcastOptions(
input.getText().toString(),
currentStory.getStoryMediaId(),
currentStory.getUserId()
);
final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId);
broadcast.setOnTaskCompleteListener(result -> Toast.makeText(
requireContext(),
result != null ? R.string.answered_story : R.string.downloader_unknown_error,
Toast.LENGTH_SHORT
).show());
broadcast.execute(options);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
}
}).execute())
.setNegativeButton(R.string.cancel, null)
.show();
return true;
}
return false;
}
@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)
downloadStory();
}
@Override
public void onPause() {
super.onPause();
releasePlayer();
}
@Override
public void onDestroy() {
releasePlayer();
// reset subtitle
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setSubtitle(null);
}
super.onDestroy();
}
private void init() {
final FeedStoriesViewModel feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
feedStoryModels = feedStoriesViewModel.getList().getValue();
if (feedStoryModels == null || feedStoryModels.isEmpty() || getArguments() == null) return;
fragmentArgs = StoryViewerFragmentArgs.fromBundle(getArguments());
currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex();
setupStories();
}
private void setupStories() {
storiesViewModel = new ViewModelProvider(this).get(StoriesViewModel.class);
setupListeners();
binding.storiesList.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
storiesAdapter = new StoriesAdapter((model, position) -> {
currentStory = model;
slidePos = position;
refreshStory();
});
binding.storiesList.setAdapter(storiesAdapter);
storiesViewModel.getList().observe(fragmentActivity, storiesAdapter::submitList);
resetView();
}
@SuppressLint("ClickableViewAccessibility")
private void setupListeners() {
swipeEvent = isRightSwipe -> {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
final int storiesLen = storyModels == null ? 0 : storyModels.size();
if (storiesLen <= 0) return;
final boolean hasFeedStories = feedStoryModels != null && !feedStoryModels.isEmpty();
final boolean isLeftSwipe = !isRightSwipe;
final boolean endOfCurrentStories = slidePos + 1 >= storiesLen;
final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe);
if (swipingBeyondCurrentStories && hasFeedStories) {
final int index = currentFeedStoryIndex;
if (settingsHelper.getBoolean(MARK_AS_SEEN)) {
new SeenAction(cookie, currentStory).execute();
}
if ((isRightSwipe && index == 0) || (isLeftSwipe && index == feedStoryModels.size() - 1)) {
Toast.makeText(requireContext(), R.string.no_more_stories, Toast.LENGTH_SHORT).show();
return;
}
final FeedStoryModel feedStoryModel = isRightSwipe
? feedStoryModels.get(index - 1)
: feedStoryModels.size() == index + 1 ? null : feedStoryModels.get(index + 1);
if (feedStoryModel != null) {
if (fetching) {
Toast.makeText(requireContext(), R.string.be_patient, Toast.LENGTH_SHORT).show();
return;
}
fetching = true;
currentFeedStoryIndex = isRightSwipe ? (index - 1) : (index + 1);
resetView();
// new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
// if (result != null && result.length > 0) {
// final Intent newIntent = new Intent(requireContext(), StoryViewer.class)
// .putExtra(Constants.EXTRAS_STORIES, result)
// .putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
// .putExtra(Constants.FEED, storyFeed)
// .putExtra(Constants.FEED_ORDER, isRightSwipe ? (index - 1) : (index + 1));
// newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// startActivity(newIntent);
// } else
// Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
return;
}
if (isRightSwipe) {
if (--slidePos <= 0) {
slidePos = 0;
}
} else if (++slidePos >= storiesLen) {
slidePos = storiesLen - 1;
}
currentStory = storyModels.get(slidePos);
refreshStory();
};
gestureDetector = new GestureDetectorCompat(requireContext(), new SwipeGestureListener(swipeEvent));
binding.playerView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
final GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
final float diffX = e2.getX() - e1.getX();
try {
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
swipeEvent.onSwipe(diffX > 0);
return true;
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners",
new Pair<>("swipeEvent", swipeEvent),
new Pair<>("diffX", diffX));
if (BuildConfig.DEBUG) Log.e(TAG, "Error", e);
}
return false;
}
};
binding.imageViewer.setTapListener(simpleOnGestureListener);
binding.spotify.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(tag.toString()));
startActivity(intent);
}
});
binding.viewStoryPost.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence)
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(tag.toString(), tag.toString().matches("^[\\d]+$"))));
});
final View.OnClickListener storyActionListener = v -> {
final Object tag = v.getTag();
if (tag instanceof PollModel) {
poll = (PollModel) tag;
if (poll.getMyChoice() > -1) {
new AlertDialog.Builder(requireContext()).setTitle(R.string.voted_story_poll)
.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, new String[]{
(poll.getMyChoice() == 0 ? "√ " : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
(poll.getMyChoice() == 1 ? "√ " : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), null)
.setPositiveButton(R.string.ok, null)
.show();
} else {
new AlertDialog.Builder(requireContext())
.setTitle(poll.getQuestion())
.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, new String[]{
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
if (!Utils.isEmpty(cookie))
new VoteAction(currentStory, poll, cookie, choice -> {
if (choice > -1) {
poll.setMyChoice(choice);
Toast.makeText(requireContext(), R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
} else if (tag instanceof QuestionModel) {
question = (QuestionModel) tag;
final EditText input = new EditText(requireContext());
input.setHint(R.string.answer_hint);
new AlertDialog.Builder(requireContext())
.setTitle(question.getQuestion())
.setView(input)
.setPositiveButton(R.string.ok, (d, w) -> new RespondAction(currentStory, question, cookie, result -> {
if (result) {
Toast.makeText(requireContext(), R.string.answered_story, Toast.LENGTH_SHORT).show();
} else
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(input.getText().toString()))
.setNegativeButton(R.string.cancel, null)
.show();
} else if (tag instanceof String[]) {
mentions = (String[]) tag;
new AlertDialog.Builder(requireContext())
.setTitle(R.string.story_mentions)
.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, mentions), (d, w) -> {
// searchUsername(mentions[w]);
})
.setPositiveButton(R.string.cancel, null)
.show();
} else if (tag instanceof QuizModel) {
String[] choices = new String[quiz.getChoices().length];
for (int q = 0; q < choices.length; ++q) {
choices[q] = (quiz.getMyChoice() == q ? "√ " : "") + quiz.getChoices()[q] + " (" + quiz.getCounts()[q] + ")";
}
new AlertDialog.Builder(requireContext())
.setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setAdapter(new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, choices), (d, w) -> {
if (quiz.getMyChoice() == -1 && !Utils.isEmpty(cookie))
new QuizAction(currentStory, quiz, cookie, choice -> {
if (choice > -1) {
quiz.setMyChoice(choice);
Toast.makeText(requireContext(), R.string.answered_story, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
};
binding.poll.setOnClickListener(storyActionListener);
binding.answer.setOnClickListener(storyActionListener);
binding.mention.setOnClickListener(storyActionListener);
binding.quiz.setOnClickListener(storyActionListener);
}
private void resetView() {
slidePos = 0;
lastSlidePos = 0;
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
binding.imageViewer.setController(null);
releasePlayer();
final FeedStoryModel feedStoryModel = feedStoryModels.get(currentFeedStoryIndex);
currentStoryMediaId = feedStoryModel.getStoryMediaId();
currentStoryUsername = feedStoryModel.getProfileModel().getUsername();
isHashtag = fragmentArgs.getIsHashtag();
highlight = fragmentArgs.getHighlight();
final boolean hasUsername = !Utils.isEmpty(currentStoryUsername);
final boolean hasHighlight = !Utils.isEmpty(highlight);
if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", "");
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(currentStoryUsername);
// actionBar.setOnClickListener(v -> {
// searchUsername(username);
// });
if (hasHighlight) {
actionBar.setSubtitle(getString(R.string.title_highlight, highlight));
} else {
actionBar.setSubtitle(R.string.title_user_story);
}
}
}
storiesViewModel.getList().setValue(Collections.emptyList());
storiesService.getUserStory(currentStoryMediaId, null, false, false, false, false, new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
fetching = false;
if (storyModels == null || storyModels.isEmpty()) {
storiesViewModel.getList().setValue(Collections.emptyList());
currentStory = null;
binding.storiesList.setVisibility(View.GONE);
return;
}
binding.storiesList.setVisibility(View.VISIBLE);
storiesViewModel.getList().setValue(storyModels);
currentStory = storyModels.get(0);
refreshStory();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
}
private void refreshStory() {
if (binding.storiesList.getVisibility() == View.VISIBLE) {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
if (storyModels != null) {
StoryModel item = storyModels.get(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
storiesAdapter.notifyItemChanged(lastSlidePos, item);
}
item = storyModels.get(slidePos);
if (item != null) {
item.setCurrentSlide(true);
storiesAdapter.notifyItemChanged(slidePos, item);
}
}
}
lastSlidePos = slidePos;
final MediaItemType itemType = currentStory.getItemType();
if (menuDownload != null) menuDownload.setVisible(false);
url = itemType == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final String shortCode = currentStory.getTappableShortCode();
binding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
binding.viewStoryPost.setTag(shortCode);
final String spotify = currentStory.getSpotify();
binding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE);
binding.spotify.setTag(spotify);
poll = currentStory.getPoll();
binding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE);
binding.poll.setTag(poll);
question = currentStory.getQuestion();
binding.answer.setVisibility((question != null && !Utils.isEmpty(cookie)) ? View.VISIBLE : View.GONE);
binding.answer.setTag(question);
mentions = currentStory.getMentions();
binding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE);
binding.mention.setTag(mentions);
quiz = currentStory.getQuiz();
binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
binding.quiz.setTag(quiz);
releasePlayer();
if (isHashtag) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(currentStory.getUsername() + " (" + currentStoryUsername + ")");
}
// binding.toolbar.toolbar.setOnClickListener(v -> {
// searchUsername(currentStory.getUsername());
// });
}
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
if (Utils.isEmpty(highlight)) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)));
}
}
if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction(cookie, currentStory).execute();
}
private void downloadStory() {
int error = 0;
if (currentStory != null) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!Utils.isEmpty(customPath)) dir = new File(customPath);
}
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !Utils.isEmpty(currentStoryUsername))
dir = new File(dir, currentStoryUsername);
if (dir.exists() || dir.mkdirs()) {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp()
+ Utils.getExtensionFromModel(storyUrl, currentStory));
new DownloadAsync(requireContext(), storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
: R.string.downloader_error_download_file;
Toast.makeText(requireContext(), toastRes, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1)
Toast.makeText(requireContext(), R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2)
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
private void setupImage() {
binding.progressView.setVisibility(View.VISIBLE);
binding.playerView.setVisibility(View.GONE);
binding.imageViewer.setVisibility(View.VISIBLE);
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setOldController(binding.imageViewer.getController())
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(final String id, final ImageInfo imageInfo, final Animatable animatable) {
if (menuDownload != null) {
menuDownload.setVisible(true);
}
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) {
menuDm.setVisible(true);
}
binding.progressView.setVisibility(View.GONE);
}
})
.build();
binding.imageViewer.setController(controller);
}
private void setupVideo() {
binding.playerView.setVisibility(View.VISIBLE);
binding.progressView.setVisibility(View.GONE);
binding.imageViewer.setVisibility(View.GONE);
binding.imageViewer.setController(null);
player = new SimpleExoPlayer.Builder(requireContext()).build();
binding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(requireContext(), "instagram"))
.createMediaSource(Uri.parse(url));
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie))
menuDm.setVisible(true);
binding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie))
menuDm.setVisible(true);
binding.progressView.setVisibility(View.VISIBLE);
}
@Override
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
binding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) {
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
binding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
binding.playerView.setOnClickListener(v -> {
if (player != null) {
if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0);
player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying());
}
});
}
private void releasePlayer() {
if (player == null) return;
try { player.stop(true); } catch (Exception ignored) { }
try { player.release(); } catch (Exception ignored) { }
player = null;
}
}

12
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java

@ -28,6 +28,7 @@ import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessageInboxAdapter; import awais.instagrabber.adapters.DirectMessageInboxAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher; import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding; import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -39,7 +40,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
private static final String TAG = "DirectMessagesInboxFrag"; private static final String TAG = "DirectMessagesInboxFrag";
private FragmentActivity fragmentActivity; private FragmentActivity fragmentActivity;
private SwipeRefreshLayout root;
private NestedCoordinatorLayout root;
private RecyclerView inboxList; private RecyclerView inboxList;
private RecyclerLazyLoader lazyLoader; private RecyclerLazyLoader lazyLoader;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
@ -51,7 +52,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() { private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override @Override
public void doBefore() { public void doBefore() {
root.setRefreshing(true);
binding.swipeRefreshLayout.setRefreshing(true);
} }
@Override @Override
@ -71,10 +72,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
listViewModel.getList().postValue(list); listViewModel.getList().postValue(list);
} }
} }
root.setRefreshing(false);
binding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor(); stopCurrentExecutor();
} }
}; };
private FragmentDirectMessagesInboxBinding binding;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -89,9 +91,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
if (root != null) { if (root != null) {
return root; return root;
} }
final FragmentDirectMessagesInboxBinding binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false);
binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false);
root = binding.getRoot(); root = binding.getRoot();
root.setOnRefreshListener(this);
binding.swipeRefreshLayout.setOnRefreshListener(this);
inboxList = binding.inboxList; inboxList = binding.inboxList;
inboxList.setHasFixedSize(true); inboxList.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(requireContext()); layoutManager = new LinearLayoutManager(requireContext());

31
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java

@ -50,7 +50,6 @@ import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.ProfileViewer; import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.DirectMessageItemsAdapter; import awais.instagrabber.adapters.DirectMessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader; import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher; import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
@ -225,21 +224,21 @@ public class DirectMessageThreadFragment extends Fragment {
break; break;
case STORY_SHARE: case STORY_SHARE:
if (directItemModel.getReelShare() != null) { if (directItemModel.getReelShare() != null) {
StoryModel sm = new StoryModel(
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName(),
String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
false
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};
startActivity(new Intent(requireContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms)
);
// StoryModel sm = new StoryModel(
// directItemModel.getReelShare().getReelId(),
// directItemModel.getReelShare().getMedia().getVideoUrl(),
// directItemModel.getReelShare().getMedia().getMediaType(),
// directItemModel.getTimestamp(),
// directItemModel.getReelShare().getReelOwnerName(),
// String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
// false
// );
// sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
// StoryModel[] sms = {sm};
// startActivity(new Intent(requireContext(), StoryViewer.class)
// .putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
// .putExtra(Constants.EXTRAS_STORIES, sms)
// );
} else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) { } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]); searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
} }

241
app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java

@ -0,0 +1,241 @@
package awais.instagrabber.fragments.main;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.i.iTopicFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDiscoverBinding;
import awais.instagrabber.fragments.main.viewmodels.DiscoverItemViewModel;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.DiscoverTopicModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public class DiscoverFragment extends Fragment {
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentDiscoverBinding binding;
private DiscoverAdapter discoverAdapter;
private RecyclerLazyLoader lazyLoader;
private boolean discoverHasMore = false;
private String[] topicIds;
private String rankToken;
private String currentTopic;
private String discoverEndMaxId;
private ActionMode actionMode;
private DiscoverItemViewModel discoverItemViewModel;
private final FetchListener<DiscoverTopicModel> topicFetchListener = new FetchListener<DiscoverTopicModel>() {
@Override
public void doBefore() {}
@Override
public void onResult(final DiscoverTopicModel result) {
if (result != null) {
topicIds = result.getIds();
rankToken = result.getToken();
final ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(
requireContext(),
android.R.layout.simple_spinner_dropdown_item,
result.getNames()
);
binding.discoverType.setAdapter(spinnerArrayAdapter);
}
}
};
private final FetchListener<DiscoverItemModel[]> discoverFetchListener = new FetchListener<DiscoverItemModel[]>() {
@Override
public void doBefore() {
binding.discoverSwipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final DiscoverItemModel[] result) {
binding.discoverSwipeRefreshLayout.setRefreshing(false);
if (result == null || result.length == 0) {
Toast.makeText(requireContext(), R.string.discover_empty, Toast.LENGTH_SHORT).show();
return;
}
final List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
final List<DiscoverItemModel> resultList = Arrays.asList(result);
if (current == null) {
discoverItemViewModel.getList().postValue(resultList);
} else {
final List<DiscoverItemModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(resultList);
discoverItemViewModel.getList().postValue(currentCopy);
}
final DiscoverItemModel discoverItemModel = result[result.length - 1];
if (discoverItemModel != null) {
discoverEndMaxId = discoverItemModel.getNextMaxId();
discoverHasMore = discoverItemModel.hasMore();
discoverItemModel.setMore(false, null);
}
}
};
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (discoverAdapter == null) {
remove();
return;
}
discoverAdapter.clearSelection();
remove();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu,
new PrimaryActionModeCallback.CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (discoverAdapter == null) return false;
Utils.batchDownload(requireContext(),
null,
DownloadMethod.DOWNLOAD_DISCOVER,
discoverAdapter.getSelectedModels());
checkAndResetAction();
return true;
}
return false;
}
});
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
return root;
}
binding = FragmentDiscoverBinding.inflate(inflater, container, false);
root = binding.getRoot();
setupExplore();
return root;
}
private void setupExplore() {
discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(requireContext(), Utils.convertDpToPx(110));
binding.discoverPosts.setLayoutManager(layoutManager);
binding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.discoverType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (topicIds != null) {
currentTopic = topicIds[pos];
binding.discoverSwipeRefreshLayout.setRefreshing(true);
if (lazyLoader != null) lazyLoader.resetState();
discoverItemViewModel.getList().postValue(Collections.emptyList());
new DiscoverFetcher(currentTopic, null, rankToken, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
binding.discoverSwipeRefreshLayout.setOnRefreshListener(() -> {
lazyLoader.resetState();
discoverItemViewModel.getList().postValue(Collections.emptyList());
new DiscoverFetcher(currentTopic, null, rankToken, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
discoverAdapter = new DiscoverAdapter((model, position) -> {
if (discoverAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, discoverAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, position)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.DISCOVER_ITEMS)
.putExtra(Constants.EXTRAS_POST, new PostModel(model.getShortCode(), false)));
}, (model, position) -> {
if (!discoverAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedDispatcher.hasEnabledCallbacks()) {
return true;
}
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(onBackPressedCallback);
return true;
});
binding.discoverPosts.setAdapter(discoverAdapter);
discoverItemViewModel.getList().observe(fragmentActivity, discoverAdapter::submitList);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (discoverHasMore) {
binding.discoverSwipeRefreshLayout.setRefreshing(true);
new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
discoverEndMaxId = null;
}
});
binding.discoverPosts.addOnScrollListener(lazyLoader);
}
private boolean checkAndResetAction() {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (!onBackPressedDispatcher.hasEnabledCallbacks() || actionMode == null) {
return false;
}
actionMode.finish();
actionMode = null;
return true;
}
}

345
app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java

@ -0,0 +1,345 @@
package awais.instagrabber.fragments.main;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.fragments.main.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.fragments.main.viewmodels.FeedViewModel;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.services.ServiceCallback;
import awais.instagrabber.services.StoriesService;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedFragment extends Fragment {
private static final String TAG = "FeedFragment";
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);
public static final boolean SHOULD_AUTO_PLAY = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentFeedBinding binding;
private StoriesService storiesService;
private boolean feedHasNextPage = false;
private String feedEndCursor = null;
private FeedViewModel feedViewModel;
private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
@Override
public void doBefore() {
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
}
@Override
public void onResult(final FeedModel[] result) {
if (result == null) return;
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
for (final FeedModel feedModel : result) {
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
@Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
final Map<String, Object> extras = dataSource.getExtras();
if (extras == null) return;
final Uri thumbUri = (Uri) extras.get("uri_source");
if (thumbUri == null) return;
final Integer encodedWidth = (Integer) extras.get("encoded_width");
final Integer encodedHeight = (Integer) extras.get("encoded_height");
if (encodedWidth == null || encodedHeight == null) return;
final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
if (feedModel == null) return;
int requiredWidth = Utils.displayMetrics.widthPixels;
int resultingHeight = Utils.getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
if (feedModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
// If its a video and the height is too large, need to reduce the height,
// so that entire video fits on screen
resultingHeight = RESIZED_VIDEO_HEIGHT;
requiredWidth = Utils.getResultingWidth(RESIZED_VIDEO_HEIGHT, resultingHeight, requiredWidth);
}
feedModel.setImageWidth(requiredWidth);
feedModel.setImageHeight(resultingHeight);
success++;
updateAdapter();
}
@Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
failed++;
updateAdapter();
}
public void updateAdapter() {
if (failed + success != result.length) return;
final List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty() ? new ArrayList<>() : new ArrayList<>(currentFeedModelList);
finalList.addAll(Arrays.asList(result));
feedViewModel.getList().postValue(finalList);
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
binding.feedSwipeRefreshLayout.setRefreshing(false);
}
};
for (final FeedModel feedModel : result) {
final DataSource<Void> ds = Fresco.getImagePipeline().prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
return root;
}
binding = FragmentFeedBinding.inflate(inflater, container, false);
root = binding.getRoot();
// setupActionBar();
setupFeedStories();
setupFeed();
return root;
}
@Override
public void onPause() {
super.onPause();
if (videoAwareRecyclerScroller != null) {
videoAwareRecyclerScroller.stopPlaying();
}
}
@Override
public void onResume() {
super.onResume();
if (videoAwareRecyclerScroller != null && SHOULD_AUTO_PLAY) {
videoAwareRecyclerScroller.startPlaying();
}
}
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> {
// final AlertDialog.Builder builder = new AlertDialog.Builder(requireContext())
// .setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
// .setTitle(text)
// .setNegativeButton(R.string.cancel, null)
// .setPositiveButton(R.string.ok, (dialog, which) -> {
// // if (MainActivityBackup.scanHack != null) {
// // mainActivity.mainBinding.drawerLayout.closeDrawers();
// // MainActivityBackup.scanHack.onResult(text);
// // }
// });
// builder.show();
if (isHashtag) {
// hashtag...
return;
}
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToProfileFragment("@" + text);
NavHostFragment.findNavController(this).navigate(action);
};
private final View.OnClickListener postViewClickListener = v -> {
final Object tag = v.getTag();
if (!(tag instanceof FeedModel)) return;
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked())
feedModel.toggleCaption();
feedModel.setMentionClicked(false);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
feedModel.toggleCaption();
return;
}
final int id = v.getId();
switch (id) {
case R.id.btnComments:
startActivity(new Intent(requireContext(), CommentsViewer.class)
.putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode())
.putExtra(Constants.EXTRAS_POST, feedModel.getPostId())
.putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()));
break;
case R.id.viewStoryPost:
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, feedModel.getPosition())
.putExtra(Constants.EXTRAS_POST, new PostModel(feedModel.getShortCode(), false))
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.FEED_ITEMS));
break;
case R.id.btnDownload:
ProfileModel profileModel = feedModel.getProfileModel();
final String username = profileModel != null ? profileModel.getUsername() : null;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
Utils.batchDownload(requireContext(), username, DownloadMethod.DOWNLOAD_FEED, Collections.singletonList(feedModel));
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected)
postModels.add(sliderItem);
else if (sliderItem.isSelected()) {
postModels.add(sliderItem);
break;
}
}
}
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
if (breakWhenFoundSelected && postModels.size() == 0)
postModels.add(sliderItems[0]);
if (postModels.size() > 0)
Utils.batchDownload(requireContext(), username, DownloadMethod.DOWNLOAD_FEED, postModels);
};
new AlertDialog.Builder(requireContext())
.setTitle(R.string.post_viewer_download_dialog_title)
.setPositiveButton(R.string.post_viewer_download_current, clickListener1)
.setNegativeButton(R.string.post_viewer_download_album, clickListener1)
.show();
}
break;
case R.id.ivProfilePic:
profileModel = feedModel.getProfileModel();
if (profileModel != null)
mentionClickListener.onClick(null, profileModel.getUsername(), false);
break;
}
};
private void setupFeed() {
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
binding.feedRecyclerView.setLayoutManager(layoutManager);
binding.feedRecyclerView.setHasFixedSize(true);
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener);
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
binding.feedRecyclerView.setAdapter(feedAdapter);
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (feedHasNextPage) {
fetchFeed();
}
});
if (SHOULD_AUTO_PLAY) {
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
}
binding.feedRecyclerView.addOnScrollListener(lazyLoader);
fetchFeed();
}
private void fetchFeed() {
binding.feedSwipeRefreshLayout.setRefreshing(true);
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
feedEndCursor = null;
}
private void setupFeedStories() {
final FeedStoriesViewModel feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter((model, position) -> {
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(
position,
null,
false);
NavHostFragment.findNavController(this).navigate(action);
});
binding.feedStoriesRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false));
binding.feedStoriesRecyclerView.setAdapter(feedStoriesAdapter);
feedStoriesViewModel.getList().observe(fragmentActivity, feedStoriesAdapter::submitList);
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
}
});
}
}

57
app/src/main/java/awais/instagrabber/fragments/main/ProfileActionListener.java

@ -0,0 +1,57 @@
package awais.instagrabber.fragments.main;
import android.content.Intent;
import android.view.View;
import awais.instagrabber.MainHelper;
import awais.instagrabber.activities.SavedViewer;
import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils;
public class ProfileActionListener implements View.OnClickListener {
private String cookie;
private boolean isLoggedIn;
private ProfileModel profileModel;
private String userQuery;
private FragmentProfileBinding binding;
private LocationModel locationModel;
public ProfileActionListener(final String cookie, final boolean isLoggedIn, final ProfileModel profileModel, final String userQuery, final FragmentProfileBinding binding, final LocationModel locationModel) {
this.cookie = cookie;
this.isLoggedIn = isLoggedIn;
this.profileModel = profileModel;
this.userQuery = userQuery;
this.binding = binding;
this.locationModel = locationModel;
}
@Override
public void onClick(final View v) {
// else if (v == binding.btnFollow) {
//
// } else if (v == mainActivity.mainBinding.profileView.btnRestrict && isLoggedIn) {
// new ProfileAction().execute("restrict");
// } else if (v == mainActivity.mainBinding.profileView.btnSaved && !isSelf) {
// new ProfileAction().execute("block");
// } else if (v == mainActivity.mainBinding.profileView.btnFollowTag) {
// new ProfileAction().execute("followtag");
// } else if (v == mainActivity.mainBinding.profileView.btnTagged || (v == mainActivity.mainBinding.profileView.btnRestrict && !isLoggedIn)) {
// mainActivity.startActivity(new Intent(mainActivity, SavedViewer.class)
// .putExtra(Constants.EXTRAS_INDEX, "%" + mainActivity.profileModel.getId())
// .putExtra(Constants.EXTRAS_USER, "@" + mainActivity.profileModel.getUsername())
// );
// } else if (v == mainActivity.mainBinding.profileView.btnSaved) {
//
// } else if (v == mainActivity.mainBinding.profileView.btnLiked) {
//
// }
}
}

532
app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java

@ -0,0 +1,532 @@
package awais.instagrabber.fragments.main;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableStringBuilder;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
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;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import java.util.Arrays;
import java.util.Collections;
import awais.instagrabber.R;
import awais.instagrabber.activities.FollowViewer;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.SavedViewer;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.fragments.main.viewmodels.ProfilePostsViewModel;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.services.ProfileService;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfileFragment extends Fragment {
private static final String TAG = "ProfileFragment";
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentProfileBinding binding;
private boolean isLoggedIn;
private String cookie;
private String username;
private ProfileModel profileModel;
private ProfilePostsViewModel profilePostsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode;
private Handler usernameSettingHandler;
private ProfileService profileService;
private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(username.substring(1));
}
};
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (postsAdapter == null) {
remove();
return;
}
postsAdapter.clearSelection();
remove();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu,
new CallbacksHelper() {
@Override
public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed();
}
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) {
return false;
}
Utils.batchDownload(requireContext(),
username,
DownloadMethod.DOWNLOAD_MAIN,
postsAdapter.getSelectedModels());
checkAndResetAction();
return true;
}
return false;
}
});
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
profileService = ProfileService.getInstance();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
return root;
}
binding = FragmentProfileBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onDestroy() {
super.onDestroy();
if (usernameSettingHandler != null) {
usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
}
if (profilePostsViewModel != null) {
profilePostsViewModel.getList().postValue(Collections.emptyList());
}
}
private void init() {
cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
if (getArguments() != null) {
final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments());
username = fragmentArgs.getUsername();
setUsernameDelayed();
}
if (!isLoggedIn) {
binding.privatePage1.setImageResource(R.drawable.ic_info);
binding.privatePage2.setText(R.string.no_acc);
binding.privatePage.setVisibility(View.VISIBLE);
return;
}
setupPosts();
fetchProfile();
}
private void fetchProfile() {
final String uid = Utils.getUserIdFromCookie(cookie);
if (username == null && uid != null) {
final FetchListener<String> fetchListener = username -> {
if (Utils.isEmpty(username)) return;
this.username = username;
setUsernameDelayed();
fetchProfileDetails();
// adds cookies to database for quick access
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid);
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername()))
Utils.dataBox.addUserCookie(new DataBox.CookieModel(uid, username, cookie));
};
boolean found = false;
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid);
if (cookieModel != null) {
final String username = cookieModel.getUsername();
if (username != null) {
found = true;
fetchListener.onResult("@" + username);
}
}
if (!found) {
// if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
return;
}
fetchProfileDetails();
}
private void fetchProfileDetails() {
new ProfileFetcher(username.substring(1), profileModel -> {
this.profileModel = profileModel;
new PostsFetcher(profileModel.getId(),
null,
result -> profilePostsViewModel.getList().postValue(Arrays.asList(result)))
.setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
setProfileDetails();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setProfileDetails() {
setupCommonListeners();
if (profileModel == null) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(requireContext(), R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId();
if (settingsHelper.getBoolean(Constants.STORIESIG)) {
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), false,
result -> {
// mainActivity.storyModels = result;
// if (result != null && result.length > 0)
// binding.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), result -> {
if (result != null && result.length > 0) {
binding.highlightsList.setVisibility(View.VISIBLE);
// highlightsAdapter.setData(result);
} else
binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
if (isLoggedIn) {
final String myId = Utils.getUserIdFromCookie(cookie);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnSaved.setText(R.string.saved);
ViewCompat.setBackgroundTintList(
binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_orange_background)));
} else {
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
ViewCompat.setBackgroundTintList(
binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_purple_background)));
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
ViewCompat.setBackgroundTintList(
binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_purple_background)));
} else {
binding.btnFollow.setText(R.string.follow);
ViewCompat.setBackgroundTintList(
binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_pink_background)));
}
binding.btnRestrict.setVisibility(View.VISIBLE);
if (profileModel.getRestricted()) {
binding.btnRestrict.setText(R.string.unrestrict);
ViewCompat.setBackgroundTintList(
binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_green_background)));
} else {
binding.btnRestrict.setText(R.string.restrict);
ViewCompat.setBackgroundTintList(
binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_orange_background)));
}
if (profileModel.isReallyPrivate()) {
binding.btnBlock.setVisibility(View.VISIBLE);
binding.btnTagged.setVisibility(View.GONE);
if (profileModel.getBlocked()) {
binding.btnBlock.setText(R.string.unblock);
ViewCompat.setBackgroundTintList(
binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_green_background)));
} else {
binding.btnBlock.setText(R.string.block);
ViewCompat.setBackgroundTintList(
binding.btnBlock,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_red_background)));
}
} else {
binding.btnBlock.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnTagged.setVisibility(View.VISIBLE);
if (profileModel.getBlocked()) {
binding.btnSaved.setText(R.string.unblock);
ViewCompat.setBackgroundTintList(
binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_green_background)));
} else {
binding.btnSaved.setText(R.string.block);
ViewCompat.setBackgroundTintList(
binding.btnSaved,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_red_background))
);
}
}
}
} else {
if (Utils.dataBox.getFavorite(username) != null) {
binding.btnFollow.setText(R.string.unfavorite_short);
ViewCompat.setBackgroundTintList(
binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_purple_background)));
} else {
binding.btnFollow.setText(R.string.favorite_short);
ViewCompat.setBackgroundTintList(
binding.btnFollow,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_pink_background)));
}
binding.btnFollow.setVisibility(View.VISIBLE);
if (!profileModel.isReallyPrivate()) {
binding.btnRestrict.setVisibility(View.VISIBLE);
binding.btnRestrict.setText(R.string.tagged);
ViewCompat.setBackgroundTintList(
binding.btnRestrict,
ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.btn_blue_background)));
}
}
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
final String postCount = String.valueOf(profileModel.getPostCount());
SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
binding.mainPostCount.setText(span);
final String followersCountStr = String.valueOf(followersCount);
final int followersCountStrLen = followersCountStr.length();
span = new SpannableStringBuilder(getString(R.string.main_posts_followers, followersCountStr));
span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0);
binding.mainFollowers.setText(span);
final String followingCountStr = String.valueOf(followingCount);
final int followingCountStrLen = followingCountStr.length();
span = new SpannableStringBuilder(getString(R.string.main_posts_following, followingCountStr));
span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0);
binding.mainFollowing.setText(span);
binding.mainFullName.setText(Utils.isEmpty(profileModel.getName()) ? profileModel.getUsername() : profileModel.getName());
CharSequence biography = profileModel.getBiography();
binding.mainBiography.setCaptionIsExpandable(true);
binding.mainBiography.setCaptionIsExpanded(true);
if (Utils.hasMentions(biography)) {
biography = Utils.getMentionText(biography);
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
// binding.mainBiography.setMentionClickListener(mentionClickListener);
} else {
binding.mainBiography.setText(biography);
binding.mainBiography.setMentionClickListener(null);
}
final String url = profileModel.getUrl();
if (Utils.isEmpty(url)) {
binding.mainUrl.setVisibility(View.GONE);
} else {
binding.mainUrl.setVisibility(View.VISIBLE);
binding.mainUrl.setText(Utils.getSpannableUrl(url));
}
binding.mainFullName.setSelected(true);
binding.mainBiography.setEnabled(true);
if (!profileModel.isReallyPrivate()) {
binding.mainFollowing.setClickable(true);
binding.mainFollowers.setClickable(true);
if (isLoggedIn) {
final View.OnClickListener followClickListener = v -> startActivity(new Intent(requireContext(), FollowViewer.class)
.putExtra(Constants.EXTRAS_FOLLOWERS, v == binding.mainFollowers)
.putExtra(Constants.EXTRAS_NAME, profileModel.getUsername())
.putExtra(Constants.EXTRAS_ID, profileId));
binding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null);
binding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null);
}
if (profileModel.getPostCount() == 0) {
binding.swipeRefreshLayout.setRefreshing(false);
binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
} else {
binding.swipeRefreshLayout.setRefreshing(true);
binding.mainPosts.setVisibility(View.VISIBLE);
// currentlyExecuting = new PostsFetcher(profileId, postsFetchListener)
// .setUsername(profileModel.getUsername())
// .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
binding.mainFollowers.setClickable(false);
binding.mainFollowing.setClickable(false);
binding.swipeRefreshLayout.setRefreshing(false);
// error
binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE);
binding.mainPosts.setVisibility(View.GONE);
}
}
private void setupCommonListeners() {
final String userIdFromCookie = Utils.getUserIdFromCookie(cookie);
final boolean isSelf = isLoggedIn
&& profileModel != null
&& userIdFromCookie != null
&& userIdFromCookie.equals(profileModel.getId());
final String favorite = Utils.dataBox.getFavorite(username);
binding.btnFollow.setOnClickListener(v -> {
if (!isLoggedIn) {
if (favorite != null && v == binding.btnFollow) {
Utils.dataBox.delFavorite(
new DataBox.FavoriteModel(username,
Long.parseLong(favorite.split("/")[1]),
username.replaceAll("^@", "")
)
);
} else if (v == binding.btnFollow) {
Utils.dataBox.addFavorite(
new DataBox.FavoriteModel(username, System.currentTimeMillis(),
username.replaceAll("^@", "")));
}
// onRefresh();
return;
}
profileService.followProfile(username);
});
// binding.btnRestrict.setOnClickListener(profileActionListener);
// binding.btnBlock.setOnClickListener(profileActionListener);
binding.btnSaved.setOnClickListener(v -> startActivity(new Intent(requireContext(), SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "$" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
));
binding.btnLiked.setOnClickListener(v -> startActivity(new Intent(requireContext(), SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "^" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
));
binding.btnTagged.setOnClickListener(v -> startActivity(new Intent(requireContext(), SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "%" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
));
// binding.btnFollowTag.setOnClickListener(profileActionListener);
}
private void setUsernameDelayed() {
if (usernameSettingHandler == null) {
usernameSettingHandler = new Handler(Looper.getMainLooper());
}
usernameSettingHandler.postDelayed(usernameSettingRunnable, 200);
}
private void setupPosts() {
profilePostsViewModel = new ViewModelProvider(this).get(ProfilePostsViewModel.class);
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(requireContext(), 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;
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, position)
.putExtra(Constants.EXTRAS_POST, postModel)
.putExtra(Constants.EXTRAS_USER, username)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedDispatcher.hasEnabledCallbacks()) {
return true;
}
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(onBackPressedCallback);
return true;
});
binding.mainPosts.setAdapter(postsAdapter);
profilePostsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
}
private boolean checkAndResetAction() {
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (!onBackPressedDispatcher.hasEnabledCallbacks() || actionMode == null) {
return false;
}
actionMode.finish();
actionMode = null;
return true;
}
}

19
app/src/main/java/awais/instagrabber/fragments/main/viewmodels/DiscoverItemViewModel.java

@ -0,0 +1,19 @@
package awais.instagrabber.fragments.main.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.DiscoverItemModel;
public class DiscoverItemViewModel extends ViewModel {
private MutableLiveData<List<DiscoverItemModel>> list;
public MutableLiveData<List<DiscoverItemModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

19
app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedStoriesViewModel.java

@ -0,0 +1,19 @@
package awais.instagrabber.fragments.main.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.FeedStoryModel;
public class FeedStoriesViewModel extends ViewModel {
private MutableLiveData<List<FeedStoryModel>> list;
public MutableLiveData<List<FeedStoryModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

20
app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedViewModel.java

@ -0,0 +1,20 @@
package awais.instagrabber.fragments.main.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.models.FeedModel;
public class FeedViewModel extends ViewModel {
private MutableLiveData<List<FeedModel>> list;
public MutableLiveData<List<FeedModel>> getList() {
if (list == null) {
list = new MutableLiveData<>(Collections.emptyList());
}
return list;
}
}

19
app/src/main/java/awais/instagrabber/fragments/main/viewmodels/ProfilePostsViewModel.java

@ -0,0 +1,19 @@
package awais.instagrabber.fragments.main.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.PostModel;
public class ProfilePostsViewModel extends ViewModel {
private MutableLiveData<List<PostModel>> list;
public MutableLiveData<List<PostModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

19
app/src/main/java/awais/instagrabber/fragments/main/viewmodels/StoriesViewModel.java

@ -0,0 +1,19 @@
package awais.instagrabber.fragments.main.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.StoryModel;
public class StoriesViewModel extends ViewModel {
private MutableLiveData<List<StoryModel>> list;
public MutableLiveData<List<StoryModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

17
app/src/main/java/awais/instagrabber/models/BasePostModel.java

@ -1,14 +1,16 @@
package awais.instagrabber.models; package awais.instagrabber.models;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.ObjectsCompat;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import awais.instagrabber.adapters.MultiSelectListAdapter.Selectable;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
public abstract class BasePostModel implements Serializable {
public abstract class BasePostModel implements Serializable, Selectable {
protected String postId; protected String postId;
protected String displayUrl; protected String displayUrl;
protected String shortCode; protected String shortCode;
@ -88,4 +90,17 @@ public abstract class BasePostModel implements Serializable {
public final String getPostDate() { public final String getPostDate() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L)); return Utils.datetimeParser.format(new Date(timestamp * 1000L));
} }
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final BasePostModel that = (BasePostModel) o;
return ObjectsCompat.equals(postId, that.postId);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(postId);
}
} }

2
app/src/main/java/awais/instagrabber/models/PostModel.java

@ -51,4 +51,6 @@ public class PostModel extends BasePostModel {
this.endCursor = endCursor; this.endCursor = endCursor;
this.hasNextPage = hasNextPage; this.hasNextPage = hasNextPage;
} }
} }

4
app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java

@ -0,0 +1,4 @@
package awais.instagrabber.repositories;
public interface ProfileRepository {
}

18
app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java

@ -0,0 +1,18 @@
package awais.instagrabber.repositories;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
public interface StoriesRepository {
@GET("graphql/query/")
Call<String> getStories(@QueryMap(encoded = true) Map<String, String> variables);
@GET
Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url);
}

24
app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java

@ -0,0 +1,24 @@
package awais.instagrabber.services;
import androidx.annotation.NonNull;
import java.io.IOException;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class AddCookiesInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request();
final Request.Builder builder = request.newBuilder();
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
builder.addHeader("Cookie", cookie);
final Request updatedRequest = builder.build();
return chain.proceed(updatedRequest);
}
}

24
app/src/main/java/awais/instagrabber/services/BaseService.java

@ -0,0 +1,24 @@
package awais.instagrabber.services;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public abstract class BaseService {
private Retrofit.Builder builder;
Retrofit.Builder getRetrofitBuilder() {
if (builder == null) {
final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new AddCookiesInterceptor())
.followRedirects(false)
.followSslRedirects(false)
.build();
builder = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.client(client);
}
return builder;
}
}

4
app/src/main/java/awais/instagrabber/services/FeedService.java

@ -0,0 +1,4 @@
package awais.instagrabber.services;
public interface FeedService {
}

35
app/src/main/java/awais/instagrabber/services/ProfileService.java

@ -0,0 +1,35 @@
package awais.instagrabber.services;
import awais.instagrabber.repositories.ProfileRepository;
import retrofit2.Retrofit;
public class ProfileService extends BaseService {
private static final String TAG = "ProfileService";
private final ProfileRepository repository;
private static ProfileService instance;
private ProfileService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(ProfileRepository.class);
}
public static ProfileService getInstance() {
if (instance == null) {
instance = new ProfileService();
}
return instance;
}
public void followProfile(final String username) {
// final String url = "https://www.instagram.com/web/" + (action.equals("followtag") && mainActivity.hashtagModel != null ? "tags/" + (mainActivity.hashtagModel.getFollowing() ? "unfollow/" : "follow/") + mainActivity.hashtagModel.getName() + "/" : (action.equals("restrict") && mainActivity.profileModel != null ? "restrict_action" : "friendships/" + mainActivity.profileModel.getId()) + "/" + (action.equals("follow") ?
// mainActivity.profileModel.getFollowing() || mainActivity.profileModel.getRequested()
// ? "unfollow/" : "follow/" :
// action.equals("restrict") ?
// mainActivity.profileModel.getRestricted() ? "unrestrict/" : "restrict/" :
// mainActivity.profileModel.getBlocked() ? "unblock/" : "block/"));
}
}

7
app/src/main/java/awais/instagrabber/services/ServiceCallback.java

@ -0,0 +1,7 @@
package awais.instagrabber.services;
public interface ServiceCallback<T> {
void onSuccess(T result);
void onFailure(Throwable t);
}

277
app/src/main/java/awais/instagrabber/services/StoriesService.java

@ -0,0 +1,277 @@
package awais.instagrabber.services;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.repositories.StoriesRepository;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class StoriesService extends BaseService {
private static final String TAG = "StoriesService";
private final StoriesRepository repository;
private static StoriesService instance;
private StoriesService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
repository = retrofit.create(StoriesRepository.class);
}
public static StoriesService getInstance() {
if (instance == null) {
instance = new StoriesService();
}
return instance;
}
public void getFeedStories(final ServiceCallback<List<FeedStoryModel>> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "b7b84d884400bc5aa7cfe12ae843a091");
queryMap.put("variables", "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}");
final Call<String> response = repository.getStories(queryMap);
response.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "getFeedStories: body is empty");
return;
}
try {
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
final JSONArray feedStoriesReel = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("feed_reels_tray")
.getJSONObject("edge_reels_tray_to_reel")
.getJSONArray("edges");
for (int i = 0; i < feedStoriesReel.length(); ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
final ProfileModel profileModel = new ProfileModel(false, false, false,
user.getString("id"),
user.getString("username"),
null, null, null,
user.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final String id = node.getString("id");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead));
}
callback.onSuccess(feedStoryModels);
} catch (JSONException e) {
Log.e(TAG, "Error parsing json", e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void getUserStory(final String id,
final String username,
final boolean storiesig,
final boolean isLoc,
final boolean isHashtag,
final boolean highlight,
final ServiceCallback<List<StoryModel>> callback) {
final String url = buildUrl(id, storiesig, isLoc, isHashtag, highlight);
final String userAgent = storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT;
final Call<String> userStoryCall = repository.getUserStory(userAgent, url);
userStoryCall.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
JSONObject data;
String localUsername = username;
try {
final String body = response.body();
if (body == null) {
Log.e(TAG, "body is null");
return;
}
data = new JSONObject(body);
if (!storiesig && !highlight)
data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
else if (highlight) data = data.getJSONObject("reels").optJSONObject(id);
if (data != null
&& localUsername == null
&& !isLoc
&& !isHashtag)
localUsername = data.getJSONObject("user").getString("username");
JSONArray media;
if (data != null
&& (media = data.optJSONArray("items")) != null
&& media.length() > 0 && media.optJSONObject(0) != null) {
final int mediaLen = media.length();
final List<StoryModel> models = new ArrayList<>();
for (int i = 0; i < mediaLen; ++i) {
data = media.getJSONObject(i);
final boolean isVideo = data.has("video_duration");
final StoryModel model = new StoryModel(data.getString("id"),
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0).getString("url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0),
(isLoc || isHashtag) ? data.getJSONObject("user").getString("username") : localUsername,
data.getJSONObject("user").getString("pk"),
data.getBoolean("can_reply"));
final JSONArray videoResources = data.optJSONArray("video_versions");
if (isVideo && videoResources != null)
model.setVideoUrl(Utils.getHighQualityPost(videoResources, true, true, false));
if (data.has("story_feed_media")) {
model.setTappableShortCode(data.getJSONArray("story_feed_media").getJSONObject(0).optString("media_id"));
}
if (!data.isNull("story_app_attribution"))
model.setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
if (data.has("story_polls")) {
final JSONArray storyPolls = data.optJSONArray("story_polls");
JSONObject tappableObject = null;
if (storyPolls != null) {
tappableObject = storyPolls.getJSONObject(0).optJSONObject("poll_sticker");
}
if (tappableObject != null) model.setPoll(new PollModel(
String.valueOf(tappableObject.getLong("poll_id")),
tappableObject.getString("question"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getInt("count"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getInt("count"),
tappableObject.optInt("viewer_vote", -1)
));
}
if (data.has("story_questions")) {
JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0).optJSONObject("question_sticker");
if (tappableObject != null && !tappableObject.getString("question_type").equals("music"))
model.setQuestion(new QuestionModel(
String.valueOf(tappableObject.getLong("question_id")),
tappableObject.getString("question")
));
}
if (data.has("story_quizs")) {
JSONObject tappableObject = data.getJSONArray("story_quizs").getJSONObject(0).optJSONObject("quiz_sticker");
if (tappableObject != null) {
String[] choices = new String[tappableObject.getJSONArray("tallies").length()];
Long[] counts = new Long[choices.length];
for (int q = 0; q < choices.length; ++q) {
JSONObject tempchoice = tappableObject.getJSONArray("tallies").getJSONObject(q);
choices[q] = (q == tappableObject.getInt("correct_answer") ? "*** " : "")
+ tempchoice.getString("text");
counts[q] = tempchoice.getLong("count");
}
model.setQuiz(new QuizModel(
String.valueOf(tappableObject.getLong("quiz_id")),
tappableObject.getString("question"),
choices,
counts,
tappableObject.optInt("viewer_answer", -1)
));
}
}
JSONArray hashtags = data.optJSONArray("story_hashtags");
JSONArray locations = data.optJSONArray("story_locations");
JSONArray atmarks = data.optJSONArray("reel_mentions");
String[] mentions = new String[(hashtags == null ? 0 : hashtags.length())
+ (atmarks == null ? 0 : atmarks.length())
+ (locations == null ? 0 : locations.length())];
if (hashtags != null) {
for (int h = 0; h < hashtags.length(); ++h) {
mentions[h] = "#" + hashtags.getJSONObject(h).getJSONObject("hashtag").getString("name");
}
}
if (atmarks != null) {
for (int h = 0; h < atmarks.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length())] =
"@" + atmarks.getJSONObject(h).getJSONObject("user").getString("username");
}
}
if (locations != null) {
for (int h = 0; h < locations.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length()) + (atmarks == null ? 0 : atmarks.length())] =
locations.getJSONObject(h).getJSONObject("location").getLong("pk")
+ "/ (" + locations.getJSONObject(h).getJSONObject("location").getString("short_name") + ")";
}
}
if (mentions.length != 0) model.setMentions(mentions);
models.add(model);
}
callback.onSuccess(models);
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing string");
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
private String buildUrl(final String id, final boolean storiesig, final boolean isLoc, final boolean isHashtag, final boolean highlight) {
final String userId = id.replace(":", "%3A");
final StringBuilder builder = new StringBuilder();
builder.append("https://");
if (storiesig) {
builder.append("storiesig");
} else {
builder.append("i.instagram");
}
builder.append(".com/api/v1/");
if (isLoc) {
builder.append("locations/");
}
if (isHashtag) {
builder.append("tags/");
}
if (highlight) {
builder.append("feed/reels_media?user_ids=");
} else {
builder.append("feed/user/");
}
builder.append(userId);
if (!highlight) {
if (storiesig) {
builder.append("/reel_media/");
} else {
builder.append("/story/");
}
}
return builder.toString();
}
}

207
app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java

@ -0,0 +1,207 @@
package awais.instagrabber.utils;
import android.content.Intent;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.navigation.NavController;
import androidx.navigation.NavGraph;
import androidx.navigation.fragment.NavHostFragment;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.List;
import awais.instagrabber.R;
public class NavigationExtensions {
@NonNull
public static LiveData<NavController> setupWithNavController(@NonNull final BottomNavigationView bottomNavigationView,
@NonNull List<Integer> navGraphIds,
@NonNull final FragmentManager fragmentManager,
final int containerId,
@NonNull Intent intent,
final int firstFragmentGraphIndex) {
final SparseArray<String> graphIdToTagMap = new SparseArray<>();
final MutableLiveData<NavController> selectedNavController = new MutableLiveData<>();
int firstFragmentGraphId = 0;
for (int i = 0; i < navGraphIds.size(); i++) {
final int navGraphId = navGraphIds.get(i);
final String fragmentTag = getFragmentTag(i);
final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId);
final NavController navController = navHostFragment.getNavController();
final int graphId = navController.getGraph().getId();
if (i == firstFragmentGraphIndex) {
firstFragmentGraphId = graphId;
}
graphIdToTagMap.put(graphId, fragmentTag);
if (bottomNavigationView.getSelectedItemId() == graphId) {
selectedNavController.setValue(navHostFragment.getNavController());
attachNavHostFragment(fragmentManager, navHostFragment, i == firstFragmentGraphIndex);
} else {
detachNavHostFragment(fragmentManager, navHostFragment);
}
}
final String[] selectedItemTag = {graphIdToTagMap.get(bottomNavigationView.getSelectedItemId())};
final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId);
final boolean[] isOnFirstFragment = {selectedItemTag[0] != null && selectedItemTag[0].equals(firstFragmentTag)};
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
if (fragmentManager.isStateSaved()) {
return false;
}
String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
if (!selectedItemTag[0].equals(newlySelectedItemTag)) {
fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragment == null) {
throw new RuntimeException("null cannot be cast to non-null NavHostFragment");
}
final NavHostFragment selectedFragment = (NavHostFragment) fragment;
if (!firstFragmentTag.equals(newlySelectedItemTag)) {
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction()
.setCustomAnimations(
R.anim.nav_default_enter_anim,
R.anim.nav_default_exit_anim,
R.anim.nav_default_pop_enter_anim,
R.anim.nav_default_pop_exit_anim
)
.attach(selectedFragment)
.setPrimaryNavigationFragment(selectedFragment);
for (int i = 0; i < graphIdToTagMap.size(); i++) {
final int key = graphIdToTagMap.keyAt(i);
final String fragmentTagForId = graphIdToTagMap.get(key);
if (!fragmentTagForId.equals(newlySelectedItemTag)) {
final Fragment fragmentByTag = fragmentManager.findFragmentByTag(firstFragmentTag);
if (fragmentByTag == null) {
continue;
}
fragmentTransaction.detach(fragmentByTag);
}
}
fragmentTransaction.addToBackStack(firstFragmentTag)
.setReorderingAllowed(true)
.commit();
}
selectedItemTag[0] = newlySelectedItemTag;
isOnFirstFragment[0] = selectedItemTag[0].equals(firstFragmentTag);
selectedNavController.setValue(selectedFragment.getNavController());
return true;
}
return false;
});
setupItemReselected(bottomNavigationView, graphIdToTagMap, fragmentManager);
setupDeepLinks(bottomNavigationView, navGraphIds, fragmentManager, containerId, intent);
final int finalFirstFragmentGraphId = firstFragmentGraphId;
fragmentManager.addOnBackStackChangedListener(() -> {
if (!isOnFirstFragment[0]) {
if (firstFragmentTag == null) {
return;
}
if (!isOnBackStack(fragmentManager, firstFragmentTag)) {
bottomNavigationView.setSelectedItemId(finalFirstFragmentGraphId);
}
}
final NavController navController = selectedNavController.getValue();
if (navController != null && navController.getCurrentDestination() == null) {
final NavGraph navControllerGraph = navController.getGraph();
navController.navigate(navControllerGraph.getId());
}
});
return selectedNavController;
}
private static NavHostFragment obtainNavHostFragment(final FragmentManager fragmentManager,
final String fragmentTag,
final int navGraphId,
final int containerId) {
final NavHostFragment existingFragment = (NavHostFragment) fragmentManager.findFragmentByTag(fragmentTag);
if (existingFragment != null) {
return existingFragment;
}
final NavHostFragment navHostFragment = NavHostFragment.create(navGraphId);
fragmentManager.beginTransaction()
.add(containerId, navHostFragment, fragmentTag)
.commitNow();
return navHostFragment;
}
private static void attachNavHostFragment(final FragmentManager fragmentManager,
final NavHostFragment navHostFragment,
final boolean isPrimaryNavFragment) {
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.attach(navHostFragment);
if (isPrimaryNavFragment) {
fragmentTransaction.setPrimaryNavigationFragment(navHostFragment);
}
fragmentTransaction.commitNow();
}
private static void detachNavHostFragment(final FragmentManager fragmentManager, final NavHostFragment navHostFragment) {
fragmentManager.beginTransaction()
.detach(navHostFragment)
.commitNow();
}
private static void setupItemReselected(final BottomNavigationView bottomNavigationView,
final SparseArray<String> graphIdToTagMap,
final FragmentManager fragmentManager) {
bottomNavigationView.setOnNavigationItemReselectedListener(item -> {
final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragmentByTag == null) {
throw new NullPointerException("null cannot be cast to non-null type NavHostFragment");
}
final NavHostFragment selectedFragment = (NavHostFragment) fragmentByTag;
final NavController navController = selectedFragment.getNavController();
final NavGraph navControllerGraph = navController.getGraph();
navController.popBackStack(navControllerGraph.getStartDestination(), false);
});
}
private static void setupDeepLinks(final BottomNavigationView bottomNavigationView,
final List<Integer> navGraphIds,
final FragmentManager fragmentManager,
final int containerId,
final Intent intent) {
for (int i = 0; i < navGraphIds.size(); i++) {
final int navGraphId = navGraphIds.get(i);
final String fragmentTag = getFragmentTag(i);
final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId);
if (navHostFragment.getNavController().handleDeepLink(intent)) {
final int selectedItemId = bottomNavigationView.getSelectedItemId();
NavController navController = navHostFragment.getNavController();
NavGraph graph = navController.getGraph();
if (selectedItemId != graph.getId()) {
navController = navHostFragment.getNavController();
graph = navController.getGraph();
bottomNavigationView.setSelectedItemId(graph.getId());
}
}
}
}
private static boolean isOnBackStack(final FragmentManager fragmentManager, final String backStackName) {
int backStackCount = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < backStackCount; i++) {
final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
final String name = backStackEntry.getName();
if (name != null && name.equals(backStackName)) {
return true;
}
}
return false;
}
private static String getFragmentTag(final int index) {
return "bottomNavigation#" + index;
}
}

24
app/src/main/java/awais/instagrabber/utils/Utils.java

@ -71,6 +71,7 @@ import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.MainActivityBackup;
import awais.instagrabber.activities.ProfileViewer; import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.activities.SavedViewer; import awais.instagrabber.activities.SavedViewer;
import awais.instagrabber.asyncs.DownloadAsync; import awais.instagrabber.asyncs.DownloadAsync;
@ -942,7 +943,7 @@ public final class Utils {
dir = new File(dir, username); dir = new File(dir, username);
if (dir.exists() || dir.mkdirs()) { if (dir.exists() || dir.mkdirs()) {
final MainActivity mainActivity = method != DownloadMethod.DOWNLOAD_FEED && context instanceof MainActivity ? (MainActivity) context : null;
final MainActivityBackup mainActivity = method != DownloadMethod.DOWNLOAD_FEED && context instanceof MainActivityBackup ? (MainActivityBackup) context : null;
final ProfileViewer pv = method == DownloadMethod.DOWNLOAD_MAIN && context instanceof ProfileViewer ? (ProfileViewer) context : null; final ProfileViewer pv = method == DownloadMethod.DOWNLOAD_MAIN && context instanceof ProfileViewer ? (ProfileViewer) context : null;
final SavedViewer saved = method == DownloadMethod.DOWNLOAD_SAVED && context instanceof SavedViewer ? (SavedViewer) context : null; final SavedViewer saved = method == DownloadMethod.DOWNLOAD_SAVED && context instanceof SavedViewer ? (SavedViewer) context : null;
@ -973,19 +974,20 @@ public final class Utils {
saveFile, saveFile,
file -> { file -> {
model.setDownloaded(true); model.setDownloaded(true);
if (saved != null)
saved.deselectSelection(selectedItem);
else if (mainActivity != null)
mainActivity.mainHelper.deselectSelection(selectedItem);
else if (pv != null) pv.deselectSelection(selectedItem);
// if (saved != null)
// saved.deselectSelection(selectedItem);
// else if (mainActivity != null)
// mainActivity.mainHelper.deselectSelection(selectedItem);
// else pv.deselectSelection(selectedItem);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} else {
if (saved != null) saved.deselectSelection(selectedItem);
else if (mainActivity != null)
mainActivity.mainHelper.deselectSelection(selectedItem);
else if (pv != null) pv.deselectSelection(selectedItem);
} }
// else {
// if (saved != null) saved.deselectSelection(selectedItem);
// else if (mainActivity != null)
// mainActivity.mainHelper.deselectSelection(selectedItem);
// else if (pv != null) pv.deselectSelection(selectedItem);
// }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} }

BIN
app/src/main/res/drawable-night/comments.png

Before

Width: 200  |  Height: 200  |  Size: 6.1 KiB

BIN
app/src/main/res/drawable-night/mute.png

Before

Width: 200  |  Height: 200  |  Size: 6.8 KiB

BIN
app/src/main/res/drawable-night/video_views.png

Before

Width: 200  |  Height: 200  |  Size: 10 KiB

BIN
app/src/main/res/drawable-night/vol.png

Before

Width: 200  |  Height: 200  |  Size: 7.3 KiB

BIN
app/src/main/res/drawable/comments.png

Before

Width: 200  |  Height: 200  |  Size: 4.6 KiB

10
app/src/main/res/drawable/ic_close_24.xml

@ -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="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

10
app/src/main/res/drawable/ic_more_horiz_24.xml

@ -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="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

10
app/src/main/res/drawable/ic_open_in_new_24.xml

@ -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="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
</vector>

10
app/src/main/res/drawable/ic_outline_comments_24.xml

@ -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="M20,17.17L18.83,16H4V4h16v13.17zM20,2H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4V4c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

10
app/src/main/res/drawable/ic_outline_views_24.xml

@ -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,6.5c3.79,0 7.17,2.13 8.82,5.5 -1.65,3.37 -5.02,5.5 -8.82,5.5S4.83,15.37 3.18,12C4.83,8.63 8.21,6.5 12,6.5m0,-2C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,9.5c1.38,0 2.5,1.12 2.5,2.5s-1.12,2.5 -2.5,2.5 -2.5,-1.12 -2.5,-2.5 1.12,-2.5 2.5,-2.5m0,-2c-2.48,0 -4.5,2.02 -4.5,4.5s2.02,4.5 4.5,4.5 4.5,-2.02 4.5,-4.5 -2.02,-4.5 -4.5,-4.5z"/>
</vector>

9
app/src/main/res/drawable/ic_star_24.xml

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>

10
app/src/main/res/drawable/ic_volume_off_24.xml

@ -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="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
</vector>

10
app/src/main/res/drawable/ic_volume_up_24.xml

@ -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="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
</vector>

BIN
app/src/main/res/drawable/mute.png

Before

Width: 200  |  Height: 200  |  Size: 5.3 KiB

BIN
app/src/main/res/drawable/video_views.png

Before

Width: 200  |  Height: 200  |  Size: 6.4 KiB

BIN
app/src/main/res/drawable/vol.png

Before

Width: 200  |  Height: 200  |  Size: 5.7 KiB

107
app/src/main/res/layout/activity_main.xml

@ -1,104 +1,49 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/layout_include_toolbar" />
<awais.instagrabber.customviews.RemixDrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- Profile View -->
<include
android:id="@+id/profile_view"
layout="@layout/layout_profile_view" />
<!-- Feed View -->
<include
android:id="@+id/feed_view"
layout="@layout/layout_feed_view" />
<!-- Discover View -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/discoverLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:tag="@android:string/yes">
tools:context=".activities.MainActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways">
app:layout_scrollFlags="scroll|snap|enterAlways"
app:titleEnabled="false">
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/discoverType"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
style="@style/Widget.MaterialComponents.Toolbar.PrimarySurface"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/discover_placeholder"/>
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="none"
app:popupTheme="@style/Widget.MaterialComponents.Toolbar.PrimarySurface"
app:title="@string/app_name" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:tag="@android:string/yes"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverPosts"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_nav_host"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</awais.instagrabber.customviews.RemixDrawerLayout>
</LinearLayout>
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<LinearLayout
android:id="@+id/iconSlider"
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_feed"
app:tint="?android:textColorSecondary" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_profile"
app:tint="?android:textColorPrimary" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_discover"
app:tint="?android:textColorSecondary" />
</LinearLayout>
</FrameLayout>
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"
app:menu="@menu/main_bottom_navigation_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

104
app/src/main/res/layout/activity_mainbackup.xml

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/layout_include_toolbar" />
<awais.instagrabber.customviews.RemixDrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- Profile View -->
<include
android:id="@+id/profile_view"
layout="@layout/layout_profile_view" />
<!-- Feed View -->
<include
android:id="@+id/feed_view"
layout="@layout/layout_feed_view" />
<!-- Discover View -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/discoverLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:animateLayoutChanges="true"
android:tag="@android:string/yes">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways">
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/discoverType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/discover_placeholder"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:tag="@android:string/yes"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</awais.instagrabber.customviews.RemixDrawerLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/iconSlider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_feed"
app:tint="?android:textColorSecondary" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_profile"
app:tint="?android:textColorPrimary" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="20dp"
android:layout_height="20dp"
app:srcCompat="@drawable/ic_discover"
app:tint="?android:textColorSecondary" />
</LinearLayout>
</FrameLayout>

41
app/src/main/res/layout/activity_story_viewer.xml

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.PostViewer">
<include
android:id="@+id/toolbar"
layout="@layout/layout_include_toolbar" />
android:orientation="vertical">
<FrameLayout <FrameLayout
android:id="@+id/story_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1">
android:layout_marginTop="?attr/actionBarSize"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@id/storiesList"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.exoplayer2.ui.PlayerView <com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView" android:id="@+id/playerView"
@ -25,7 +25,7 @@
app:controller_layout_id="@layout/layout_controls" app:controller_layout_id="@layout/layout_controls"
app:repeat_toggle_modes="none" /> app:repeat_toggle_modes="none" />
<com.github.chrisbanes.photoview.PhotoView
<awais.instagrabber.customviews.drawee.ZoomableDraweeView
android:id="@+id/imageViewer" android:id="@+id/imageViewer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -42,9 +42,9 @@
android:id="@+id/postActions" android:id="@+id/postActions"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0.3"
android:background="#0000"
android:layout_gravity="bottom">
android:layout_gravity="bottom"
android:background="#0000">
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/viewStoryPost" android:id="@+id/viewStoryPost"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -54,6 +54,7 @@
android:textColor="@color/btn_green_text_color" android:textColor="@color/btn_green_text_color"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_green_background" /> app:backgroundTint="@color/btn_green_background" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/poll" android:id="@+id/poll"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -63,6 +64,7 @@
android:textColor="@color/btn_blue_text_color" android:textColor="@color/btn_blue_text_color"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" /> app:backgroundTint="@color/btn_blue_background" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/answer" android:id="@+id/answer"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -72,6 +74,7 @@
android:textColor="@color/btn_blue_text_color" android:textColor="@color/btn_blue_text_color"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" /> app:backgroundTint="@color/btn_blue_background" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/quiz" android:id="@+id/quiz"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -81,6 +84,7 @@
android:textColor="@color/btn_blue_text_color" android:textColor="@color/btn_blue_text_color"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" /> app:backgroundTint="@color/btn_blue_background" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/mention" android:id="@+id/mention"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -90,6 +94,7 @@
android:textColor="@color/btn_orange_text_color" android:textColor="@color/btn_orange_text_color"
android:visibility="gone" android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" /> app:backgroundTint="@color/btn_orange_background" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/spotify" android:id="@+id/spotify"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -107,9 +112,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingEnd="5dp"
android:paddingRight="5dp" />
</LinearLayout>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

17
app/src/main/res/layout/fragment_direct_messages_inbox.xml

@ -1,10 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefreshLayout"
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorOnPrimarySurface"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/inbox_list" android:id="@+id/inbox_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
tools:listitem="@layout/layout_include_simple_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

4
app/src/main/res/layout/fragment_direct_messages_thread.xml

@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"> android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -14,7 +15,8 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/messageList" android:id="@+id/messageList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
tools:listitem="@layout/layout_dm_base"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout <LinearLayout

43
app/src/main/res/layout/fragment_discover.xml

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="?attr/colorOnPrimarySurface">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorOnPrimarySurface"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways">
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/discoverType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/discover_placeholder"
android:spinnerMode="dialog" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_post" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

41
app/src/main/res/layout/fragment_feed.xml

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="?attr/colorOnPrimarySurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/stories_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feed_stories_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/feed_swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feed_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

304
app/src/main/res/layout/fragment_profile.xml

@ -0,0 +1,304 @@
<?xml version="1.0" encoding="utf-8"?>
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorOnPrimarySurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorOnPrimarySurface">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll">
<RelativeLayout
android:id="@+id/infoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/profileInfoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileInfo"
android:orientation="horizontal"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified"
android:layout_width="18dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/verified" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFullName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
tools:text="Austin Huang" />
</LinearLayout>
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainBiography"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileInfoText"
android:background="?android:selectableItemBackground"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="THE GLORIOUS (step)OWNER OF THIS APP" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/mainBiography"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone"
tools:text="https://austinhuang.me/" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/profileActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/mainUrl">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFollow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRestrict"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/restrict"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<!-- only for invisible private accounts -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnBlock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/block"
android:textColor="@color/btn_red_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_red_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/myActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileActions">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnTagged"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/tagged"
android:textColor="@color/btn_blue_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" />
<!-- also used as block -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/saved"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLiked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_lightpink_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/highlightsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/myActions"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/profileInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainProfileImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFollowers"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?selectableItemBackgroundBorderless"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="68\nFollowers" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFollowing"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:background="?selectableItemBackgroundBorderless"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="64\nFollowing" />
</LinearLayout>
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_post" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/privatePage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/private_page_margins"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/privatePage1"
android:layout_width="@dimen/private_page_margins"
android:layout_height="@dimen/private_page_margins"
app:srcCompat="@drawable/lock" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/privatePage2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/priv_acc"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

116
app/src/main/res/layout/item_feed_bottom.xml

@ -1,117 +1,100 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/top_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout <LinearLayout
android:id="@+id/btnComments" android:id="@+id/btnComments"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:orientation="horizontal">
android:gravity="center"
android:orientation="horizontal"
android:padding="4dp">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:layout_width="32dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/comments" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
app:srcCompat="@drawable/ic_outline_comments_24"
app:tint="?android:textColorPrimary" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/commentsCount" android:id="@+id/commentsCount"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:textColorPrimary"
android:textAppearance="?attr/textAppearanceButton"
tools:text="690000" /> tools:text="690000" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/videoViewsContainer" android:id="@+id/videoViewsContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toEndOf="@id/btnComments"
android:layout_toRightOf="@id/btnComments"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"> android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:layout_width="32dp"
android:layout_height="48dp"
app:srcCompat="@drawable/video_views" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
app:srcCompat="@drawable/ic_outline_views_24"
app:tint="?android:textColorPrimary" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvVideoViews" android:id="@+id/tvVideoViews"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="2dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:textColorPrimary"
android:textAppearance="?attr/textAppearanceButton"
tools:text="690000" /> tools:text="690000" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignEnd="@id/btnMute"
android:layout_alignRight="@id/btnMute"
android:layout_marginEnd="48dp"
android:layout_marginRight="48dp"
android:layout_toEndOf="@id/videoViewsContainer"
android:layout_toRightOf="@id/videoViewsContainer">
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:layout_marginStart="2dp"
android:layout_marginLeft="2dp"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:background="?android:attr/dividerVertical" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvPostDate" android:id="@+id/tvPostDate"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:textColorPrimary"
android:maxLines="1"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="690000" /> tools:text="690000" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/btnMute" android:id="@+id/btnMute"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignWithParentIfMissing="true"
android:layout_toStartOf="@id/btnDownload"
android:layout_toLeftOf="@id/btnDownload"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:padding="8dp"
android:visibility="gone"
app:srcCompat="@drawable/vol" />
android:padding="4dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_volume_up_24"
app:tint="?android:textColorPrimary" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/btnDownload" android:id="@+id/btnDownload"
android:layout_width="40dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:padding="4dp" android:padding="4dp"
app:srcCompat="@drawable/ic_download" />
app:srcCompat="@drawable/ic_download"
app:tint="?android:textColorPrimary" />
</LinearLayout>
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/btnDownload"
android:layout_height="0dp"
android:layout_weight="1"
android:isScrollContainer="true" android:isScrollContainer="true"
android:scrollbars="vertical" android:scrollbars="vertical"
android:scrollHorizontally="false"> android:scrollHorizontally="false">
@ -129,10 +112,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:clipToPadding="false" android:clipToPadding="false"
android:padding="12dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="BOTTOM TEXT" /> tools:text="BOTTOM TEXT" />
</FrameLayout> </FrameLayout>
</ScrollView> </ScrollView>
</RelativeLayout>
</LinearLayout>

5
app/src/main/res/layout/item_feed_photo.xml

@ -8,11 +8,6 @@
android:id="@+id/item_feed_top" android:id="@+id/item_feed_top"
layout="@layout/item_feed_top" /> layout="@layout/item_feed_top" />
<!--<com.github.chrisbanes.photoview.PhotoView-->
<!-- android:id="@+id/imageViewer"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<awais.instagrabber.customviews.drawee.ZoomableDraweeView <awais.instagrabber.customviews.drawee.ZoomableDraweeView
android:id="@+id/imageViewer" android:id="@+id/imageViewer"
android:layout_width="match_parent" android:layout_width="match_parent"

28
app/src/main/res/layout/item_feed_top.xml

@ -1,17 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
android:padding="8dp">
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"
android:layout_width="@dimen/feed_profile_size" android:layout_width="@dimen/feed_profile_size"
android:layout_height="@dimen/feed_profile_size" android:layout_height="@dimen/feed_profile_size"
app:roundAsCircle="true"
android:background="?selectableItemBackgroundBorderless" />
android:background="?selectableItemBackgroundBorderless"
app:roundAsCircle="true" />
<RelativeLayout <RelativeLayout
android:id="@+id/infoContainer" android:id="@+id/infoContainer"
@ -21,10 +23,10 @@
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:background="@null" android:background="@null"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:weightSum="2"> android:weightSum="2">
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
@ -32,8 +34,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@color/feed_text_primary_color" android:textColor="@color/feed_text_primary_color"
android:textSize="18sp" />
tools:text="username" />
<awais.instagrabber.customviews.RamboTextView <awais.instagrabber.customviews.RamboTextView
android:id="@+id/location" android:id="@+id/location"
@ -41,14 +44,17 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/title" android:layout_below="@+id/title"
android:gravity="center_vertical" android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="@color/feed_text_primary_color" android:textColor="@color/feed_text_primary_color"
android:textSize="18sp" />
tools:text="location" />
</RelativeLayout> </RelativeLayout>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/viewStoryPost" android:id="@+id/viewStoryPost"
android:layout_width="40dp"
android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
app:srcCompat="@android:drawable/ic_menu_set_as" />
app:srcCompat="@drawable/ic_open_in_new_24"
app:tint="?android:textColorPrimary" />
</LinearLayout> </LinearLayout>

13
app/src/main/res/layout/item_highlight.xml

@ -3,11 +3,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/profile_picture_size" android:layout_width="@dimen/profile_picture_size"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_margin="8dp"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:orientation="vertical"
tools:viewBindingIgnore="true">
android:orientation="vertical">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/icon" android:id="@+id/icon"
@ -20,11 +18,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:lines="1"
android:maxLines="1" android:maxLines="1"
android:padding="2dp" android:padding="2dp"
android:singleLine="true" android:singleLine="true"
android:text="@string/app_name"
android:textColor="?android:textColorPrimary"
android:textStyle="bold" />
android:textColor="?android:textColorPrimaryInverse"
android:textStyle="bold"
tools:text="@string/app_name" />
</LinearLayout> </LinearLayout>

22
app/src/main/res/layout/item_post.xml

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:foreground="?android:selectableItemBackground"
tools:viewBindingIgnore="true">
android:foreground="?android:selectableItemBackground">
<awais.instagrabber.customviews.FixedImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/postImage" android:id="@+id/postImage"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
app:actualImageScaleType="fitCenter"
app:viewAspectRatio="1"/>
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isDownloaded" android:id="@+id/isDownloaded"
@ -31,12 +29,12 @@
android:layout_margin="8dp" android:layout_margin="8dp"
app:srcCompat="@drawable/video" /> app:srcCompat="@drawable/video" />
<ProgressBar
android:id="@+id/progressView"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center"
android:visibility="gone" />
<!--<ProgressBar-->
<!-- android:id="@+id/progressView"-->
<!-- android:layout_width="45dp"-->
<!-- android:layout_height="45dp"-->
<!-- android:layout_gravity="center"-->
<!-- android:visibility="gone" />-->
<FrameLayout <FrameLayout
android:id="@+id/selectedView" android:id="@+id/selectedView"

16
app/src/main/res/layout/item_story.xml

@ -1,24 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/story_item_width" android:layout_width="@dimen/story_item_width"
android:layout_height="@dimen/story_item_height" android:layout_height="@dimen/story_item_height"
android:layout_marginStart="2dp"
android:layout_marginLeft="2dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="2dp"
android:layout_marginRight="2dp"
android:layout_marginBottom="4dp"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?android:selectableItemBackground"
tools:viewBindingIgnore="true">
android:layout_margin="8dp"
android:foreground="?android:selectableItemBackground">
<androidx.appcompat.widget.AppCompatImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/icon" android:id="@+id/icon"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="fitCenter" />
app:actualImageScaleType="fitCenter" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/selectedView" android:id="@+id/selectedView"

30
app/src/main/res/menu/main_bottom_navigation_menu.xml

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/direct_messages_nav_graph"
android:icon="@drawable/ic_send_24"
android:title="@string/title_dm" />
<item
android:id="@+id/feed_nav_graph"
android:icon="@drawable/ic_feed"
android:title="@string/feed" />
<item
android:id="@+id/profile_nav_graph"
android:icon="@drawable/ic_profile"
android:title="@string/profile" />
<item
android:id="@+id/discover_nav_graph"
android:icon="@drawable/ic_discover"
android:title="@string/title_discover" />
<!--<item-->
<!-- android:id="@+id/favouritesFragment"-->
<!-- android:icon="@drawable/ic_star_24"-->
<!-- android:title="@string/title_favorites"/>-->
<item
android:id="@+id/moreFragment"
android:icon="@drawable/ic_more_horiz_24"
android:title="@string/more" />
</menu>

18
app/src/main/res/menu/main_menu.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/favourites"
android:enabled="true"
android:icon="@drawable/ic_star_24"
android:title="@string/title_favorites"
app:showAsAction="ifRoom" />
<!--<item-->
<!-- android:id="@+id/direct_messages"-->
<!-- android:enabled="true"-->
<!-- android:icon="@drawable/ic_send_24"-->
<!-- android:title="@string/action_dms"-->
<!-- app:showAsAction="always" />-->
</menu>

11
app/src/main/res/menu/multi_select_download_menu.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/action_download"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
</menu>

17
app/src/main/res/menu/story_menu.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/action_download"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
<item
android:id="@+id/action_dms"
android:icon="@drawable/ic_send_24"
android:title="@string/action_dms"
android:titleCondensed="@string/action_dms"
app:showAsAction="ifRoom" />
</menu>

10
app/src/main/res/navigation/direct_messages_nav_graph.xml

@ -1,13 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android" <navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/direct_messages_nav_graph" android:id="@+id/direct_messages_nav_graph"
app:startDestination="@id/directMessagesInboxFragment"> app:startDestination="@id/directMessagesInboxFragment">
<fragment <fragment
android:id="@+id/directMessagesInboxFragment" android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment" android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"
android:label="DirectMessagesInboxFragment" >
android:label="@string/action_dms"
tools:layout="@layout/fragment_direct_messages_inbox">
<action <action
android:id="@+id/action_dMInboxFragment_to_dMThreadFragment" android:id="@+id/action_dMInboxFragment_to_dMThreadFragment"
app:destination="@id/directMessagesThreadFragment" /> app:destination="@id/directMessagesThreadFragment" />
@ -15,7 +17,8 @@
<fragment <fragment
android:id="@+id/directMessagesThreadFragment" android:id="@+id/directMessagesThreadFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment" android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment"
android:label="DirectMessagesThreadFragment">
android:label="DirectMessagesThreadFragment"
tools:layout="@layout/fragment_direct_messages_thread">
<argument <argument
android:name="threadId" android:name="threadId"
app:argType="string" /> app:argType="string" />
@ -29,7 +32,8 @@
<fragment <fragment
android:id="@+id/directMessagesSettingsFragment" android:id="@+id/directMessagesSettingsFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment" android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment"
android:label="DirectMessagesSettingsFragment">
android:label="DirectMessagesSettingsFragment"
tools:layout="@layout/fragment_direct_messages_settings">
<argument <argument
android:name="threadId" android:name="threadId"
app:argType="string" /> app:argType="string" />

11
app/src/main/res/navigation/discover_nav_graph.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/discover_nav_graph"
app:startDestination="@id/discoverFragment">
<fragment
android:id="@+id/discoverFragment"
android:name="awais.instagrabber.fragments.main.DiscoverFragment"
android:label="DiscoverFragment" />
</navigation>

47
app/src/main/res/navigation/feed_nav_graph.xml

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feed_nav_graph"
app:startDestination="@id/feedFragment">
<fragment
android:id="@+id/feedFragment"
android:name="awais.instagrabber.fragments.main.FeedFragment"
android:label="FeedFragment"
tools:layout="@layout/fragment_feed">
<action
android:id="@+id/action_feedFragment_to_storyViewerFragment"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_feedFragment_to_profileFragment"
app:destination="@id/profileFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/activity_story_viewer">
<argument
android:name="feedStoryIndex"
app:argType="integer"
app:nullable="false" />
<argument
android:name="highlight"
app:argType="string"
app:nullable="true" />
<argument
android:name="isHashtag"
app:argType="boolean" />
</fragment>
<fragment
android:id="@+id/profileFragment"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="ProfileFragment"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
</fragment>
</navigation>

12
app/src/main/res/navigation/profile_nav_graph.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/profile_nav_graph"
app:startDestination="@id/profileFragment">
<fragment
android:id="@+id/profileFragment"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile" />
</navigation>

2
app/src/main/res/values/dimens.xml

@ -19,4 +19,6 @@
<dimen name="message_item_profile_size">@dimen/feed_profile_size</dimen> <dimen name="message_item_profile_size">@dimen/feed_profile_size</dimen>
<dimen name="dm_media_img_max_height">500dp</dimen> <dimen name="dm_media_img_max_height">500dp</dimen>
<dimen name="dm_message_card_radius">8dp</dimen> <dimen name="dm_message_card_radius">8dp</dimen>
<dimen name="feed_item_bottom_icon_size">32dp</dimen>
</resources> </resources>

5
app/src/main/res/values/strings.xml

@ -234,4 +234,9 @@
<string name="activity_count_usertags">%d usertags</string> <string name="activity_count_usertags">%d usertags</string>
<string name="activity_count_likes">%d likes</string> <string name="activity_count_likes">%d likes</string>
<string name="activity_notloggedin">You logged out before clicking this notification?!</string> <string name="activity_notloggedin">You logged out before clicking this notification?!</string>
<string name="feed">Feed</string>
<string name="profile">Profile</string>
<string name="more">More</string>
<string name="title_dm">DM</string>
<string name="number_selected">%d selected</string>
</resources> </resources>

17
app/src/main/res/values/styles.xml

@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="bottomSheetDialogTheme">@style/AppTheme.BottomSheetDialog</item>
<item name="android:windowAnimationStyle">@style/AppTheme.WindowAnimationTransition</item>
</style>
<style name="CompletelyTransparent" parent="Theme.AppCompat.NoActionBar"> <style name="CompletelyTransparent" parent="Theme.AppCompat.NoActionBar">
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:backgroundDimEnabled">false</item> <item name="android:backgroundDimEnabled">false</item>
@ -34,4 +29,16 @@
<item name="android:windowEnterAnimation">@android:anim/fade_in</item> <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
<item name="android:windowExitAnimation">@android:anim/fade_out</item> <item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style> </style>
<style name="AppTheme.BottomNavigationView" parent="@style/Widget.MaterialComponents.BottomNavigationView.PrimarySurface">
<item name="materialThemeOverlay">@style/ThemeOverlay.App.BottomNavigationView</item>
</style>
<style name="ThemeOverlay.App.BottomNavigationView" parent="" />
<style name="Widget.App.ActionMode" parent="Widget.AppCompat.ActionMode">
<item name="titleTextStyle">?attr/textAppearanceHeadline6</item>
<item name="subtitleTextStyle">?attr/textAppearanceSubtitle1</item>
<item name="background">?attr/colorPrimary</item>
</style>
</resources> </resources>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save