From 3f6c74d6717ae49618ed08cc94318f7127a534a4 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 29 Aug 2020 17:01:42 +0900 Subject: [PATCH] Tabs! Fragments! WIP! --- app/build.gradle | 31 +- .../java/awais/instagrabber/MainHelper.java | 236 ++--- .../instagrabber/activities/MainActivity.java | 647 ++---------- .../activities/MainActivityBackup.java | 607 ++++++++++++ .../instagrabber/activities/PostViewer.java | 10 +- .../activities/ProfileViewer.java | 78 +- .../instagrabber/activities/SavedViewer.java | 180 ++-- .../instagrabber/activities/StoryViewer.java | 936 ++++-------------- .../adapters/DiscoverAdapter.java | 77 +- .../instagrabber/adapters/FeedAdapter.java | 13 +- .../adapters/FeedStoriesAdapter.java | 58 +- .../adapters/HighlightsAdapter.java | 17 +- .../adapters/MultiSelectListAdapter.java | 106 ++ .../instagrabber/adapters/PostsAdapter.java | 93 +- .../instagrabber/adapters/StoriesAdapter.java | 108 +- .../viewholder/DiscoverViewHolder.java | 10 +- .../viewholder/HighlightViewHolder.java | 37 +- .../adapters/viewholder/PostViewHolder.java | 40 +- .../viewholder/feed/FeedItemViewHolder.java | 7 - .../viewholder/feed/FeedPhotoViewHolder.java | 16 +- .../viewholder/feed/FeedSliderViewHolder.java | 4 +- .../viewholder/feed/FeedVideoViewHolder.java | 7 +- .../instagrabber/asyncs/CommentAction.java | 81 ++ .../instagrabber/asyncs/PostsFetcher.java | 18 +- .../awais/instagrabber/asyncs/QuizAction.java | 88 ++ .../instagrabber/asyncs/RespondAction.java | 87 ++ .../awais/instagrabber/asyncs/SeenAction.java | 51 + .../awais/instagrabber/asyncs/VoteAction.java | 69 ++ .../asyncs/i/iStoryStatusFetcher.java | 63 +- .../PrimaryActionModeCallback.java | 71 ++ .../drawee/ZoomableDraweeView.java | 2 + .../CustomHideBottomViewOnScrollBehavior.java | 33 + .../helpers/NestedCoordinatorLayout.java | 154 +++ .../dialogs/QuickAccessDialog.java | 5 +- .../directdownload/MultiDirectDialog.java | 46 +- .../fragments/StoryViewerFragment.java | 673 +++++++++++++ .../DirectMessageInboxFragment.java | 12 +- .../DirectMessageThreadFragment.java | 31 +- .../fragments/main/DiscoverFragment.java | 241 +++++ .../fragments/main/FeedFragment.java | 345 +++++++ .../fragments/main/ProfileActionListener.java | 57 ++ .../fragments/main/ProfileFragment.java | 532 ++++++++++ .../viewmodels/DiscoverItemViewModel.java | 19 + .../main/viewmodels/FeedStoriesViewModel.java | 19 + .../main/viewmodels/FeedViewModel.java | 20 + .../viewmodels/ProfilePostsViewModel.java | 19 + .../main/viewmodels/StoriesViewModel.java | 19 + .../instagrabber/models/BasePostModel.java | 17 +- .../awais/instagrabber/models/PostModel.java | 2 + .../repositories/ProfileRepository.java | 4 + .../repositories/StoriesRepository.java | 18 + .../services/AddCookiesInterceptor.java | 24 + .../instagrabber/services/BaseService.java | 24 + .../instagrabber/services/FeedService.java | 4 + .../instagrabber/services/ProfileService.java | 35 + .../services/ServiceCallback.java | 7 + .../instagrabber/services/StoriesService.java | 277 ++++++ .../utils/NavigationExtensions.java | 207 ++++ .../java/awais/instagrabber/utils/Utils.java | 24 +- app/src/main/res/drawable-night/comments.png | Bin 6242 -> 0 bytes app/src/main/res/drawable-night/mute.png | Bin 6950 -> 0 bytes .../main/res/drawable-night/video_views.png | Bin 10342 -> 0 bytes app/src/main/res/drawable-night/vol.png | Bin 7514 -> 0 bytes app/src/main/res/drawable/comments.png | Bin 4755 -> 0 bytes app/src/main/res/drawable/ic_close_24.xml | 10 + .../main/res/drawable/ic_more_horiz_24.xml | 10 + .../main/res/drawable/ic_open_in_new_24.xml | 10 + .../res/drawable/ic_outline_comments_24.xml | 10 + .../main/res/drawable/ic_outline_views_24.xml | 10 + app/src/main/res/drawable/ic_star_24.xml | 9 + .../main/res/drawable/ic_volume_off_24.xml | 10 + app/src/main/res/drawable/ic_volume_up_24.xml | 10 + app/src/main/res/drawable/mute.png | Bin 5408 -> 0 bytes app/src/main/res/drawable/video_views.png | Bin 6578 -> 0 bytes app/src/main/res/drawable/vol.png | Bin 5804 -> 0 bytes app/src/main/res/layout/activity_main.xml | 129 +-- .../main/res/layout/activity_mainbackup.xml | 104 ++ .../main/res/layout/activity_story_viewer.xml | 41 +- .../layout/fragment_direct_messages_inbox.xml | 23 +- .../fragment_direct_messages_thread.xml | 4 +- app/src/main/res/layout/fragment_discover.xml | 43 + app/src/main/res/layout/fragment_feed.xml | 41 + app/src/main/res/layout/fragment_profile.xml | 304 ++++++ app/src/main/res/layout/item_feed_bottom.xml | 174 ++-- app/src/main/res/layout/item_feed_photo.xml | 5 - app/src/main/res/layout/item_feed_top.xml | 28 +- app/src/main/res/layout/item_highlight.xml | 13 +- app/src/main/res/layout/item_post.xml | 22 +- app/src/main/res/layout/item_story.xml | 16 +- .../res/menu/main_bottom_navigation_menu.xml | 30 + app/src/main/res/menu/main_menu.xml | 18 + .../res/menu/multi_select_download_menu.xml | 11 + app/src/main/res/menu/story_menu.xml | 17 + .../navigation/direct_messages_nav_graph.xml | 10 +- .../res/navigation/discover_nav_graph.xml | 11 + .../main/res/navigation/feed_nav_graph.xml | 47 + .../main/res/navigation/profile_nav_graph.xml | 12 + app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/styles.xml | 17 +- app/src/main/res/values/themes.xml | 13 +- 101 files changed, 5734 insertions(+), 2201 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/activities/MainActivityBackup.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/MultiSelectListAdapter.java create mode 100644 app/src/main/java/awais/instagrabber/asyncs/CommentAction.java create mode 100644 app/src/main/java/awais/instagrabber/asyncs/QuizAction.java create mode 100644 app/src/main/java/awais/instagrabber/asyncs/RespondAction.java create mode 100644 app/src/main/java/awais/instagrabber/asyncs/SeenAction.java create mode 100644 app/src/main/java/awais/instagrabber/asyncs/VoteAction.java create mode 100644 app/src/main/java/awais/instagrabber/customviews/PrimaryActionModeCallback.java create mode 100644 app/src/main/java/awais/instagrabber/customviews/helpers/CustomHideBottomViewOnScrollBehavior.java create mode 100644 app/src/main/java/awais/instagrabber/customviews/helpers/NestedCoordinatorLayout.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/ProfileActionListener.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/viewmodels/DiscoverItemViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedStoriesViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/viewmodels/ProfilePostsViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/main/viewmodels/StoriesViewModel.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java create mode 100644 app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java create mode 100644 app/src/main/java/awais/instagrabber/services/BaseService.java create mode 100644 app/src/main/java/awais/instagrabber/services/FeedService.java create mode 100644 app/src/main/java/awais/instagrabber/services/ProfileService.java create mode 100644 app/src/main/java/awais/instagrabber/services/ServiceCallback.java create mode 100644 app/src/main/java/awais/instagrabber/services/StoriesService.java create mode 100644 app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java delete mode 100644 app/src/main/res/drawable-night/comments.png delete mode 100644 app/src/main/res/drawable-night/mute.png delete mode 100644 app/src/main/res/drawable-night/video_views.png delete mode 100644 app/src/main/res/drawable-night/vol.png delete mode 100644 app/src/main/res/drawable/comments.png create mode 100644 app/src/main/res/drawable/ic_close_24.xml create mode 100644 app/src/main/res/drawable/ic_more_horiz_24.xml create mode 100644 app/src/main/res/drawable/ic_open_in_new_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_comments_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_views_24.xml create mode 100644 app/src/main/res/drawable/ic_star_24.xml create mode 100644 app/src/main/res/drawable/ic_volume_off_24.xml create mode 100644 app/src/main/res/drawable/ic_volume_up_24.xml delete mode 100644 app/src/main/res/drawable/mute.png delete mode 100644 app/src/main/res/drawable/video_views.png delete mode 100644 app/src/main/res/drawable/vol.png create mode 100644 app/src/main/res/layout/activity_mainbackup.xml create mode 100644 app/src/main/res/layout/fragment_discover.xml create mode 100644 app/src/main/res/layout/fragment_feed.xml create mode 100644 app/src/main/res/layout/fragment_profile.xml create mode 100644 app/src/main/res/menu/main_bottom_navigation_menu.xml create mode 100644 app/src/main/res/menu/main_menu.xml create mode 100644 app/src/main/res/menu/multi_select_download_menu.xml create mode 100644 app/src/main/res/menu/story_menu.xml create mode 100644 app/src/main/res/navigation/discover_nav_graph.xml create mode 100644 app/src/main/res/navigation/feed_nav_graph.xml create mode 100644 app/src/main/res/navigation/profile_nav_graph.xml diff --git a/app/build.gradle b/app/build.gradle index 284887eb..7295a592 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,21 +37,28 @@ android { } 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" + + 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-ui:$nav_version" + implementation "androidx.constraintlayout:constraintlayout:2.0.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 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.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.facebook.fresco:fresco:2.3.0' } diff --git a/app/src/main/java/awais/instagrabber/MainHelper.java b/app/src/main/java/awais/instagrabber/MainHelper.java index 64f3844e..a5d66116 100755 --- a/app/src/main/java/awais/instagrabber/MainHelper.java +++ b/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.FollowViewer; -import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.activities.MainActivityBackup; import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.SavedViewer; -import awais.instagrabber.activities.StoryViewer; import awais.instagrabber.adapters.DiscoverAdapter; import awais.instagrabber.adapters.FeedAdapter; -import awais.instagrabber.adapters.FeedStoriesAdapter; import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; import awais.instagrabber.asyncs.DiscoverFetcher; @@ -111,8 +109,14 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { private static AsyncTask currentlyExecuting; private AsyncTask prevStoriesFetcher; 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 final boolean autoloadPosts; @@ -293,7 +297,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { @Override public void onResult(final FeedStoryModel[] result) { - feedStoriesAdapter.setData(result); + // feedStoriesAdapter.setData(result); if (result != null && result.length > 0) { mainActivity.mainBinding.feedView.feedStories.setVisibility(View.VISIBLE); 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) { 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) -> { - if (MainActivity.scanHack != null) MainActivity.scanHack.onResult(text); + if (MainActivityBackup.scanHack != null) MainActivityBackup.scanHack.onResult(text); }).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 final View collapsingToolbar; private final RecyclerLazyLoader lazyLoader; @@ -345,7 +349,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { private RequestManager glide; private VideoAwareRecyclerScroller videoAwareRecyclerScroller; - public MainHelper(@NonNull final MainActivity mainActivity) { + public MainHelper(@NonNull final MainActivityBackup mainActivity) { stopCurrentExecutor(); this.mainActivity = mainActivity; @@ -522,26 +526,27 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(mainActivity, Utils.convertDpToPx(110)); mainActivity.mainBinding.profileView.mainPosts.setLayoutManager(layoutManager); 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) -> { if ((!autoloadPosts || isHashtag) && hasNextPage) { @@ -643,18 +648,18 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { private void setupFeed() { mainActivity.mainBinding.feedView.feedStories.setLayoutManager(new LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false)); - mainActivity.mainBinding.feedView.feedStories.setAdapter(feedStoriesAdapter); + // mainActivity.mainBinding.feedView.feedStories.setAdapter(feedStoriesAdapter); refreshFeedStories(); final LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity); mainActivity.mainBinding.feedView.feedPosts.setHasFixedSize(true); 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) .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.scanHack.onResult(text); + MainActivityBackup.scanHack.onResult(text); } }).show())); @@ -727,25 +732,25 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { 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) -> { if (discoverHasMore) { @@ -811,7 +816,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { mainActivity.allItems.clear(); mainActivity.selectedItems.clear(); if (postsAdapter != null) { - postsAdapter.isSelecting = false; + // postsAdapter.isSelecting = false; postsAdapter.notifyDataSetChanged(); } mainActivity.mainBinding.profileView.appBarLayout.setExpanded(true, true); @@ -1279,12 +1284,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { } 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) { @@ -1297,33 +1302,34 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { } 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() { - 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; + // 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; } public void deselectSelection(final BasePostModel postModel) { @@ -1364,7 +1370,8 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { private final View.OnClickListener profileActionListener = new View.OnClickListener() { @Override 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) { Utils.dataBox.delFavorite(new DataBox.FavoriteModel(mainActivity.userQuery, Long.parseLong(Utils.dataBox.getFavorite(mainActivity.userQuery).split("/")[1]), @@ -1378,7 +1385,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { new ProfileAction().execute("follow"); } else if (v == mainActivity.mainBinding.profileView.btnRestrict && isLoggedIn) { new ProfileAction().execute("restrict"); - } else if (v == mainActivity.mainBinding.profileView.btnSaved && !iamme) { + } else if (v == mainActivity.mainBinding.profileView.btnSaved && !isSelf) { new ProfileAction().execute("block"); } else if (v == mainActivity.mainBinding.profileView.btnFollowTag) { new ProfileAction().execute("followtag"); @@ -1407,17 +1414,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { protected Void doInBackground(String... rawAction) { 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 { final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); urlConnection.setRequestMethod("POST"); diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index eddb3a3e..70b61dba 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -1,603 +1,132 @@ 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 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.Arrays; 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.customviews.helpers.CustomHideBottomViewOnScrollBehavior; 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.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.NavigationExtensions.setupWithNavController; 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 scanHack; - public static ItemGetter itemGetter; +public class MainActivity extends BaseLanguageActivity { + private static final String TAG = "MainActivity"; - public final ArrayList allItems = new ArrayList<>(); - public final ArrayList feedItems = new ArrayList<>(); - public final ArrayList discoverItems = new ArrayList<>(); - public final ArrayList selectedItems = new ArrayList<>(); - public final ArrayList 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(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 profileDialogAdapter; - private DialogInterface.OnClickListener profileDialogListener; - private Stack queriesStack; - private DataBox.CookieModel cookieModel; - private Runnable runnable; - private Handler handler; - private boolean isLoggedIn; + private ActivityMainBinding binding; + private LiveData currentNavControllerLiveData; @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); + 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; + final Toolbar toolbar = binding.toolbar; + setSupportActionBar(toolbar); - 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 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); + if (savedInstanceState == null) { + setupBottomNavigationBar(); } - 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); + setupScrollingListener(); + } + + private void setupScrollingListener() { + final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams(); + layoutParams.setBehavior(new CustomHideBottomViewOnScrollBehavior()); + binding.bottomNavView.requestLayout(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + private void setupBottomNavigationBar() { + final List 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 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 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; } + removeScrollingBehaviour(); + binding.bottomNavView.setVisibility(View.GONE); }); - - 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 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); - } + private void setScrollingBehaviour() { + final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams(); + layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior()); + binding.mainNavHost.requestLayout(); } - @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); + private void removeScrollingBehaviour() { + final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams(); + layoutParams.setBehavior(null); + binding.mainNavHost.requestLayout(); } @Override protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - userQuery = savedInstanceState.getString("query"); - setStack(savedInstanceState); + setupBottomNavigationBar(); } @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 fetchListener = new FetchListener() { - @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) 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; } diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivityBackup.java b/app/src/main/java/awais/instagrabber/activities/MainActivityBackup.java new file mode 100644 index 00000000..e1ea8536 --- /dev/null +++ b/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 scanHack; + public static ItemGetter itemGetter; + + public final ArrayList allItems = new ArrayList<>(); + public final ArrayList feedItems = new ArrayList<>(); + public final ArrayList discoverItems = new ArrayList<>(); + public final ArrayList selectedItems = new ArrayList<>(); + public final ArrayList 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 profileDialogAdapter; + private DialogInterface.OnClickListener profileDialogListener; + private Stack 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 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 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 fetchListener = new FetchListener() { + @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) 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/PostViewer.java b/app/src/main/java/awais/instagrabber/activities/PostViewer.java index 1ef75aa9..2a7297b3 100755 --- a/app/src/main/java/awais/instagrabber/activities/PostViewer.java +++ b/app/src/main/java/awais/instagrabber/activities/PostViewer.java @@ -161,7 +161,7 @@ public final class PostViewer extends BaseLanguageActivity { if (player != null) { final float intVol = player.getVolume() == 0f ? 1f : 0f; 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; } } else if (v == viewerBinding.btnLike) { @@ -307,7 +307,7 @@ public final class PostViewer extends BaseLanguageActivity { isFromShare = postModel.getPosition() == -1 || postIdNull; 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.setAdapter(mediaAdapter); @@ -320,8 +320,8 @@ public final class PostViewer extends BaseLanguageActivity { if (itemGetType == ItemGetType.SAVED_ITEMS && SavedViewer.itemGetter != null) { itemGetterItems = SavedViewer.itemGetter.get(itemGetType); 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); } else { itemGetterItems = null; @@ -449,7 +449,7 @@ public final class PostViewer extends BaseLanguageActivity { player.prepare(mediaSource); 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); } diff --git a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java b/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java index e44181a5..2ed18177 100755 --- a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java +++ b/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.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; -import awais.instagrabber.models.enums.ItemGetType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.Utils; @@ -140,13 +139,13 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe 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(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(); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } @@ -188,11 +187,12 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe newintent = 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 - 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); @@ -213,26 +213,26 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(ProfileViewer.this, Utils.convertDpToPx(110)); profileBinding.profileView.mainPosts.setLayoutManager(layoutManager); 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) -> { if ((!autoloadPosts || isHashtag) && hasNextPage) { @@ -279,7 +279,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe allItems.clear(); selectedItems.clear(); if (postsAdapter != null) { - postsAdapter.isSelecting = false; + // postsAdapter.isSelecting = false; postsAdapter.notifyDataSetChanged(); } profileBinding.profileView.appBarLayout.setExpanded(true, true); @@ -776,11 +776,11 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe } 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 diff --git a/app/src/main/java/awais/instagrabber/activities/SavedViewer.java b/app/src/main/java/awais/instagrabber/activities/SavedViewer.java index 0ec94cd2..a02a1b72 100755 --- a/app/src/main/java/awais/instagrabber/activities/SavedViewer.java +++ b/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.Bundle; import android.util.Log; +import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.Toast; +import androidx.activity.OnBackPressedCallback; +import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.i.iLikedFetcher; +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.ActivitySavedBinding; +import awais.instagrabber.fragments.main.viewmodels.ProfilePostsViewModel; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.ItemGetter; -import awais.instagrabber.models.BasePostModel; import awais.instagrabber.models.PostModel; import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.ItemGetType; @@ -48,22 +54,61 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr //private CommentModel commentModel; private ActivitySavedBinding savedBinding; private String action, username, endCursor; - private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); private RecyclerLazyLoader lazyLoader; private Resources resources; private ArrayList selectedItems = new ArrayList<>(); - private final ArrayList 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 postsFetchListener = new FetchListener() { @Override public void onResult(final PostModel[] result) { - final int oldSize = allItems.size(); if (result != null) { - allItems.addAll(Arrays.asList(result)); - - postsAdapter.notifyItemRangeInserted(oldSize, result.length); - + final List current = profilePostsViewModel.getList().getValue(); + final List resultList = Arrays.asList(result); + if (current == null) { + profilePostsViewModel.getList().postValue(resultList); + } else { + final List currentCopy = new ArrayList<>(current); + currentCopy.addAll(resultList); + profilePostsViewModel.getList().postValue(currentCopy); + } savedBinding.mainPosts.post(() -> { savedBinding.mainPosts.setNestedScrollingEnabled(true); savedBinding.mainPosts.setVisibility(View.VISIBLE); @@ -85,13 +130,12 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr } model.setPageCursor(false, null); } - } - else { + } else { 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; } - savedBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(allItems, v -> { - final Object tag = v.getTag(); - if (tag instanceof PostModel) { - final PostModel postModel = (PostModel) tag; + 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_USER, username) + .putExtra(Constants.EXTRAS_TYPE, ItemGetType.SAVED_ITEMS)); - if (postsAdapter.isSelecting) toggleSelection(postModel); - else startActivity(new Intent(this, PostViewer.class) - .putExtra(Constants.EXTRAS_INDEX, postModel.getPosition()) - .putExtra(Constants.EXTRAS_POST, postModel) - .putExtra(Constants.EXTRAS_USER, username) - .putExtra(Constants.EXTRAS_TYPE, ItemGetType.SAVED_ITEMS)); - } - }, v -> { - final Object tag = v.getTag(); - if (tag instanceof PostModel) { - postsAdapter.isSelecting = true; - toggleSelection((PostModel) tag); + }, (model, position) -> { + if (!postsAdapter.isSelecting()) { + checkAndResetAction(); + return true; } + 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; - })); + }); + savedBinding.mainPosts.setAdapter(postsAdapter); + profilePostsViewModel.getList().observe(this, postsAdapter::submitList); savedBinding.swipeRefreshLayout.setRefreshing(true); setSupportActionBar(savedBinding.toolbar.toolbar); savedBinding.toolbar.toolbar.setTitle((action.charAt(0) == '$' ? R.string.saved : @@ -149,27 +203,30 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr stopCurrentExecutor(); currentlyExecuting = action.charAt(0) == '^' - ? new iLikedFetcher(endCursor, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - : new PostsFetcher(action, endCursor, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + ? new iLikedFetcher(endCursor, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + : new PostsFetcher(action, endCursor, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); endCursor = null; } }); savedBinding.mainPosts.addOnScrollListener(lazyLoader); itemGetter = itemGetType -> { - if (itemGetType == ItemGetType.SAVED_ITEMS) return allItems; + if (itemGetType == ItemGetType.SAVED_ITEMS) + return profilePostsViewModel.getList().getValue(); 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 public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.saved, menu); - downloadAction = menu.findItem(R.id.downloadAction); + final MenuItem downloadAction = menu.findItem(R.id.downloadAction); downloadAction.setVisible(false); menu.findItem(R.id.favouriteAction).setVisible(false); @@ -183,27 +240,21 @@ public final class SavedViewer extends BaseLanguageActivity implements SwipeRefr 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 public void onRefresh() { if (lazyLoader != null) lazyLoader.resetState(); stopCurrentExecutor(); - allItems.clear(); + profilePostsViewModel.getList().postValue(Collections.emptyList()); selectedItems.clear(); if (postsAdapter != null) { - postsAdapter.isSelecting = false; + // postsAdapter.isSelecting = false; postsAdapter.notifyDataSetChanged(); } 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 @@ -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; } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java b/app/src/main/java/awais/instagrabber/activities/StoryViewer.java index 2385b557..dd63ba7a 100755 --- a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java +++ b/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() { - @Override - public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target target, final boolean isFirstResource) { - storyViewerBinding.progressView.setVisibility(View.GONE); - return false; - } - - @Override - public boolean onResourceReady(final Drawable resource, final Object model, final Target 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 { - 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 { - 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 { - 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 { - 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 { - 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; - } - } -} \ No newline at end of file +// 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; +// } +// +// } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java index ebb0231c..052451ff 100755 --- a/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java @@ -1,87 +1,56 @@ package awais.instagrabber.adapters; -import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; 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.adapters.viewholder.DiscoverViewHolder; import awais.instagrabber.models.DiscoverItemModel; import awais.instagrabber.models.enums.MediaItemType; -public final class DiscoverAdapter extends RecyclerView.Adapter { - private final ArrayList discoverItemModels; - private final View.OnClickListener clickListener; - private final View.OnLongClickListener longClickListener; - private LayoutInflater layoutInflater; - public boolean isSelecting = false; +public final class DiscoverAdapter extends MultiSelectListAdapter { - public DiscoverAdapter(final ArrayList discoverItemModels, final View.OnClickListener clickListener, - final View.OnLongClickListener longClickListener) { - this.discoverItemModels = discoverItemModels; - this.longClickListener = longClickListener; - this.clickListener = clickListener; + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) { + return oldItem.getPostId().equals(newItem.getPostId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final DiscoverItemModel oldItem, @NonNull final DiscoverItemModel newItem) { + return oldItem.getPostId().equals(newItem.getPostId()); + } + }; + + public DiscoverAdapter(final OnItemClickListener clickListener, + final OnItemLongClickListener longClickListener) { + super(diffCallback, clickListener, longClickListener); } @NonNull @Override 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)); } @Override public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) { - final DiscoverItemModel itemModel = discoverItemModels.get(position); + final DiscoverItemModel itemModel = getItem(position); if (itemModel != null) { itemModel.setPosition(position); 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(); - - 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.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE); - holder.progressView.setVisibility(View.VISIBLE); - - Glide.with(layoutInflater.getContext()).load(itemModel.getDisplayUrl()).listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target target, final boolean isFirstResource) { - holder.progressView.setVisibility(View.GONE); - return false; - } - - @Override - public boolean onResourceReady(final Drawable resource, final Object model, final Target target, final DataSource dataSource, final boolean isFirstResource) { - holder.progressView.setVisibility(View.GONE); - return false; - } - }).into(holder.postImage); - + holder.postImage.setImageURI(itemModel.getDisplayUrl()); } } - - @Override - public int getItemCount() { - return discoverItemModels == null ? 0 : discoverItemModels.size(); - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java index c3209451..7ed66356 100755 --- a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java +++ b/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.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.FeedPhotoViewHolder; import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder; @@ -27,11 +24,8 @@ import awais.instagrabber.utils.Utils; public final class FeedAdapter extends ListAdapter { private static final String TAG = "FeedAdapter"; - // private final static String ellipsize = "… more"; - private final RequestManager glide; private final View.OnClickListener clickListener; private final MentionClickListener mentionClickListener; - public SimpleExoPlayer pagerPlayer; private final View.OnLongClickListener longClickListener = v -> { final Object tag; if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) @@ -51,11 +45,10 @@ public final class FeedAdapter extends ListAdapter { - private final View.OnClickListener clickListener; - private LayoutInflater layoutInflater; - private FeedStoryModel[] feedStoryModels; +public final class FeedStoriesAdapter extends ListAdapter { + private final OnFeedStoryClickListener listener; - public FeedStoriesAdapter(final FeedStoryModel[] feedStoryModels, final View.OnClickListener clickListener) { - this.feedStoryModels = feedStoryModels; - this.clickListener = clickListener; + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @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 @Override 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 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); } } diff --git a/app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java index af7460f7..a8d15a4b 100755 --- a/app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java @@ -27,18 +27,19 @@ public final class HighlightsAdapter extends RecyclerView.Adapter extends ListAdapter { + + private boolean isSelecting = false; + private OnItemClickListener clickListener; + private OnItemLongClickListener longClickListener; + + private final List selectedItems = new ArrayList<>(); + protected final OnItemClickListener internalOnItemClickListener = (item, position) -> { + if (isSelecting) { + toggleSelection(item, position); + } + if (clickListener == null) { + return; + } + clickListener.onItemClick(item, position); + }; + protected final OnItemLongClickListener 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 diffCallback, + final OnItemClickListener clickListener, + final OnItemLongClickListener longClickListener) { + super(diffCallback); + this.clickListener = clickListener; + this.longClickListener = longClickListener; + } + + protected MultiSelectListAdapter(@NonNull final AsyncDifferConfig config, + final OnItemClickListener clickListener, + final OnItemLongClickListener 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 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 { + void onItemClick(T item, int position); + } + + public interface OnItemLongClickListener { + boolean onItemLongClick(T item, int position); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java index 7a528f65..9365357e 100755 --- a/app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java @@ -1,92 +1,45 @@ package awais.instagrabber.adapters; -import android.graphics.drawable.Drawable; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; 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.databinding.ItemPostBinding; import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.enums.MediaItemType; -public final class PostsAdapter extends RecyclerView.Adapter { - private final ArrayList postModels; - private final View.OnClickListener clickListener; - private final View.OnLongClickListener longClickListener; - private LayoutInflater layoutInflater; - public boolean isSelecting = false; +public final class PostsAdapter extends MultiSelectListAdapter { - public PostsAdapter(final ArrayList postModels, final View.OnClickListener clickListener, - final View.OnLongClickListener longClickListener) { - this.postModels = postModels; - this.clickListener = clickListener; - this.longClickListener = longClickListener; + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final PostModel oldItem, @NonNull final PostModel newItem) { + return oldItem.getPostId().equals(newItem.getPostId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final PostModel oldItem, @NonNull final PostModel newItem) { + return oldItem.getPostId().equals(newItem.getPostId()); + } + }; + + public PostsAdapter(final OnItemClickListener clickListener, + final OnItemLongClickListener longClickListener) { + super(diffCallback, clickListener, longClickListener); } @NonNull @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)); + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final ItemPostBinding binding = ItemPostBinding.inflate(layoutInflater, parent, false); + return new PostViewHolder(binding); } @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); - - glideRequestManager.load(postModel.getThumbnailUrl()).listener(new RequestListener() { - @Override - public boolean onResourceReady(final Drawable resource, final Object model, final Target target, final DataSource dataSource, final boolean isFirstResource) { - holder.progressView.setVisibility(View.GONE); - return false; - } - - @Override - public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target target, final boolean isFirstResource) { - holder.progressView.setVisibility(View.GONE); - // glideRequestManager.load(postModel.getDisplayUrl()).into(holder.postImage); - return false; - } - }).into(holder.postImage); - } - } - - @Override - public int getItemCount() { - return postModels == null ? 0 : postModels.size(); + final PostModel postModel = getItem(position); + holder.bind(postModel, position, internalOnItemClickListener, internalOnLongItemClickListener); } } diff --git a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java index 411fb98e..9afc52d3 100755 --- a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java @@ -1,84 +1,80 @@ package awais.instagrabber.adapters; -import android.content.Context; -import android.content.res.Resources; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; 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; -public final class StoriesAdapter extends RecyclerView.Adapter { - private final View.OnClickListener clickListener; - private LayoutInflater layoutInflater; - private StoryModel[] storyModels; - private Resources resources; - private int width, height; +public final class StoriesAdapter extends ListAdapter { + private final OnItemClickListener onItemClickListener; - public StoriesAdapter(final StoryModel[] storyModels, final View.OnClickListener clickListener) { - this.storyModels = storyModels; - this.clickListener = clickListener; + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) { + return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); + } + + @Override + public boolean areContentsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) { + return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); + } + }; + + public StoriesAdapter(final OnItemClickListener onItemClickListener) { + super(diffCallback); + this.onItemClickListener = onItemClickListener; } @NonNull @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)); + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + final ItemStoryBinding binding = ItemStoryBinding.inflate(layoutInflater, parent, false); + return new StoryViewHolder(binding); } @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 void setData(final StoryModel[] storyModels) { - this.storyModels = storyModels; - notifyDataSetChanged(); - } - - public StoryModel getItemAt(final int position) { - return storyModels == null ? null : storyModels[position]; - } - - @Override - public int getItemCount() { - return storyModels == null ? 0 : storyModels.length; + final StoryModel storyModel = getItem(position); + holder.bind(storyModel, position, onItemClickListener); } public final static class StoryViewHolder extends RecyclerView.ViewHolder { - public final ImageView icon, selectedView; + private final ItemStoryBinding binding; - public StoryViewHolder(@NonNull final View itemView) { - super(itemView); - selectedView = itemView.findViewById(R.id.selectedView); - icon = itemView.findViewById(R.id.icon); + public StoryViewHolder(final ItemStoryBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + 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); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java index 6795ceb6..1dbd28a0 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java @@ -6,17 +6,21 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import com.facebook.drawee.view.SimpleDraweeView; + import awais.instagrabber.R; 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) { super(itemView); typeIcon = itemView.findViewById(R.id.typeIcon); postImage = itemView.findViewById(R.id.postImage); selectedView = itemView.findViewById(R.id.selectedView); - progressView = itemView.findViewById(R.id.progressView); + // progressView = itemView.findViewById(R.id.progressView); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java index 249f2f98..a412c319 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java @@ -1,22 +1,33 @@ 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 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 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); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java index f76778b8..01e614ba 100755 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java @@ -1,23 +1,43 @@ package awais.instagrabber.adapters.viewholder; import android.view.View; -import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; 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 ImageView postImage, typeIcon; - public final View selectedView, progressView, isDownloaded; + private final ItemPostBinding binding; - 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); + public PostViewHolder(@NonNull final ItemPostBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final PostModel postModel, + final int position, + final OnItemClickListener clickListener, + final OnItemLongClickListener 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()); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java index 6b85b9fa..b4b842e4 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java +++ b/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.recyclerview.widget.RecyclerView; -import com.bumptech.glide.RequestManager; - import org.json.JSONObject; import awais.instagrabber.R; @@ -31,8 +29,6 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { private final ItemFeedBottomBinding bottomBinding; private final MentionClickListener mentionClickListener; - boolean captionExpanded = false; - public FeedItemViewHolder(@NonNull final View root, final ItemFeedTopBinding topBinding, final ItemFeedBottomBinding bottomBinding, @@ -64,9 +60,6 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { bottomBinding.btnComments.setTag(feedModel); final ProfileModel profileModel = feedModel.getProfileModel(); if (profileModel != null) { - // glide.load(profileModel.getSdProfilePic()) - // .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) - // .into(topBinding.ivProfilePic); topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic()); final int titleLen = profileModel.getUsername().length() + 1; final SpannableString spannableString = new SpannableString("@" + profileModel.getUsername()); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java index 324676e9..2e8c33bb 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java @@ -1,14 +1,11 @@ package awais.instagrabber.adapters.viewholder.feed; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; -import com.bumptech.glide.RequestManager; import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchy; @@ -25,22 +22,15 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder { private static final String TAG = "FeedPhotoViewHolder"; private final ItemFeedPhotoBinding binding; - private final RequestManager glide; - private final ColorDrawable drawable; - // private final PipelineDraweeControllerBuilder controllerBuilder; - // private final CustomTarget customTarget; public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding, - final RequestManager glide, final MentionClickListener mentionClickListener, final View.OnClickListener clickListener, final View.OnLongClickListener longClickListener) { super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener); this.binding = binding; - this.glide = glide; binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE); binding.itemFeedBottom.btnMute.setVisibility(View.GONE); - drawable = new ColorDrawable(Color.WHITE); binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false); final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources()) .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) @@ -50,15 +40,13 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder { @Override public void bindItem(final FeedModel feedModel) { - // glide.clear(customTarget); if (feedModel == null) { return; } final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams(); 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(); final String thumbnailUrl = feedModel.getThumbnailUrl(); String url = feedModel.getDisplayUrl(); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java index 572286f2..fe8d7ccf 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java +++ b/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 float intVol = player.getVolume() == 0f ? 1f : 0f; 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; }; final ViewerPostModel firstItem = sliderItems[0]; if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { 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); } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java index 2df6c499..30d57e97 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java +++ b/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) { 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(); final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl())) .setProgressiveRenderingEnabled(true) @@ -131,7 +132,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder { } 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() { diff --git a/app/src/main/java/awais/instagrabber/asyncs/CommentAction.java b/app/src/main/java/awais/instagrabber/asyncs/CommentAction.java new file mode 100644 index 00000000..03ba9496 --- /dev/null +++ b/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 { + 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java index 3e972871..5937708b 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java +++ b/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; public final class PostsFetcher extends AsyncTask { + private static final String TAG = "PostsFetcher"; private final String endCursor; private final String id; private final FetchListener fetchListener; @@ -60,13 +61,13 @@ public final class PostsFetcher extends AsyncTask { "{\"tag_name\":\"" + id.substring(1).toLowerCase() + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; else if (isLocation) url = "https://www.instagram.com/graphql/query/?query_hash=36bd0f2bf5911908de389b8ceaa3be6d&variables=" + - "{\"id\":\""+ id.split("/")[0] +"\",\"first\":150,\"after\":\"" + endCursor + "\"}"; + "{\"id\":\"" + id.split("/")[0] + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; else if (isSaved) url = "https://www.instagram.com/graphql/query/?query_hash=8c86fed24fa03a8a2eea2a70a80c7b6b&variables=" + - "{\"id\":\""+ id.substring(1) +"\",\"first\":150,\"after\":\"" + endCursor + "\"}"; + "{\"id\":\"" + id.substring(1) + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; else if (isTagged) url = "https://www.instagram.com/graphql/query/?query_hash=ff260833edf142911047af6024eb634a&variables=" + - "{\"id\":\""+ id.substring(1) +"\",\"first\":150,\"after\":\"" + endCursor + "\"}"; + "{\"id\":\"" + id.substring(1) + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; else url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; @@ -79,15 +80,16 @@ public final class PostsFetcher extends AsyncTask { if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { // to check if file exists final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); File customDir = null; if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); 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 : (isLocation ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_USER)) .getJSONObject(isHashTag ? "edge_hashtag_to_media" : @@ -131,7 +133,7 @@ public final class PostsFetcher extends AsyncTask { 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); result = models; @@ -141,7 +143,7 @@ public final class PostsFetcher extends AsyncTask { } catch (Exception e) { if (logCollector != null) 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; diff --git a/app/src/main/java/awais/instagrabber/asyncs/QuizAction.java b/app/src/main/java/awais/instagrabber/asyncs/QuizAction.java new file mode 100644 index 00000000..d8a61131 --- /dev/null +++ b/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 { + 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/RespondAction.java b/app/src/main/java/awais/instagrabber/asyncs/RespondAction.java new file mode 100644 index 00000000..990a0290 --- /dev/null +++ b/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 { + + 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/SeenAction.java b/app/src/main/java/awais/instagrabber/asyncs/SeenAction.java new file mode 100644 index 00000000..805942e9 --- /dev/null +++ b/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 { + 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; + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/VoteAction.java b/app/src/main/java/awais/instagrabber/asyncs/VoteAction.java new file mode 100644 index 00000000..cfa91c77 --- /dev/null +++ b/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 { + + 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java index d85f3d47..12a713e7 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java +++ b/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 { private final String id; 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 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 fetchListener) { this.id = id; this.username = username; @@ -43,9 +50,35 @@ public final class iStoryStatusFetcher extends AsyncTask { + 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); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/NestedCoordinatorLayout.java b/app/src/main/java/awais/instagrabber/customviews/helpers/NestedCoordinatorLayout.java new file mode 100644 index 00000000..da0a1e99 --- /dev/null +++ b/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); + } +} diff --git a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java b/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java index 248b8a31..602914a1 100755 --- a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java +++ b/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.activities.MainActivityBackup; import awais.instagrabber.adapters.SimpleAdapter; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.DataBox; @@ -112,8 +113,8 @@ public final class QuickAccessDialog extends BottomSheetDialogFragment implement else Utils.showImportExportDialog(v.getContext()); } 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(); } diff --git a/app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java b/app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java index 72aa1853..f44374f9 100755 --- a/app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java +++ b/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.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); } @@ -109,10 +109,10 @@ public final class MultiDirectDialog extends BaseLanguageActivity { } 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); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java new file mode 100644 index 00000000..7818c94f --- /dev/null +++ b/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 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 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>() { + @Override + public void onSuccess(final List 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 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() { + @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; + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java index 3827e390..0eb70d79 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java +++ b/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.adapters.DirectMessageInboxAdapter; import awais.instagrabber.asyncs.direct_messages.InboxFetcher; +import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding; import awais.instagrabber.interfaces.FetchListener; @@ -39,7 +40,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh private static final String TAG = "DirectMessagesInboxFrag"; private FragmentActivity fragmentActivity; - private SwipeRefreshLayout root; + private NestedCoordinatorLayout root; private RecyclerView inboxList; private RecyclerLazyLoader lazyLoader; private LinearLayoutManager layoutManager; @@ -51,7 +52,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh private final FetchListener fetchListener = new FetchListener() { @Override public void doBefore() { - root.setRefreshing(true); + binding.swipeRefreshLayout.setRefreshing(true); } @Override @@ -71,10 +72,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh listViewModel.getList().postValue(list); } } - root.setRefreshing(false); + binding.swipeRefreshLayout.setRefreshing(false); stopCurrentExecutor(); } }; + private FragmentDirectMessagesInboxBinding binding; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -89,9 +91,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh if (root != null) { return root; } - final FragmentDirectMessagesInboxBinding binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false); + binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false); root = binding.getRoot(); - root.setOnRefreshListener(this); + binding.swipeRefreshLayout.setOnRefreshListener(this); inboxList = binding.inboxList; inboxList.setHasFixedSize(true); layoutManager = new LinearLayoutManager(requireContext()); diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index a077b082..70b9f8a8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/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.activities.PostViewer; import awais.instagrabber.activities.ProfileViewer; -import awais.instagrabber.activities.StoryViewer; import awais.instagrabber.adapters.DirectMessageItemsAdapter; import awais.instagrabber.asyncs.ImageUploader; import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher; @@ -225,21 +224,21 @@ public class DirectMessageThreadFragment extends Fragment { break; case STORY_SHARE: 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("@")) { searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]); } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java new file mode 100644 index 00000000..2ddb607a --- /dev/null +++ b/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 topicFetchListener = new FetchListener() { + @Override + public void doBefore() {} + + @Override + public void onResult(final DiscoverTopicModel result) { + if (result != null) { + topicIds = result.getIds(); + rankToken = result.getToken(); + final ArrayAdapter spinnerArrayAdapter = new ArrayAdapter<>( + requireContext(), + android.R.layout.simple_spinner_dropdown_item, + result.getNames() + ); + binding.discoverType.setAdapter(spinnerArrayAdapter); + } + } + }; + private final FetchListener discoverFetchListener = new FetchListener() { + @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 current = discoverItemViewModel.getList().getValue(); + final List resultList = Arrays.asList(result); + if (current == null) { + discoverItemViewModel.getList().postValue(resultList); + } else { + final List 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; + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java new file mode 100644 index 00000000..1add9834 --- /dev/null +++ b/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 feedFetchListener = new FetchListener() { + @Override + public void doBefore() { + binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true)); + } + + @Override + public void onResult(final FeedModel[] result) { + if (result == null) return; + final List currentFeedModelList = feedViewModel.getList().getValue(); + final Map thumbToFeedMap = new HashMap<>(); + for (final FeedModel feedModel : result) { + thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel); + } + final BaseDataSubscriber subscriber = new BaseDataSubscriber() { + int success = 0; + int failed = 0; + + @Override + protected void onNewResultImpl(@NonNull final DataSource dataSource) { + final Map 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 dataSource) { + failed++; + updateAdapter(); + } + + public void updateAdapter() { + if (failed + success != result.length) return; + final List 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 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 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>() { + @Override + public void onSuccess(final List result) { + feedStoriesViewModel.getList().postValue(result); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "failed", t); + } + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileActionListener.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileActionListener.java new file mode 100644 index 00000000..3c4a4721 --- /dev/null +++ b/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) { + // + // } + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java new file mode 100644 index 00000000..3013c7c9 --- /dev/null +++ b/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 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; + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/DiscoverItemViewModel.java b/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/DiscoverItemViewModel.java new file mode 100644 index 00000000..5bd1eff0 --- /dev/null +++ b/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; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedStoriesViewModel.java b/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedStoriesViewModel.java new file mode 100644 index 00000000..dbd593aa --- /dev/null +++ b/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; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedViewModel.java b/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/FeedViewModel.java new file mode 100644 index 00000000..a6782e7d --- /dev/null +++ b/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; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(Collections.emptyList()); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/ProfilePostsViewModel.java b/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/ProfilePostsViewModel.java new file mode 100644 index 00000000..5c4c645c --- /dev/null +++ b/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; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/StoriesViewModel.java b/app/src/main/java/awais/instagrabber/fragments/main/viewmodels/StoriesViewModel.java new file mode 100644 index 00000000..baafc040 --- /dev/null +++ b/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; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/BasePostModel.java b/app/src/main/java/awais/instagrabber/models/BasePostModel.java index ec2780c3..d693a5f7 100755 --- a/app/src/main/java/awais/instagrabber/models/BasePostModel.java +++ b/app/src/main/java/awais/instagrabber/models/BasePostModel.java @@ -1,14 +1,16 @@ package awais.instagrabber.models; import androidx.annotation.NonNull; +import androidx.core.util.ObjectsCompat; import java.io.Serializable; import java.util.Date; +import awais.instagrabber.adapters.MultiSelectListAdapter.Selectable; import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.utils.Utils; -public abstract class BasePostModel implements Serializable { +public abstract class BasePostModel implements Serializable, Selectable { protected String postId; protected String displayUrl; protected String shortCode; @@ -88,4 +90,17 @@ public abstract class BasePostModel implements Serializable { public final String getPostDate() { 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); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/PostModel.java b/app/src/main/java/awais/instagrabber/models/PostModel.java index 8c079a5a..27aeb6a2 100755 --- a/app/src/main/java/awais/instagrabber/models/PostModel.java +++ b/app/src/main/java/awais/instagrabber/models/PostModel.java @@ -51,4 +51,6 @@ public class PostModel extends BasePostModel { this.endCursor = endCursor; this.hasNextPage = hasNextPage; } + + } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java new file mode 100644 index 00000000..b7167ce2 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java @@ -0,0 +1,4 @@ +package awais.instagrabber.repositories; + +public interface ProfileRepository { +} diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java b/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java new file mode 100644 index 00000000..42f262d6 --- /dev/null +++ b/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 getStories(@QueryMap(encoded = true) Map variables); + + @GET + Call getUserStory(@Header("User-Agent") String userAgent, @Url String url); +} diff --git a/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java b/app/src/main/java/awais/instagrabber/services/AddCookiesInterceptor.java new file mode 100644 index 00000000..052e4f6c --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/services/BaseService.java b/app/src/main/java/awais/instagrabber/services/BaseService.java new file mode 100644 index 00000000..bbf7c28f --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/awais/instagrabber/services/FeedService.java b/app/src/main/java/awais/instagrabber/services/FeedService.java new file mode 100644 index 00000000..9f65b2f0 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/services/FeedService.java @@ -0,0 +1,4 @@ +package awais.instagrabber.services; + +public interface FeedService { +} diff --git a/app/src/main/java/awais/instagrabber/services/ProfileService.java b/app/src/main/java/awais/instagrabber/services/ProfileService.java new file mode 100644 index 00000000..d5be7b17 --- /dev/null +++ b/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/")); + } +} diff --git a/app/src/main/java/awais/instagrabber/services/ServiceCallback.java b/app/src/main/java/awais/instagrabber/services/ServiceCallback.java new file mode 100644 index 00000000..f296bc1e --- /dev/null +++ b/app/src/main/java/awais/instagrabber/services/ServiceCallback.java @@ -0,0 +1,7 @@ +package awais.instagrabber.services; + +public interface ServiceCallback { + void onSuccess(T result); + + void onFailure(Throwable t); +} diff --git a/app/src/main/java/awais/instagrabber/services/StoriesService.java b/app/src/main/java/awais/instagrabber/services/StoriesService.java new file mode 100644 index 00000000..adbd5005 --- /dev/null +++ b/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> callback) { + final Map queryMap = new HashMap<>(); + queryMap.put("query_hash", "b7b84d884400bc5aa7cfe12ae843a091"); + queryMap.put("variables", "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}"); + final Call response = repository.getStories(queryMap); + response.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + Log.e(TAG, "getFeedStories: body is empty"); + return; + } + try { + final List 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 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> callback) { + final String url = buildUrl(id, storiesig, isLoc, isHashtag, highlight); + final String userAgent = storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT; + final Call userStoryCall = repository.getUserStory(userAgent, url); + userStoryCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response 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 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 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(); + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java new file mode 100644 index 00000000..025adb11 --- /dev/null +++ b/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 setupWithNavController(@NonNull final BottomNavigationView bottomNavigationView, + @NonNull List navGraphIds, + @NonNull final FragmentManager fragmentManager, + final int containerId, + @NonNull Intent intent, + final int firstFragmentGraphIndex) { + final SparseArray graphIdToTagMap = new SparseArray<>(); + final MutableLiveData 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 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 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; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index f96920e3..64c7985a 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/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.R; import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.activities.MainActivityBackup; import awais.instagrabber.activities.ProfileViewer; import awais.instagrabber.activities.SavedViewer; import awais.instagrabber.asyncs.DownloadAsync; @@ -942,7 +943,7 @@ public final class Utils { dir = new File(dir, username); 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 SavedViewer saved = method == DownloadMethod.DOWNLOAD_SAVED && context instanceof SavedViewer ? (SavedViewer) context : null; @@ -973,19 +974,20 @@ public final class Utils { saveFile, file -> { 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); } - } 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); } } diff --git a/app/src/main/res/drawable-night/comments.png b/app/src/main/res/drawable-night/comments.png deleted file mode 100644 index cee092e29cf5306834a8a28ce6490e69ccf58322..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6242 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 AKmY&$ diff --git a/app/src/main/res/drawable-night/video_views.png b/app/src/main/res/drawable-night/video_views.png deleted file mode 100644 index 41098f4a21ec207776f9fe4cf7ab79e7bf7749ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10342 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

7Cu?M>twQ4@;;$`on#DtokNKK*hML1568dZ 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 a%*@{cPgV8b$Vos;068gT$tv+TLH`RW6B>B{ diff --git a/app/src/main/res/drawable-night/vol.png b/app/src/main/res/drawable-night/vol.png deleted file mode 100644 index bd695d547c5d4557a10b60e0f40ae85fa9ff91f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7514 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 E0=ndgP5=M^ diff --git a/app/src/main/res/drawable/comments.png b/app/src/main/res/drawable/comments.png deleted file mode 100644 index 98be2f4c31b0da812c56c27f922ae4cba04f799a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4755 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 diff --git a/app/src/main/res/drawable/ic_close_24.xml b/app/src/main/res/drawable/ic_close_24.xml new file mode 100644 index 00000000..16d6d37d --- /dev/null +++ b/app/src/main/res/drawable/ic_close_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_horiz_24.xml b/app/src/main/res/drawable/ic_more_horiz_24.xml new file mode 100644 index 00000000..6439bcc7 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_horiz_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_open_in_new_24.xml b/app/src/main/res/drawable/ic_open_in_new_24.xml new file mode 100644 index 00000000..455b503a --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_new_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_comments_24.xml b/app/src/main/res/drawable/ic_outline_comments_24.xml new file mode 100644 index 00000000..fce13024 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_comments_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_views_24.xml b/app/src/main/res/drawable/ic_outline_views_24.xml new file mode 100644 index 00000000..f75a6afc --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_views_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_star_24.xml b/app/src/main/res/drawable/ic_star_24.xml new file mode 100644 index 00000000..5ce65693 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_off_24.xml b/app/src/main/res/drawable/ic_volume_off_24.xml new file mode 100644 index 00000000..aaf49e9b --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up_24.xml b/app/src/main/res/drawable/ic_volume_up_24.xml new file mode 100644 index 00000000..0db34695 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/mute.png b/app/src/main/res/drawable/mute.png deleted file mode 100644 index 1b078533acfd2b11a60f094adc460f97d783ae7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5408 zcmd5=2{=@3`#&>-G4`F&$Y5+q8A};u8T*8?q!cZf8Ai4kYl$dJZ+5++vNVGf+LX|u z%~GcDDkX_h$d(u?+y5DryzljX*Z+IJ>-w(iJL5jT`<(lCpL5^ObD#S>PLlm*D?vV4 zJ_v#YN!I3$02?{N%LVpG2dX3hxQC6EIkb?F+I$Bbp#rUU1Os*lM_{IEr@}c*I?2ug z*@uLQ^67n3F24vtuzQhPUFeSPVc5W+04mLwf~7|UQm`~nPYBXaE^=~B!6*pK_Dc|A z$FCAL3+pDg)tY-u^qWbvLDlvPU3s7E8pqQU zqjZRpIv4qRVEN+#-6h0l_O4yD4KKWU;Vzo`wbG~8``7Ai7TPWz^`@L2$no-I%MdlYS$e`Z2A=FYbm-wJ1=QUW&>Ix=aiX8t)RDI;*eO^j;xo!L{$&+73nkRfOh+Q)f zclH`jIy^OSJY^-S^dR||Tcp;s>kvoH_PPxnblF7Me$=|F=uk!Zt>yg zU@xuZr)u_Vyo4a`6p}e{Yk22K*QlgX0Jh9U+tE|MEfNVei{LY)rd?zA4tA_*Ko$fk>eN zueoC6hNlkl<_&b$6RUQq-y6?dI@;VZ5##S~96DkBIj9}+@~%T^ z+xs$%@o4xkHNEe{ImDF&MqBY3YE2$t?vj@+OQGp0UY03rwN8vR82QFQFOJF8pq2*1 zxZ(n&-+J?q&?&uGGkq16*3-A;4YUI?95MGzECYSh-DD;rsaApEnOW~-1RUc$eZ^QG zYj-jxaOzEXUZzTeRMXcUcJshc=er3d^~bI9<`dTltDHMqszn;s7m9WdNHo-KZ=5N# zDz!U{j*^V*i)Qn z)8M#2dc1YOsiJGXN?)8(H}^k}_zeW!WQ&W=a;tl-kd`%>F}g01F^5$je25|yH??x7 zp-BFNpXeofHb5B}yJa%TJ5p^A7~K>HjEFhuL(ku^KTS%j(YbKSa>DbGd!C!kT=rmU zTujR|;xp=(I|DJ>y{u1rK9!Kde!WexD9NA7WFqdE)%OoXVum@ zInj)X81oJai)TGUb10?t0y9KI+KcTsnNpF*{~FcZzAt9$QGI_uR@i{K_igfU)R-Ki z)r}SPzE;2fIIh z8ofluMiL9(rP|bd`L_r83|-O)-Zx#sz-qc1b&l0zO^X(KH#RXeXF0#UVT!TVTU}4R zXt#1wczVkPG^#u|y`>b5N(NAZM)?6KLZj>eFwiJX0EK9jAb@-{Y9uGU3yA z*xz?Xk+zsw;RLU$UAI!)l_7|~fZ{@;l1Qr{Uhp0U0^@>UK;yi{Vas$l3;`j)Ie#Fs zFz!VT9q225=m&wW1~?pF4i685LjnMg0MQ1z1i&;POd!0AKP8}X*gw&VWM_}nUPB

31SA9n`1u9U zV&cLQaX3>t~Tc)Fcb zvy7l@512c0AODk*ACa0~=+qS#x*y%~8P`$iqKEd>m(6I1`$had% z;}a5-j%8$Ko%lWbWKQl`2D7NRo-nboIHPS-!uD5UZNl`I2RX!3(3g~1`p#TF3QEN zfaeh-Iw0Nmiz{j$=9Mr_FT8vgrKIEdNzx;zl}`$%+p9dzNo_H+e=o7v|0uJi#D3+~ z2?-!zVDb>6kTLXi{+L<;{C`uR3<#}SPyoRjifMV4Dkl-DpHfE`AoidxOUsn& zk8|1~&c^o_ppNpL&^xuW$Dsd2AY6|?rfVnlqyqoO`_l<&C10YyP1aCz`aEa}WB7Xu zP}l-=ac36fU?Bmk2Rn}P4+Ii3LEt7q7b4Z6RjMXgI*kwhfXMTrb8qHvspJ&%n&gJ? zY(CGqDf@y6w}iu@U?_`<^SQD$%2l)zLai-f0Y#AW*<)(Zk`!ORp7GHEvoGK21)u8$ zlArQR?7OQ%@iEvX#&R~$q0AU zP=nj^iM#ZQ%0knQY~C`yzO(?|*0#ag6x(0w*`6En@MaiZrVq0+;YO)Fph@&ZJ3J&{nb0F~trXavRZsh#~>(SHOC z>n2%SLSLwEoZMzOm+ZS8?HWlfsTJAWwE*$w&DY1QfFAr|AwceqP94a86aLmTA6um%Ok99= z7`JSD4@o#!{H^TI`eE=J%q<=!26jzuU(7?`v zrAc{q<{Y>GQn@TI9gYsl2h@_2>nl>o+H9HrYC~=*F!h_1l7qq2@6s!0hNk-xn`T&D zEwFw2R$iPCvx zj5{~VHVqHSJGD(y4}|nzz>bZoA;gw-vwv0xxt*OhrAPLs+}g5tm26(y;E0NtsuDxk z!9-58hzUxQ_|&nXRL{^NV82|mqV*H2{R2KbA@zzOS?N$dMyc+GGVfi9d<0wv;hc|V z=!|$Gu?LJEq$fzM=sGK=AopW>S42`vYen4F8%jQn_H>o3-`1U7I{GSLLvX9h^pq~_ zG8i%*qmp9(=1*&lAH;DoeACWei<5E3HXa(EA6tNSUs~1#em&xdXxsMLSu_0H2mJu> zWDYsrsIw*@xwcK^lqSXA#n1Kh;OE!Q--3Kvf6eD&KZAL+joJ{xO0aY9*^+G*VFK6Z z0|TWo{nvu%_S_?8_WSBPt6dIra#>ahu&QF81J~~o`p;e-8}_lYarzuApsFqUIGWQe zU4C_tzm^2d1SdkgD<=q&#@(PlweS=z zwn7J!A3gEc1M;(3{(rdQ8?~hWEjo1!e?R&Fr~#lSO&0$_2Stf{qLQwzTpzurKLV4$U? zp#@gXT9b@R&3|(63n0^|5&zoZ=sa&AaFFHPff7!m8ko}P!S<9OCwIR_3HXdSgj|w1Ptd-v$jl0|Fz0XkOlQ$Uelwm*$Bz z5AbuR`Cm`Ry8@cxPa2sL>={H0q|*ZYzhBKGgyu`vr1@im9j#1ly@=v~E%}q}=k6au rb@!x)1W|$(>9%=kj{revi4Z`h_(B|4*S#b&V1Y;$o6Rds_s0GoFgG?w diff --git a/app/src/main/res/drawable/video_views.png b/app/src/main/res/drawable/video_views.png deleted file mode 100644 index 63332a9da5e238d1c3b4ec076fac737709beae98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6578 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 b0G>pW8{n|i{ygp!Ed#*mnPMO7o)7sCJ(G7t diff --git a/app/src/main/res/drawable/vol.png b/app/src/main/res/drawable/vol.png deleted file mode 100644 index 14b2d13758b8d34b451725c7cc4381322fe5b07a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5804 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 diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cbefb539..c02f7c58 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,104 +1,49 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + - + + + - - - \ No newline at end of file + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_mainbackup.xml b/app/src/main/res/layout/activity_mainbackup.xml new file mode 100644 index 00000000..cbefb539 --- /dev/null +++ b/app/src/main/res/layout/activity_mainbackup.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_story_viewer.xml b/app/src/main/res/layout/activity_story_viewer.xml index 55bae71f..b815b721 100755 --- a/app/src/main/res/layout/activity_story_viewer.xml +++ b/app/src/main/res/layout/activity_story_viewer.xml @@ -1,20 +1,20 @@ - - - + android:orientation="vertical"> + 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"> - + android:layout_gravity="bottom" + android:background="#0000"> + + + + + + - \ No newline at end of file + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_direct_messages_inbox.xml b/app/src/main/res/layout/fragment_direct_messages_inbox.xml index 724b313f..3c3e91a4 100644 --- a/app/src/main/res/layout/fragment_direct_messages_inbox.xml +++ b/app/src/main/res/layout/fragment_direct_messages_inbox.xml @@ -1,10 +1,21 @@ - - - + android:layout_height="match_parent" + android:background="?attr/colorOnPrimarySurface" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + + + diff --git a/app/src/main/res/layout/fragment_direct_messages_thread.xml b/app/src/main/res/layout/fragment_direct_messages_thread.xml index 94bdde72..b4a72897 100644 --- a/app/src/main/res/layout/fragment_direct_messages_thread.xml +++ b/app/src/main/res/layout/fragment_direct_messages_thread.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> + android:layout_height="match_parent" + tools:listitem="@layout/layout_dm_base"/> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml new file mode 100644 index 00000000..5c408779 --- /dev/null +++ b/app/src/main/res/layout/fragment_feed.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml new file mode 100644 index 00000000..5bcdcecc --- /dev/null +++ b/app/src/main/res/layout/fragment_profile.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_bottom.xml b/app/src/main/res/layout/item_feed_bottom.xml index 5f37390d..d8bfaf4c 100755 --- a/app/src/main/res/layout/item_feed_bottom.xml +++ b/app/src/main/res/layout/item_feed_bottom.xml @@ -1,117 +1,100 @@ - + android:layout_height="wrap_content" + android:orientation="vertical"> - - - - - - - - - - - + android:background="?android:selectableItemBackground" + android:gravity="center" + android:orientation="horizontal" + android:padding="4dp"> - + - + + + + android:gravity="center" + android:orientation="horizontal"> + + + + + + + + + - - - - @@ -129,10 +112,13 @@ android:layout_height="match_parent" android:background="?android:selectableItemBackground" 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:textSize="16sp" tools:text="BOTTOM TEXT" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_photo.xml b/app/src/main/res/layout/item_feed_photo.xml index f52b2483..999641db 100644 --- a/app/src/main/res/layout/item_feed_photo.xml +++ b/app/src/main/res/layout/item_feed_photo.xml @@ -8,11 +8,6 @@ android:id="@+id/item_feed_top" layout="@layout/item_feed_top" /> - - - - - + android:orientation="horizontal" + android:padding="8dp"> + android:background="?selectableItemBackgroundBorderless" + app:roundAsCircle="true" /> + tools:text="username" /> + tools:text="location" /> + app:srcCompat="@drawable/ic_open_in_new_24" + app:tint="?android:textColorPrimary" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_highlight.xml b/app/src/main/res/layout/item_highlight.xml index 91a78f98..eb731c73 100755 --- a/app/src/main/res/layout/item_highlight.xml +++ b/app/src/main/res/layout/item_highlight.xml @@ -3,11 +3,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="@dimen/profile_picture_size" android:layout_height="wrap_content" - android:layout_marginTop="4dp" - android:layout_marginBottom="4dp" + android:layout_margin="8dp" android:background="?android:selectableItemBackground" - android:orientation="vertical" - tools:viewBindingIgnore="true"> + android:orientation="vertical"> + android:textColor="?android:textColorPrimaryInverse" + android:textStyle="bold" + tools:text="@string/app_name" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_post.xml b/app/src/main/res/layout/item_post.xml index 92d2299a..3888f32b 100755 --- a/app/src/main/res/layout/item_post.xml +++ b/app/src/main/res/layout/item_post.xml @@ -1,18 +1,16 @@ + android:foreground="?android:selectableItemBackground"> - + app:actualImageScaleType="fitCenter" + app:viewAspectRatio="1"/> - + + + + + + + android:layout_margin="8dp" + android:foreground="?android:selectableItemBackground"> - + app:actualImageScaleType="fitCenter" /> +

+ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 00000000..2b94374d --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/multi_select_download_menu.xml b/app/src/main/res/menu/multi_select_download_menu.xml new file mode 100644 index 00000000..08719c66 --- /dev/null +++ b/app/src/main/res/menu/multi_select_download_menu.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/story_menu.xml b/app/src/main/res/menu/story_menu.xml new file mode 100644 index 00000000..c1178c99 --- /dev/null +++ b/app/src/main/res/menu/story_menu.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index 9f4d984c..04b8a5b1 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -1,13 +1,15 @@ + android:label="@string/action_dms" + tools:layout="@layout/fragment_direct_messages_inbox"> @@ -15,7 +17,8 @@ + android:label="DirectMessagesThreadFragment" + tools:layout="@layout/fragment_direct_messages_thread"> @@ -29,7 +32,8 @@ + android:label="DirectMessagesSettingsFragment" + tools:layout="@layout/fragment_direct_messages_settings"> diff --git a/app/src/main/res/navigation/discover_nav_graph.xml b/app/src/main/res/navigation/discover_nav_graph.xml new file mode 100644 index 00000000..09840eb7 --- /dev/null +++ b/app/src/main/res/navigation/discover_nav_graph.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/feed_nav_graph.xml b/app/src/main/res/navigation/feed_nav_graph.xml new file mode 100644 index 00000000..0dc1f3f6 --- /dev/null +++ b/app/src/main/res/navigation/feed_nav_graph.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/profile_nav_graph.xml b/app/src/main/res/navigation/profile_nav_graph.xml new file mode 100644 index 00000000..dd294b61 --- /dev/null +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index be3e011d..a84c5407 100755 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -19,4 +19,6 @@ @dimen/feed_profile_size 500dp 8dp + + 32dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05fbc373..d2b35a59 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -234,4 +234,9 @@ %d usertags %d likes You logged out before clicking this notification?! + Feed + Profile + More + DM + %d selected diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6fccb7f1..a34919c8 100755 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,11 +1,6 @@ - - + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 5a10e802..3b76d8a6 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,10 +1,21 @@ + + + -