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 cee092e2..00000000 Binary files a/app/src/main/res/drawable-night/comments.png and /dev/null differ diff --git a/app/src/main/res/drawable-night/mute.png b/app/src/main/res/drawable-night/mute.png deleted file mode 100644 index 0295f0b8..00000000 Binary files a/app/src/main/res/drawable-night/mute.png and /dev/null differ 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 41098f4a..00000000 Binary files a/app/src/main/res/drawable-night/video_views.png and /dev/null differ 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 bd695d54..00000000 Binary files a/app/src/main/res/drawable-night/vol.png and /dev/null differ diff --git a/app/src/main/res/drawable/comments.png b/app/src/main/res/drawable/comments.png deleted file mode 100644 index 98be2f4c..00000000 Binary files a/app/src/main/res/drawable/comments.png and /dev/null differ 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 1b078533..00000000 Binary files a/app/src/main/res/drawable/mute.png and /dev/null differ 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 63332a9d..00000000 Binary files a/app/src/main/res/drawable/video_views.png and /dev/null differ diff --git a/app/src/main/res/drawable/vol.png b/app/src/main/res/drawable/vol.png deleted file mode 100644 index 14b2d137..00000000 Binary files a/app/src/main/res/drawable/vol.png and /dev/null differ 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 @@ + + + -