Merge branch 'master' into l10n_master
11
README.md
@ -16,10 +16,12 @@ InstaGrabber is an app that allows...
|
||||
|
||||
It can be used as a drop-in replacement for read functionalities of the official Instagram app, with unnecessary components stripped.
|
||||
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/1.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/1.jpg" alt="Profile" width="23%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/2.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/2.jpg" alt="Post" width="23%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/3.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Story (Highlight shown)" width="23%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/4.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Hashtag" width="23%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/1.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/1.jpg" alt="Profile" width="15%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/2.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/2.jpg" alt="Post" width="15%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/3.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Comments" width="15%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/4.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/4.jpg" alt="Story (Highlight shown)" width="15%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/5.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/5.jpg" alt="Hashtag" width="15%"/></a>
|
||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/6.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/6.jpg" alt="Location" width="15%"/></a>
|
||||
|
||||
This app is originally made by [@AwaisKing](https://github.com/AwaisKing) who posted on [GitLab](https://gitlab.com/AwaisKing/instagrabber) but subsequently abandoned it. I decided to continue the app cuz why not, ~~even though it might not be that *cash money*.~~ (Also I need to learn Java.)
|
||||
|
||||
@ -34,6 +36,7 @@ Not compatible with pre-16.6 versions (including alpha).
|
||||
Remember to read the [wiki](https://github.com/austinhuang0131/instagrabber/wiki) for more info!
|
||||
|
||||
[![Open Source Love svg3](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
||||
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
|
||||
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
|
||||
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber)
|
||||
[![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)
|
||||
|
@ -177,6 +177,24 @@
|
||||
android:value=".activities.Main" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.SavedViewer"
|
||||
android:parentActivityName=".activities.Main">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activities.Main" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.NotificationsViewer"
|
||||
android:parentActivityName=".activities.Main">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activities.Main" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.Login"
|
||||
android:label="@string/login"
|
||||
|
@ -46,6 +46,7 @@ import java.util.Arrays;
|
||||
import awais.instagrabber.activities.FollowViewer;
|
||||
import awais.instagrabber.activities.Main;
|
||||
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;
|
||||
@ -461,7 +462,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
if ((!autoloadPosts || isHashtag) && hasNextPage) {
|
||||
main.mainBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
stopCurrentExecutor();
|
||||
currentlyExecuting = new PostsFetcher(isHashtag ? main.userQuery : main.profileModel.getId(), endCursor, postsFetchListener)
|
||||
currentlyExecuting = new PostsFetcher((isHashtag || isLocation) ? main.userQuery : main.profileModel.getId(), endCursor, postsFetchListener)
|
||||
.setUsername(isHashtag ? null : main.profileModel.getUsername())
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
endCursor = null;
|
||||
@ -522,7 +523,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
}
|
||||
|
||||
private void setupExplore() {
|
||||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130));
|
||||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(110));
|
||||
main.mainBinding.discoverPosts.setLayoutManager(layoutManager);
|
||||
main.mainBinding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
|
||||
|
||||
@ -650,8 +651,17 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
main.mainBinding.btnFollow.setVisibility(View.GONE);
|
||||
main.mainBinding.btnRestrict.setVisibility(View.GONE);
|
||||
main.mainBinding.btnBlock.setVisibility(View.GONE);
|
||||
main.mainBinding.btnSaved.setVisibility(View.GONE);
|
||||
main.mainBinding.btnTagged.setVisibility(View.GONE);
|
||||
main.mainBinding.btnMap.setVisibility(View.GONE);
|
||||
|
||||
main.mainBinding.btnFollow.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnRestrict.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnBlock.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnSaved.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnTagged.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnFollowTag.setOnClickListener(profileActionListener);
|
||||
|
||||
main.mainBinding.infoContainer.setVisibility(View.GONE);
|
||||
main.mainBinding.tagInfoContainer.setVisibility(View.GONE);
|
||||
main.mainBinding.locInfoContainer.setVisibility(View.GONE);
|
||||
@ -698,7 +708,6 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
main.mainBinding.btnFollowTag.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnFollowTag.setOnClickListener(profileActionListener);
|
||||
|
||||
if (isLoggedIn) {
|
||||
new StoryStatusFetcher(profileId, hashtagModel.getName(), false, result -> {
|
||||
@ -779,8 +788,9 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
final String myId = Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE));
|
||||
if (!profileId.equals(myId)) {
|
||||
main.mainBinding.btnTagged.setVisibility(View.GONE);
|
||||
main.mainBinding.btnSaved.setVisibility(View.GONE);
|
||||
main.mainBinding.btnFollow.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnFollow.setOnClickListener(profileActionListener);
|
||||
if (profileModel.getFollowing() == true) {
|
||||
main.mainBinding.btnFollow.setText(R.string.unfollow);
|
||||
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
@ -797,43 +807,69 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
R.color.btn_pink_background, null)));
|
||||
}
|
||||
main.mainBinding.btnRestrict.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnRestrict.setOnClickListener(profileActionListener);
|
||||
if (profileModel.getRestricted() == true) {
|
||||
main.mainBinding.btnRestrict.setText(R.string.unrestrict);
|
||||
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_blue_background, null)));
|
||||
R.color.btn_green_background, null)));
|
||||
}
|
||||
else {
|
||||
main.mainBinding.btnRestrict.setText(R.string.restrict);
|
||||
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_orange_background, null)));
|
||||
}
|
||||
if (profileModel.isReallyPrivate()) {
|
||||
main.mainBinding.btnBlock.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnBlock.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnSaved.setVisibility(View.GONE);
|
||||
main.mainBinding.btnTagged.setVisibility(View.GONE);
|
||||
if (profileModel.getBlocked() == true) {
|
||||
main.mainBinding.btnBlock.setText(R.string.unblock);
|
||||
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_green_background, null)));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
main.mainBinding.btnBlock.setText(R.string.block);
|
||||
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_red_background, null)));
|
||||
}
|
||||
} else {
|
||||
main.mainBinding.btnBlock.setVisibility(View.GONE);
|
||||
main.mainBinding.btnSaved.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnTagged.setVisibility(View.VISIBLE);
|
||||
if (profileModel.getBlocked() == true) {
|
||||
main.mainBinding.btnSaved.setText(R.string.unblock);
|
||||
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_green_background, null)));
|
||||
} else {
|
||||
main.mainBinding.btnSaved.setText(R.string.block);
|
||||
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_red_background, null)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
main.mainBinding.btnTagged.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnSaved.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnSaved.setText(R.string.saved);
|
||||
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_orange_background, null)));
|
||||
}
|
||||
} else {
|
||||
if (Utils.dataBox.getFavorite(main.userQuery) != null) {
|
||||
main.mainBinding.btnFollow.setText(R.string.unfavorite);
|
||||
main.mainBinding.btnFollow.setText(R.string.unfavorite_short);
|
||||
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_purple_background, null)));
|
||||
}
|
||||
else {
|
||||
main.mainBinding.btnFollow.setText(R.string.favorite);
|
||||
main.mainBinding.btnFollow.setText(R.string.favorite_short);
|
||||
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_pink_background, null)));
|
||||
}
|
||||
main.mainBinding.btnFollow.setOnClickListener(profileActionListener);
|
||||
main.mainBinding.btnFollow.setVisibility(View.VISIBLE);
|
||||
if (!profileModel.isReallyPrivate()) {
|
||||
main.mainBinding.btnRestrict.setVisibility(View.VISIBLE);
|
||||
main.mainBinding.btnRestrict.setText(R.string.tagged);
|
||||
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
|
||||
R.color.btn_blue_background, null)));
|
||||
}
|
||||
}
|
||||
|
||||
main.mainBinding.mainProfileImage.setEnabled(false);
|
||||
@ -864,7 +900,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0);
|
||||
main.mainBinding.mainFollowing.setText(span);
|
||||
|
||||
main.mainBinding.mainFullName.setText(profileModel.getName());
|
||||
main.mainBinding.mainFullName.setText(Utils.isEmpty(profileModel.getName()) ? profileModel.getUsername() : profileModel.getName());
|
||||
|
||||
CharSequence biography = profileModel.getBiography();
|
||||
main.mainBinding.mainBiography.setCaptionIsExpandable(true);
|
||||
@ -1163,23 +1199,36 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
|
||||
private final View.OnClickListener profileActionListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (!isLoggedIn && Utils.dataBox.getFavorite(main.userQuery) != null) {
|
||||
final boolean iamme = isLoggedIn
|
||||
? Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)).equals(main.profileModel.getId())
|
||||
: false;
|
||||
if (!isLoggedIn && Utils.dataBox.getFavorite(main.userQuery) != null && v == main.mainBinding.btnFollow) {
|
||||
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(main.userQuery,
|
||||
Long.parseLong(Utils.dataBox.getFavorite(main.userQuery).split("/")[1]),
|
||||
main.locationModel != null ? main.locationModel.getName() : main.userQuery));
|
||||
onRefresh();
|
||||
} else if (!isLoggedIn) {
|
||||
} else if (!isLoggedIn && v == main.mainBinding.btnFollow) {
|
||||
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(main.userQuery, System.currentTimeMillis(),
|
||||
main.locationModel != null ? main.locationModel.getName() : main.userQuery));
|
||||
onRefresh();
|
||||
} else if (v == main.mainBinding.btnFollow) {
|
||||
new ProfileAction().execute("follow");
|
||||
} else if (v == main.mainBinding.btnRestrict) {
|
||||
} else if (v == main.mainBinding.btnRestrict && isLoggedIn) {
|
||||
new ProfileAction().execute("restrict");
|
||||
} else if (v == main.mainBinding.btnBlock) {
|
||||
} else if (v == main.mainBinding.btnSaved && !iamme) {
|
||||
new ProfileAction().execute("block");
|
||||
} else if (v == main.mainBinding.btnFollowTag) {
|
||||
new ProfileAction().execute("followtag");
|
||||
} else if (v == main.mainBinding.btnTagged || (v == main.mainBinding.btnRestrict && !isLoggedIn)) {
|
||||
main.startActivity(new Intent(main, SavedViewer.class)
|
||||
.putExtra(Constants.EXTRAS_INDEX, "%"+main.profileModel.getId())
|
||||
.putExtra(Constants.EXTRAS_USER, "@"+main.profileModel.getUsername())
|
||||
);
|
||||
} else if (v == main.mainBinding.btnSaved) {
|
||||
main.startActivity(new Intent(main, SavedViewer.class)
|
||||
.putExtra(Constants.EXTRAS_INDEX, "$"+main.profileModel.getId())
|
||||
.putExtra(Constants.EXTRAS_USER, "@"+main.profileModel.getUsername())
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -87,8 +87,6 @@ public final class CommentsViewer extends BaseLanguageActivity implements SwipeR
|
||||
new CommentsFetcher(shortCode, new FetchListener<CommentModel[]>() {
|
||||
@Override
|
||||
public void onResult(final CommentModel[] commentModels) {
|
||||
commentsBinding.toolbar.progressCircular.setVisibility(View.GONE);
|
||||
|
||||
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
|
||||
|
||||
commentsBinding.rvComments.setAdapter(commentsAdapter);
|
||||
@ -105,8 +103,6 @@ public final class CommentsViewer extends BaseLanguageActivity implements SwipeR
|
||||
public void onResult(final CommentModel[] commentModels) {
|
||||
commentsBinding.swipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
commentsBinding.toolbar.progressCircular.setVisibility(View.GONE);
|
||||
|
||||
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
|
||||
|
||||
commentsBinding.rvComments.setAdapter(commentsAdapter);
|
||||
|
@ -87,7 +87,7 @@ public final class Main extends BaseLanguageActivity {
|
||||
private MenuItem searchAction;
|
||||
public ActivityMainBinding mainBinding;
|
||||
public SearchView searchView;
|
||||
public MenuItem downloadAction, settingsAction, dmsAction;
|
||||
public MenuItem downloadAction, settingsAction, dmsAction, notifAction;
|
||||
public StoryModel[] storyModels;
|
||||
public String userQuery = null;
|
||||
public MainHelper mainHelper;
|
||||
@ -118,9 +118,6 @@ public final class Main extends BaseLanguageActivity {
|
||||
final String uid = Utils.getUserIdFromCookie(cookie);
|
||||
Utils.setupCookies(cookie);
|
||||
|
||||
if (settingsHelper.getInteger(Constants.PROFILE_FETCH_MODE) == 2)
|
||||
settingsHelper.putInteger(Constants.PROFILE_FETCH_MODE, 1);
|
||||
|
||||
MainHelper.stopCurrentExecutor();
|
||||
mainHelper = new MainHelper(this);
|
||||
if (bundle == null) {
|
||||
@ -292,10 +289,12 @@ public final class Main extends BaseLanguageActivity {
|
||||
final MenuItem quickAccessAction = menu.findItem(R.id.action_quickaccess).setVisible(true);
|
||||
|
||||
final MenuItem.OnMenuItemClickListener clickListener = item -> {
|
||||
if (item == downloadAction) {
|
||||
if (item == downloadAction)
|
||||
downloadSelectedItems();
|
||||
} else if (item == dmsAction)
|
||||
else if (item == dmsAction)
|
||||
startActivity(new Intent(this, DirectMessages.class));
|
||||
else if (item == notifAction)
|
||||
startActivity(new Intent(this, NotificationsViewer.class));
|
||||
else if (item == settingsAction)
|
||||
new SettingsDialog().show(fragmentManager, "settings");
|
||||
else if (item == quickAccessAction)
|
||||
@ -310,11 +309,12 @@ public final class Main extends BaseLanguageActivity {
|
||||
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))) {
|
||||
//settingsAction.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
notifAction.setVisible(true);
|
||||
dmsAction.setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
}
|
||||
|
||||
@ -332,6 +332,7 @@ public final class Main extends BaseLanguageActivity {
|
||||
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
|
||||
@ -345,6 +346,7 @@ public final class Main extends BaseLanguageActivity {
|
||||
menu.findItem(R.id.action_settings).setVisible(true);
|
||||
menu.findItem(R.id.action_dms).setVisible(true);
|
||||
menu.findItem(R.id.action_quickaccess).setVisible(true);
|
||||
menu.findItem(R.id.action_notif).setVisible(true);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -458,6 +460,9 @@ public final class Main extends BaseLanguageActivity {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
140
app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java
Executable file
@ -0,0 +1,140 @@
|
||||
package awais.instagrabber.activities;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.NotificationsAdapter;
|
||||
import awais.instagrabber.asyncs.NotificationsFetcher;
|
||||
import awais.instagrabber.databinding.ActivityNotificationBinding;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.models.PostModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public final class NotificationsViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
|
||||
private NotificationsAdapter notificationsAdapter;
|
||||
private NotificationModel notificationModel;
|
||||
private ActivityNotificationBinding notificationsBinding;
|
||||
private ArrayAdapter<String> commmentDialogAdapter;
|
||||
private String shortCode, postId, userId;
|
||||
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
|
||||
private Resources resources;
|
||||
private InputMethodManager imm;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
notificationsBinding = ActivityNotificationBinding.inflate(getLayoutInflater());
|
||||
setContentView(notificationsBinding.getRoot());
|
||||
notificationsBinding.swipeRefreshLayout.setOnRefreshListener(this);
|
||||
|
||||
notificationsBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
setSupportActionBar(notificationsBinding.toolbar.toolbar);
|
||||
notificationsBinding.toolbar.toolbar.setTitle(R.string.title_notifications);
|
||||
|
||||
resources = getResources();
|
||||
|
||||
new NotificationsFetcher(new FetchListener<NotificationModel[]>() {
|
||||
@Override
|
||||
public void onResult(final NotificationModel[] notificationModels) {
|
||||
notificationsAdapter = new NotificationsAdapter(notificationModels, clickListener, mentionClickListener);
|
||||
|
||||
notificationsBinding.rvComments.setAdapter(notificationsAdapter);
|
||||
notificationsBinding.swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
notificationsBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
new NotificationsFetcher(new FetchListener<NotificationModel[]>() {
|
||||
@Override
|
||||
public void onResult(final NotificationModel[] notificationModels) {
|
||||
notificationsBinding.swipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
notificationsAdapter = new NotificationsAdapter(notificationModels, clickListener, mentionClickListener);
|
||||
|
||||
notificationsBinding.rvComments.setAdapter(notificationsAdapter);
|
||||
}
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
|
||||
if (which == 0)
|
||||
searchUsername(notificationModel.getUsername());
|
||||
else if (which == 1)
|
||||
startActivity(new Intent(getApplicationContext(), PostViewer.class)
|
||||
.putExtra(Constants.EXTRAS_POST, new PostModel(notificationModel.getShortcode())));
|
||||
};
|
||||
|
||||
private final View.OnClickListener clickListener = v -> {
|
||||
final Object tag = v.getTag();
|
||||
if (tag instanceof NotificationModel) {
|
||||
notificationModel = (NotificationModel) tag;
|
||||
|
||||
final String username = notificationModel.getUsername();
|
||||
final SpannableString title = new SpannableString(username + ":\n" + notificationModel.getText());
|
||||
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
||||
String[] commentDialogList;
|
||||
|
||||
if (notificationModel.getShortcode() != null) commentDialogList = new String[]{
|
||||
resources.getString(R.string.open_profile),
|
||||
resources.getString(R.string.view_post)
|
||||
};
|
||||
else commentDialogList = new String[]{
|
||||
resources.getString(R.string.open_profile)
|
||||
};
|
||||
|
||||
commmentDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, commentDialogList);
|
||||
|
||||
new AlertDialog.Builder(this).setTitle(title)
|
||||
.setAdapter(commmentDialogAdapter, profileDialogListener)
|
||||
.setNeutralButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
};
|
||||
|
||||
private final MentionClickListener mentionClickListener = (view, text, isHashtag) ->
|
||||
new AlertDialog.Builder(this).setTitle(text)
|
||||
.setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
|
||||
.setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok,
|
||||
(dialog, which) -> searchUsername(text)).show();
|
||||
|
||||
|
||||
private void searchUsername(final String text) {
|
||||
if (Main.scanHack != null) {
|
||||
Main.scanHack.onResult(text);
|
||||
setResult(6969);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -268,7 +268,11 @@ public final class PostViewer extends BaseLanguageActivity {
|
||||
final List<? extends BasePostModel> itemGetterItems;
|
||||
final boolean isMainSwipe;
|
||||
|
||||
if (itemGetType != null && Main.itemGetter != null) {
|
||||
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 && Main.itemGetter != null) {
|
||||
itemGetterItems = Main.itemGetter.get(itemGetType);
|
||||
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare);
|
||||
} else {
|
||||
|
@ -7,7 +7,6 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -29,22 +28,14 @@ import awais.instagrabber.R;
|
||||
import awais.instagrabber.asyncs.DownloadAsync;
|
||||
import awais.instagrabber.asyncs.ProfilePictureFetcher;
|
||||
import awais.instagrabber.databinding.ActivityProfileBinding;
|
||||
import awais.instagrabber.dialogs.ProfileSettingsDialog;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.HashtagModel;
|
||||
import awais.instagrabber.models.LocationModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.models.enums.ProfilePictureFetchMode;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE;
|
||||
|
||||
public final class ProfileViewer extends BaseLanguageActivity {
|
||||
private final ProfilePictureFetchMode[] fetchModes = {
|
||||
ProfilePictureFetchMode.INSTADP,
|
||||
ProfilePictureFetchMode.INSTAFULLSIZE
|
||||
};
|
||||
private ActivityProfileBinding profileBinding;
|
||||
private ProfileModel profileModel;
|
||||
private HashtagModel hashtagModel;
|
||||
@ -88,15 +79,12 @@ public final class ProfileViewer extends BaseLanguageActivity {
|
||||
profileBinding.imageViewer.setZoomTransitionDuration(420);
|
||||
profileBinding.imageViewer.setMaximumScale(7.2f);
|
||||
|
||||
final int fetchIndex = Math.min(2, Math.max(0, Utils.settingsHelper.getInteger(PROFILE_FETCH_MODE)));
|
||||
final ProfilePictureFetchMode fetchMode = fetchModes[fetchIndex];
|
||||
|
||||
fetchListener = profileUrl -> {
|
||||
profilePicUrl = profileUrl;
|
||||
|
||||
if (!fallbackToProfile && Utils.isEmpty(profilePicUrl)) {
|
||||
fallbackToProfile = true;
|
||||
new ProfilePictureFetcher(username, id, fetchListener, fetchMode, profilePicUrl, (hashtagModel != null || locationModel != null)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
new ProfilePictureFetcher(username, id, fetchListener, profilePicUrl, (hashtagModel != null || locationModel != null)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -113,7 +101,7 @@ public final class ProfileViewer extends BaseLanguageActivity {
|
||||
fallbackToProfile = true;
|
||||
if (!errorHandled) {
|
||||
errorHandled = true;
|
||||
new ProfilePictureFetcher(username, id, fetchListener, fetchModes[Math.min(2, Math.max(0, fetchIndex + 1))], profilePicUrl, (hashtagModel != null || locationModel != null))
|
||||
new ProfilePictureFetcher(username, id, fetchListener, profilePicUrl, (hashtagModel != null || locationModel != null))
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
glideRequestManager.load(profilePicUrl).into(profileBinding.imageViewer);
|
||||
@ -168,7 +156,7 @@ public final class ProfileViewer extends BaseLanguageActivity {
|
||||
}).into(profileBinding.imageViewer);
|
||||
};
|
||||
|
||||
new ProfilePictureFetcher(username, id, fetchListener, fetchMode, profilePicUrl, (hashtagModel != null || locationModel != null))
|
||||
new ProfilePictureFetcher(username, id, fetchListener, profilePicUrl, (hashtagModel != null || locationModel != null))
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@ -211,8 +199,6 @@ public final class ProfileViewer extends BaseLanguageActivity {
|
||||
final MenuItem.OnMenuItemClickListener menuItemClickListener = item -> {
|
||||
if (item == menuItemDownload) {
|
||||
downloadProfilePicture();
|
||||
} else {
|
||||
new ProfileSettingsDialog().show(fragmentManager, "settings");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@ -223,10 +209,6 @@ public final class ProfileViewer extends BaseLanguageActivity {
|
||||
menuItemDownload.setEnabled(false);
|
||||
menuItemDownload.setOnMenuItemClickListener(menuItemClickListener);
|
||||
|
||||
final MenuItem menuItemSettings = menu.findItem(R.id.action_settings);
|
||||
menuItemSettings.setVisible(true);
|
||||
menuItemSettings.setOnMenuItemClickListener(menuItemClickListener);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
234
app/src/main/java/awais/instagrabber/activities/SavedViewer.java
Executable file
@ -0,0 +1,234 @@
|
||||
package awais.instagrabber.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.PostsAdapter;
|
||||
import awais.instagrabber.asyncs.PostsFetcher;
|
||||
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.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;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS;
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
|
||||
public final class SavedViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
|
||||
private static AsyncTask<?, ?, ?> currentlyExecuting;
|
||||
public static ItemGetter itemGetter;
|
||||
private PostsAdapter postsAdapter;
|
||||
private boolean hasNextPage, autoloadPosts;
|
||||
//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<PostModel> selectedItems = new ArrayList<>();
|
||||
private final ArrayList<PostModel> allItems = new ArrayList<>();
|
||||
private MenuItem downloadAction;
|
||||
|
||||
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
|
||||
@Override
|
||||
public void onResult(final PostModel[] result) {
|
||||
if (result != null) {
|
||||
final int oldSize = allItems.size();
|
||||
allItems.addAll(Arrays.asList(result));
|
||||
|
||||
postsAdapter.notifyItemRangeInserted(oldSize, result.length);
|
||||
|
||||
savedBinding.mainPosts.post(() -> {
|
||||
savedBinding.mainPosts.setNestedScrollingEnabled(true);
|
||||
savedBinding.mainPosts.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
final PostModel model = result[result.length - 1];
|
||||
if (model != null) {
|
||||
endCursor = model.getEndCursor();
|
||||
|
||||
hasNextPage = model.hasNextPage();
|
||||
if (autoloadPosts && hasNextPage)
|
||||
currentlyExecuting = new PostsFetcher(action, endCursor, this)
|
||||
.setUsername(username).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
else {
|
||||
savedBinding.swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
model.setPageCursor(false, null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
savedBinding.swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getApplicationContext(), R.string.empty_list, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
savedBinding = ActivitySavedBinding.inflate(getLayoutInflater());
|
||||
setContentView(savedBinding.getRoot());
|
||||
savedBinding.swipeRefreshLayout.setOnRefreshListener(this);
|
||||
autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS);
|
||||
savedBinding.mainPosts.setNestedScrollingEnabled(false);
|
||||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(this, Utils.convertDpToPx(110));
|
||||
savedBinding.mainPosts.setLayoutManager(layoutManager);
|
||||
savedBinding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
|
||||
|
||||
final Intent intent = getIntent();
|
||||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_INDEX)
|
||||
|| Utils.isEmpty((action = intent.getStringExtra(Constants.EXTRAS_INDEX)))
|
||||
|| !intent.hasExtra(Constants.EXTRAS_USER)
|
||||
|| Utils.isEmpty((username = intent.getStringExtra(Constants.EXTRAS_USER)))) {
|
||||
Utils.errorFinish(this);
|
||||
return;
|
||||
}
|
||||
|
||||
savedBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(allItems, v -> {
|
||||
final Object tag = v.getTag();
|
||||
if (tag instanceof PostModel) {
|
||||
final PostModel postModel = (PostModel) tag;
|
||||
|
||||
if (postsAdapter.isSelecting) toggleSelection(postModel);
|
||||
else startActivity(new Intent(this, PostViewer.class)
|
||||
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
|
||||
.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);
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
savedBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
setSupportActionBar(savedBinding.toolbar.toolbar);
|
||||
savedBinding.toolbar.toolbar.setTitle((action.charAt(0) == '$' ? R.string.saved :
|
||||
(action.charAt(0) == '%' ? R.string.tagged : R.string.liked)));
|
||||
savedBinding.toolbar.toolbar.setSubtitle(username);
|
||||
|
||||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
||||
if (!autoloadPosts && hasNextPage) {
|
||||
savedBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
stopCurrentExecutor();
|
||||
currentlyExecuting = 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;
|
||||
return null;
|
||||
};
|
||||
|
||||
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);
|
||||
downloadAction.setVisible(false);
|
||||
|
||||
downloadAction.setOnMenuItemClickListener(item -> {
|
||||
if (selectedItems.size() > 0) {
|
||||
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public void deselectSelection(final BasePostModel postModel) {
|
||||
if (postModel instanceof PostModel) {
|
||||
selectedItems.remove(postModel);
|
||||
postModel.setSelected(false);
|
||||
if (postsAdapter != null) notifyAdapter((PostModel) postModel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
if (lazyLoader != null) lazyLoader.resetState();
|
||||
stopCurrentExecutor();
|
||||
allItems.clear();
|
||||
selectedItems.clear();
|
||||
if (postsAdapter != null) {
|
||||
postsAdapter.isSelecting = false;
|
||||
postsAdapter.notifyDataSetChanged();
|
||||
}
|
||||
savedBinding.swipeRefreshLayout.setRefreshing(true);
|
||||
new PostsFetcher(action, postsFetchListener).setUsername(username).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
@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 && selectedItems.size() > 0)
|
||||
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems);
|
||||
}
|
||||
|
||||
public static void stopCurrentExecutor() {
|
||||
if (currentlyExecuting != null) {
|
||||
try {
|
||||
currentlyExecuting.cancel(true);
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSelection(final PostModel postModel) {
|
||||
if (postModel != null && postsAdapter != null) {
|
||||
if (postModel.isSelected()) selectedItems.remove(postModel);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,12 @@ import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
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;
|
||||
@ -36,8 +38,11 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
@ -47,6 +52,7 @@ 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.PollModel;
|
||||
import awais.instagrabber.models.PostModel;
|
||||
import awais.instagrabber.models.StoryModel;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
@ -79,6 +85,7 @@ public final class StoryViewer extends BaseLanguageActivity {
|
||||
private SimpleExoPlayer player;
|
||||
private SwipeEvent swipeEvent;
|
||||
private MenuItem menuDownload;
|
||||
private PollModel poll;
|
||||
private StoryModel currentStory;
|
||||
private String url, username;
|
||||
private int slidePos = 0, lastSlidePos = 0;
|
||||
@ -121,8 +128,6 @@ public final class StoryViewer extends BaseLanguageActivity {
|
||||
|
||||
@Override
|
||||
public void onSwipe(final boolean isRightSwipe) {
|
||||
Log.d("austin_debug", "swipe: "+(isRightSwipe ? "backward " : "forward ") + slidePos + "/" + storiesLen + " "
|
||||
+ (slidePos == storiesLen - 1 && isRightSwipe == false) + " " + intent.hasExtra(Constants.FEED));
|
||||
if (storyModels != null && storiesLen > 0) {
|
||||
if (((slidePos + 1 >= storiesLen && isRightSwipe == false) || (slidePos == 0 && isRightSwipe == true))
|
||||
&& intent.hasExtra(Constants.FEED)) {
|
||||
@ -189,12 +194,45 @@ public final class StoryViewer extends BaseLanguageActivity {
|
||||
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())));
|
||||
});
|
||||
|
||||
storyViewerBinding.interactStory.setOnClickListener(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) -> {
|
||||
new VoteAction().execute(w);
|
||||
})
|
||||
.setPositiveButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
storiesAdapter.setData(storyModels);
|
||||
if (storyModels.length > 1) storyViewerBinding.storiesList.setVisibility(View.VISIBLE);
|
||||
|
||||
@ -368,9 +406,18 @@ public final class StoryViewer extends BaseLanguageActivity {
|
||||
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);
|
||||
|
||||
final PollModel poll = currentStory.getPoll();
|
||||
storyViewerBinding.interactStory.setVisibility(poll != null ? View.VISIBLE : View.GONE);
|
||||
storyViewerBinding.interactStory.setText(R.string.vote_story_poll);
|
||||
storyViewerBinding.interactStory.setTag(poll);
|
||||
|
||||
releasePlayer();
|
||||
final Intent intent = getIntent();
|
||||
if (intent.hasExtra(Constants.EXTRAS_HASHTAG)) {
|
||||
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());
|
||||
@ -408,4 +455,46 @@ public final class StoryViewer extends BaseLanguageActivity {
|
||||
}
|
||||
return returnvalue;
|
||||
}
|
||||
|
||||
class VoteAction extends AsyncTask<Integer, Void, Void> {
|
||||
int ok = -1;
|
||||
String action;
|
||||
|
||||
protected Void doInBackground(Integer... rawchoice) {
|
||||
int choice = rawchoice[0];
|
||||
final String url = "https://www.instagram.com/media/"+currentStory.getStoryMediaId()+"/"+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",
|
||||
settingsHelper.getString(Constants.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;
|
||||
}
|
||||
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -425,8 +425,6 @@ public final class FeedAdapter extends RecyclerView.Adapter<FeedItemViewHolder>
|
||||
if (btnMute != null) btnMute.setVisibility(View.VISIBLE);
|
||||
final PlayerView playerView = new PlayerView(context);
|
||||
|
||||
Log.d("austin_debug","1");
|
||||
|
||||
player = new SimpleExoPlayer.Builder(context).build();
|
||||
playerView.setPlayer(player);
|
||||
|
||||
|
90
app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java
Executable file
@ -0,0 +1,90 @@
|
||||
package awais.instagrabber.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.viewholder.NotificationViewHolder;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.utils.LocaleUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public final class NotificationsAdapter extends RecyclerView.Adapter<NotificationViewHolder> {
|
||||
private final View.OnClickListener onClickListener;
|
||||
private final MentionClickListener mentionClickListener;
|
||||
private final NotificationModel[] notificationModels;
|
||||
private LayoutInflater layoutInflater;
|
||||
|
||||
public NotificationsAdapter(final NotificationModel[] notificationModels, final View.OnClickListener onClickListener,
|
||||
final MentionClickListener mentionClickListener) {
|
||||
this.notificationModels = notificationModels;
|
||||
this.onClickListener = onClickListener;
|
||||
this.mentionClickListener = mentionClickListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public NotificationViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
||||
final Context context = parent.getContext();
|
||||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
|
||||
return new NotificationViewHolder(layoutInflater.inflate(R.layout.item_notification,
|
||||
parent, false), onClickListener, mentionClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final NotificationViewHolder holder, final int position) {
|
||||
final NotificationModel notificationModel = notificationModels[position];
|
||||
if (notificationModel != null) {
|
||||
holder.setNotificationModel(notificationModel);
|
||||
|
||||
int text = -1;
|
||||
CharSequence subtext = null;
|
||||
switch (notificationModel.getType()) {
|
||||
case LIKE:
|
||||
text = R.string.liked_notif;
|
||||
break;
|
||||
case COMMENT:
|
||||
text = R.string.comment_notif;
|
||||
subtext = notificationModel.getText();
|
||||
break;
|
||||
case MENTION:
|
||||
text = R.string.mention_notif;
|
||||
subtext = notificationModel.getText();
|
||||
break;
|
||||
case FOLLOW:
|
||||
text = R.string.follow_notif;
|
||||
break;
|
||||
}
|
||||
|
||||
holder.setCommment(text);
|
||||
holder.setSubCommment(subtext);
|
||||
holder.setDate(notificationModel.getDateTime());
|
||||
|
||||
holder.setUsername(notificationModel.getUsername());
|
||||
|
||||
final RequestManager rm = Glide.with(layoutInflater.getContext())
|
||||
.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true));
|
||||
|
||||
rm.load(notificationModel.getProfilePic()).into(holder.getProfilePicView());
|
||||
rm.load(notificationModel.getPreviewPic()).into(holder.getPreviewPicView());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return notificationModels == null ? 0 : notificationModels.length;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.text.Spannable;
|
||||
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.adapters.CommentsAdapter;
|
||||
import awais.instagrabber.customviews.RamboTextView;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
|
||||
public final class NotificationViewHolder extends RecyclerView.ViewHolder {
|
||||
private final MentionClickListener mentionClickListener;
|
||||
private final ImageView ivProfilePic, ivPreviewPic;
|
||||
private final TextView tvUsername, tvDate, tvComment, tvSubComment;
|
||||
private final View container, rightContainer;
|
||||
|
||||
public NotificationViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) {
|
||||
super(itemView);
|
||||
|
||||
container = itemView.findViewById(R.id.container);
|
||||
rightContainer = itemView.findViewById(R.id.rightContainer);
|
||||
if (onClickListener != null) container.setOnClickListener(onClickListener);
|
||||
|
||||
this.mentionClickListener = mentionClickListener;
|
||||
|
||||
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
|
||||
ivPreviewPic = itemView.findViewById(R.id.ivPreviewPic);
|
||||
tvUsername = itemView.findViewById(R.id.tvUsername);
|
||||
tvDate = itemView.findViewById(R.id.tvDate);
|
||||
tvComment = itemView.findViewById(R.id.tvComment);
|
||||
tvSubComment = itemView.findViewById(R.id.tvSubComment);
|
||||
|
||||
tvUsername.setSelected(true);
|
||||
tvDate.setSelected(true);
|
||||
}
|
||||
|
||||
public final ImageView getProfilePicView() {
|
||||
return ivProfilePic;
|
||||
}
|
||||
|
||||
public final ImageView getPreviewPicView() {
|
||||
return ivPreviewPic;
|
||||
}
|
||||
|
||||
public final void setNotificationModel(final NotificationModel notificationModel) {
|
||||
if (container != null) container.setTag(notificationModel);
|
||||
if (rightContainer != null) rightContainer.setTag(notificationModel);
|
||||
}
|
||||
|
||||
public final void setUsername(final String username) {
|
||||
if (tvUsername != null) tvUsername.setText(username);
|
||||
}
|
||||
|
||||
public final void setDate(final String date) {
|
||||
if (tvDate != null) tvDate.setText(date);
|
||||
}
|
||||
|
||||
public final void setCommment(final int commment) {
|
||||
if (tvComment != null) {
|
||||
tvComment.setText(commment);
|
||||
}
|
||||
}
|
||||
|
||||
public final void setSubCommment(final CharSequence commment) {
|
||||
if (tvSubComment != null) {
|
||||
tvSubComment.setText(commment, commment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL);
|
||||
((RamboTextView) tvSubComment).setMentionClickListener(mentionClickListener);
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
@ -45,17 +46,10 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
@Nullable
|
||||
@Override
|
||||
protected final DiscoverItemModel[] doInBackground(final Void... voids) {
|
||||
// to check if file exists
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
File customDir = null;
|
||||
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = settingsHelper.getString(FOLDER_PATH);
|
||||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath);
|
||||
}
|
||||
|
||||
DiscoverItemModel[] result = null;
|
||||
|
||||
final ArrayList<DiscoverItemModel> discoverItemModels = fetchItems(downloadDir, customDir, null, maxId);
|
||||
final ArrayList<DiscoverItemModel> discoverItemModels = fetchItems(null, maxId);
|
||||
if (discoverItemModels != null) {
|
||||
result = discoverItemModels.toArray(new DiscoverItemModel[0]);
|
||||
if (result.length > 0) {
|
||||
@ -67,8 +61,7 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
return result;
|
||||
}
|
||||
|
||||
private ArrayList<DiscoverItemModel> fetchItems(final File downloadDir, final File customDir,
|
||||
ArrayList<DiscoverItemModel> discoverItemModels, final String maxId) {
|
||||
private ArrayList<DiscoverItemModel> fetchItems(ArrayList<DiscoverItemModel> discoverItemModels, final String maxId) {
|
||||
try {
|
||||
final String url = "https://www.instagram.com/explore/grid/?is_prefetch=false&omit_cover_media=true&module=explore_popular" +
|
||||
"&use_sectional_payload=false&cluster_id=explore_all%3A0&include_fixed_destinations=true" + maxId;
|
||||
@ -99,8 +92,7 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
if ("media_grid".equals(layoutType)) {
|
||||
final JSONArray medias = layoutContent.getJSONArray("medias");
|
||||
for (int j = 0; j < medias.length(); ++j)
|
||||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir,
|
||||
medias.getJSONObject(j).getJSONObject("media")));
|
||||
discoverItemModels.add(makeDiscoverModel(medias.getJSONObject(j).getJSONObject("media")));
|
||||
|
||||
} else {
|
||||
final boolean isOneSide = "one_by_two_left".equals(layoutType);
|
||||
@ -108,14 +100,12 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
|
||||
final JSONObject layoutItem = layoutContent.getJSONObject(isOneSide ? "one_by_two_item" : "two_by_two_item");
|
||||
if (layoutItem.has("media"))
|
||||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir,
|
||||
layoutItem.getJSONObject("media")));
|
||||
discoverItemModels.add(makeDiscoverModel(layoutItem.getJSONObject("media")));
|
||||
|
||||
if (layoutContent.has("fill_items")) {
|
||||
final JSONArray fillItems = layoutContent.getJSONArray("fill_items");
|
||||
for (int j = 0; j < fillItems.length(); ++j)
|
||||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir,
|
||||
fillItems.getJSONObject(j).getJSONObject("media")));
|
||||
discoverItemModels.add(makeDiscoverModel(fillItems.getJSONObject(j).getJSONObject("media")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,8 +119,7 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
if (this.isFirst) {
|
||||
final int size = discoverItemModels.size();
|
||||
if (size > 50) this.isFirst = false;
|
||||
discoverItemModels = fetchItems(downloadDir, customDir, discoverItemModels,
|
||||
"&max_id=" + (lastId++));
|
||||
discoverItemModels = fetchItems(discoverItemModels, "&max_id=" + (lastId++));
|
||||
}
|
||||
} else {
|
||||
urlConnection.disconnect();
|
||||
@ -149,8 +138,7 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private DiscoverItemModel makeDiscoverModel(final File downloadDir, final File customDir,
|
||||
@NonNull final JSONObject media) throws Exception {
|
||||
private DiscoverItemModel makeDiscoverModel(@NonNull final JSONObject media) throws Exception {
|
||||
final JSONObject user = media.getJSONObject(Constants.EXTRAS_USER);
|
||||
final String username = user.getString(Constants.EXTRAS_USERNAME);
|
||||
// final ProfileModel userProfileModel = new ProfileModel(user.getBoolean("is_private"),
|
||||
@ -176,8 +164,18 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
|
||||
media.getString("code"),
|
||||
Utils.getThumbnailUrl(media, mediaType));
|
||||
|
||||
Utils.checkExistence(downloadDir, customDir, username,
|
||||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER, -1, model);
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
|
||||
// to check if file exists
|
||||
File customDir = null;
|
||||
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = settingsHelper.getString(FOLDER_PATH);
|
||||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
}
|
||||
|
||||
Utils.checkExistence(downloadDir, customDir, mediaType == MediaItemType.MEDIA_TYPE_SLIDER, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ public final class LocationFetcher extends AsyncTask<Void, Void, LocationModel>
|
||||
private final String idSlug;
|
||||
|
||||
public LocationFetcher(String idSlug, FetchListener<LocationModel> fetchListener) {
|
||||
Log.d("austin_debug", idSlug);
|
||||
// idSlug = id + "/" + slug
|
||||
this.idSlug = idSlug;
|
||||
this.fetchListener = fetchListener;
|
||||
|
85
app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java
Executable file
@ -0,0 +1,85 @@
|
||||
package awais.instagrabber.asyncs;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.NotificationModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
|
||||
public final class NotificationsFetcher extends AsyncTask<Void, Void, NotificationModel[]> {
|
||||
private final FetchListener<NotificationModel[]> fetchListener;
|
||||
|
||||
public NotificationsFetcher(final FetchListener<NotificationModel[]> fetchListener) {
|
||||
this.fetchListener = fetchListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NotificationModel[] doInBackground(final Void... voids) {
|
||||
NotificationModel[] result = null;
|
||||
final String url = "https://www.instagram.com/accounts/activity/?__a=1";
|
||||
|
||||
try {
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.setUseCaches(false);
|
||||
conn.connect();
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
JSONObject data = new JSONObject(Utils.readFromConnection(conn))
|
||||
.getJSONObject("graphql").getJSONObject("user").getJSONObject("activity_feed").getJSONObject("edge_web_activity_feed");
|
||||
|
||||
JSONArray media;
|
||||
if ((media = data.optJSONArray("edges")) != null && media.length() > 0 &&
|
||||
(data = media.optJSONObject(0).optJSONObject("node")) != null) {
|
||||
|
||||
final int mediaLen = media.length();
|
||||
|
||||
final NotificationModel[] models = new NotificationModel[mediaLen];
|
||||
for (int i = 0; i < mediaLen; ++i) {
|
||||
data = media.optJSONObject(i).optJSONObject("node");
|
||||
if (Utils.getNotifType(data.getString("__typename")) == null) continue;
|
||||
models[i] = new NotificationModel(data.getString(Constants.EXTRAS_ID),
|
||||
data.optString("text"), // comments or mentions
|
||||
data.getLong("timestamp"),
|
||||
data.getJSONObject("user").getString("username"),
|
||||
data.getJSONObject("user").getString("profile_pic_url"),
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("shortcode") : null,
|
||||
!data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null,
|
||||
Utils.getNotifType(data.getString("__typename")));
|
||||
}
|
||||
result = models;
|
||||
}
|
||||
}
|
||||
|
||||
conn.disconnect();
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_NOTIFICATION_FETCHER, "doInBackground");
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (fetchListener != null) fetchListener.doBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final NotificationModel[] result) {
|
||||
if (fetchListener != null) fetchListener.onResult(result);
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
@ -41,19 +42,22 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
|
||||
conn.connect();
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
// to check if file exists
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
File customDir = null;
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
||||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath);
|
||||
}
|
||||
|
||||
final JSONObject media = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("graphql")
|
||||
.getJSONObject("shortcode_media");
|
||||
|
||||
final String username = media.has("owner") ? media.getJSONObject("owner").getString(Constants.EXTRAS_USERNAME) : null;
|
||||
|
||||
// to check if file exists
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
File customDir = null;
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath);
|
||||
}
|
||||
|
||||
final long timestamp = media.getLong("taken_at_timestamp");
|
||||
|
||||
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video");
|
||||
@ -95,7 +99,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
|
||||
postModel.setCommentsCount(commentsCount);
|
||||
postModel.setCommentsEndCursor(endCursor);
|
||||
|
||||
Utils.checkExistence(downloadDir, customDir, username, false, -1, postModel);
|
||||
Utils.checkExistence(downloadDir, customDir, false, postModel);
|
||||
|
||||
result = new ViewerPostModel[]{postModel};
|
||||
|
||||
@ -119,7 +123,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
|
||||
media.optJSONObject("location"));
|
||||
postModels[i].setSliderDisplayUrl(node.getString("display_url"));
|
||||
|
||||
Utils.checkExistence(downloadDir, customDir, username, true, i, postModels[i]);
|
||||
Utils.checkExistence(downloadDir, customDir, true, postModels[i]);
|
||||
}
|
||||
|
||||
postModels[0].setCommentsCount(commentsCount);
|
||||
|
@ -19,6 +19,7 @@ import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
@ -49,6 +50,9 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
|
||||
@Override
|
||||
protected PostModel[] doInBackground(final Void... voids) {
|
||||
final boolean isHashTag = id.charAt(0) == '#';
|
||||
final boolean isSaved = id.charAt(0) == '$';
|
||||
final boolean isTagged = id.charAt(0) == '%';
|
||||
//final boolean isLiked = id.charAt(0) == '^';
|
||||
final boolean isLocation = id.contains("/");
|
||||
|
||||
final String url;
|
||||
@ -58,6 +62,12 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
|
||||
else if (isLocation)
|
||||
url = "https://www.instagram.com/graphql/query/?query_hash=36bd0f2bf5911908de389b8ceaa3be6d&variables=" +
|
||||
"{\"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 + "\"}";
|
||||
else if (isTagged)
|
||||
url = "https://www.instagram.com/graphql/query/?query_hash=ff260833edf142911047af6024eb634a&variables=" +
|
||||
"{\"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;
|
||||
|
||||
@ -69,10 +79,12 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
|
||||
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
// to check if file exists
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
File customDir = null;
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH +
|
||||
(Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : ""));
|
||||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath);
|
||||
}
|
||||
|
||||
@ -80,7 +92,9 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
|
||||
.getJSONObject(isHashTag ? Constants.EXTRAS_HASHTAG :
|
||||
(isLocation ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_USER))
|
||||
.getJSONObject(isHashTag ? "edge_hashtag_to_media" :
|
||||
(isLocation ? "edge_location_to_media" : "edge_owner_to_timeline_media"));
|
||||
(isLocation ? "edge_location_to_media" :
|
||||
(isSaved ? "edge_saved_media" :
|
||||
(isTagged ? "edge_user_to_photos_of_you" : "edge_owner_to_timeline_media"))));
|
||||
|
||||
final String endCursor;
|
||||
final boolean hasNextPage;
|
||||
@ -115,7 +129,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
|
||||
mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"),
|
||||
mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count"));
|
||||
|
||||
Utils.checkExistence(downloadDir, customDir, username, isSlider, -1, models[i]);
|
||||
Utils.checkExistence(downloadDir, customDir, isSlider, models[i]);
|
||||
}
|
||||
|
||||
if (models[models.length - 1] != null)
|
||||
|
@ -5,31 +5,26 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awaisomereport.LogCollector;
|
||||
import awais.instagrabber.models.enums.ProfilePictureFetchMode;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
|
||||
public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
|
||||
private final FetchListener<String> fetchListener;
|
||||
private final String userName, userId, picUrl;
|
||||
private final boolean isHashtag;
|
||||
private ProfilePictureFetchMode fetchMode;
|
||||
|
||||
public ProfilePictureFetcher(final String userName, final String userId, final FetchListener<String> fetchListener,
|
||||
final ProfilePictureFetchMode fetchMode, final String picUrl, final boolean isHashtag) {
|
||||
final String picUrl, final boolean isHashtag) {
|
||||
this.fetchListener = fetchListener;
|
||||
this.fetchMode = fetchMode;
|
||||
this.userName = userName;
|
||||
this.userId = userId;
|
||||
this.picUrl = picUrl;
|
||||
@ -40,69 +35,28 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
|
||||
protected String doInBackground(final Void... voids) {
|
||||
String out = picUrl;
|
||||
if (!isHashtag) try {
|
||||
if (fetchMode == ProfilePictureFetchMode.INSTA_STALKER) fetchMode = ProfilePictureFetchMode.INSTADP;
|
||||
final String url;
|
||||
|
||||
if (fetchMode == ProfilePictureFetchMode.INSTADP)
|
||||
url = "https://instadp.com/fullsize/" + userName;
|
||||
else // select from s1, s2, s3 but s1 works fine
|
||||
url = "https://instafullsize.com/ifsapi/ig/photo/s1/" + userName + "?igid=" + userId;
|
||||
|
||||
// prolly http://167.99.85.4/instagram/userid?profile-url=the.badak
|
||||
final String url = "https://i.instagram.com/api/v1/users/"+userId+"/info/";
|
||||
|
||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setUseCaches(false);
|
||||
|
||||
if (fetchMode == ProfilePictureFetchMode.INSTAFULLSIZE) {
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Authorization", "fjgt842ff582a");
|
||||
}
|
||||
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
|
||||
|
||||
final String result = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? Utils.readFromConnection(conn) : null;
|
||||
conn.disconnect();
|
||||
|
||||
if (!Utils.isEmpty(result)) {
|
||||
final Document doc = Jsoup.parse(result);
|
||||
boolean fallback = false;
|
||||
|
||||
if (fetchMode == ProfilePictureFetchMode.INSTADP) {
|
||||
final int imgIndex = result.indexOf("preloadImg('"), lastIndex;
|
||||
|
||||
Element element = doc.selectFirst(".instadp");
|
||||
if (element != null && (element = element.selectFirst(".picture")) != null)
|
||||
out = element.attr("src");
|
||||
else if ((element = doc.selectFirst(".download-btn")) != null)
|
||||
out = element.attr("href");
|
||||
else if (imgIndex != -1 && (lastIndex = result.indexOf("')", imgIndex)) != -1)
|
||||
out = result.substring(imgIndex + 12, lastIndex);
|
||||
else fallback = true;
|
||||
|
||||
} else if (fetchMode == ProfilePictureFetchMode.INSTAFULLSIZE) {
|
||||
try {
|
||||
final JSONObject object = new JSONObject(result);
|
||||
out = object.getString("result");
|
||||
} catch (final Exception e) {
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
fallback = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (fallback) {
|
||||
final Elements imgs = doc.getElementsByTag("img");
|
||||
for (final Element img : imgs) {
|
||||
final String imgStr = img.toString();
|
||||
if (imgStr.contains("cdninstagram.com")) return img.attr("src");
|
||||
}
|
||||
}
|
||||
JSONObject data = new JSONObject(result).getJSONObject("user");
|
||||
if (data.has("hd_profile_pic_url_info"))
|
||||
out = data.getJSONObject("hd_profile_pic_url_info").optString("url");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_PICTURE_FETCHER, "doInBackground",
|
||||
new Pair<>("fetchMode", fetchMode));
|
||||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_PICTURE_FETCHER, "doInBackground");
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
}
|
||||
|
||||
if (out == null) out = picUrl;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import java.net.URL;
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.models.PollModel;
|
||||
import awais.instagrabber.models.StoryModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -34,8 +35,8 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
|
||||
@Override
|
||||
protected StoryModel[] doInBackground(final Void... voids) {
|
||||
StoryModel[] result = null;
|
||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=52a36e788a02a3c612742ed5146f1676&variables=" +
|
||||
"{\"precomposed_overlay\":false,"
|
||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=90709b530ea0969f002c86a89b4f2b8d&variables=" +
|
||||
"{\"precomposed_overlay\":false,\"show_story_viewer_list\":false,\"stories_video_dash_manifest\":false,"
|
||||
+(!Utils.isEmpty(hashtag) ? ("\"tag_names\":\""+hashtag+"\"}") : (
|
||||
location ? "\"location_ids\":[\""+id.split("/")[0]+"\"]}" : "\"reel_ids\":[\"" + id + "\"]}"));
|
||||
|
||||
@ -73,12 +74,25 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
|
||||
if (isVideo && videoResources != null)
|
||||
models[i].setVideoUrl(Utils.getHighQualityPost(videoResources, true));
|
||||
|
||||
if (!data.isNull("story_app_attribution"))
|
||||
models[i].setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
|
||||
|
||||
for (int j = 0; j < tappableLength; ++j) {
|
||||
JSONObject tappableObject = tappableObjects.getJSONObject(j);
|
||||
if (tappableObject.optString("__typename").equals("GraphTappableFeedMedia")) {
|
||||
tappableObject = tappableObject.getJSONObject("media");
|
||||
models[i].setTappableShortCode(tappableObject.getString(Constants.EXTRAS_SHORTCODE));
|
||||
break;
|
||||
models[i].setTappableShortCode(tappableObject.getJSONObject("media").getString(Constants.EXTRAS_SHORTCODE));
|
||||
}
|
||||
else if (tappableObject.optString("__typename").equals("GraphTappableStoryPoll")) {
|
||||
Log.d("austin_debug", "poll: "+url+" "+tappableObject);
|
||||
models[i].setPoll(new PollModel(
|
||||
tappableObject.getString("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)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
package awais.instagrabber.dialogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE;
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
public final class ProfileSettingsDialog extends BottomSheetDialogFragment implements AdapterView.OnItemSelectedListener {
|
||||
private int fetchIndex;
|
||||
private Activity activity;
|
||||
private Spinner spProfileFetchMode;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
final Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
|
||||
final Context context = getContext();
|
||||
activity = context instanceof Activity ? (Activity) context : getActivity();
|
||||
|
||||
final View contentView = View.inflate(activity, R.layout.dialog_profile_settings, null);
|
||||
|
||||
spProfileFetchMode = contentView.findViewById(R.id.spProfileFetchMode);
|
||||
|
||||
fetchIndex = Math.min(2, Math.max(0, settingsHelper.getInteger(PROFILE_FETCH_MODE)));
|
||||
spProfileFetchMode.setSelection(fetchIndex);
|
||||
spProfileFetchMode.setOnItemSelectedListener(this);
|
||||
|
||||
dialog.setContentView(contentView);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(@NonNull final DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (activity != null && (spProfileFetchMode == null || fetchIndex != spProfileFetchMode.getSelectedItemPosition()))
|
||||
activity.recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
|
||||
settingsHelper.putInteger(PROFILE_FETCH_MODE, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(final AdapterView<?> parent) { }
|
||||
}
|
58
app/src/main/java/awais/instagrabber/models/NotificationModel.java
Executable file
@ -0,0 +1,58 @@
|
||||
package awais.instagrabber.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.models.enums.NotificationType;
|
||||
|
||||
public final class NotificationModel {
|
||||
private final String id, username, profilePicUrl, shortcode, previewUrl;
|
||||
private final NotificationType type;
|
||||
private final CharSequence text;
|
||||
private final long timestamp;
|
||||
|
||||
public NotificationModel(final String id, final String text, final long timestamp, final String username,
|
||||
final String profilePicUrl, final String shortcode, final String previewUrl, final NotificationType type) {
|
||||
this.id = id;
|
||||
this.text = Utils.hasMentions(text) ? Utils.getMentionText(text) : text;
|
||||
this.timestamp = timestamp;
|
||||
this.username = username;
|
||||
this.profilePicUrl = profilePicUrl;
|
||||
this.shortcode = shortcode;
|
||||
this.previewUrl = previewUrl;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public CharSequence getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getDateTime() {
|
||||
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getProfilePic() {
|
||||
return profilePicUrl;
|
||||
}
|
||||
|
||||
public String getShortcode() {
|
||||
return shortcode;
|
||||
}
|
||||
|
||||
public String getPreviewPic() {
|
||||
return previewUrl;
|
||||
}
|
||||
|
||||
public NotificationType getType() { return type; }
|
||||
}
|
52
app/src/main/java/awais/instagrabber/models/PollModel.java
Executable file
@ -0,0 +1,52 @@
|
||||
package awais.instagrabber.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public final class PollModel implements Serializable {
|
||||
private int leftcount, rightcount, mychoice;
|
||||
private final String id, question, leftchoice, rightchoice;
|
||||
|
||||
public PollModel(final String id, final String question, final String leftchoice, final int leftcount,
|
||||
final String rightchoice, final int rightcount, final int mychoice) {
|
||||
this.id = id; // only the poll id
|
||||
this.question = question;
|
||||
this.leftchoice = leftchoice;
|
||||
this.leftcount = leftcount;
|
||||
this.rightchoice = rightchoice;
|
||||
this.rightcount = rightcount;
|
||||
this.mychoice = mychoice;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getQuestion() {
|
||||
return question;
|
||||
}
|
||||
|
||||
public String getLeftChoice() {
|
||||
return leftchoice;
|
||||
}
|
||||
|
||||
public int getLeftCount() {
|
||||
return leftcount;
|
||||
}
|
||||
|
||||
public String getRightChoice() {
|
||||
return rightchoice;
|
||||
}
|
||||
|
||||
public int getRightCount() {
|
||||
return rightcount;
|
||||
}
|
||||
|
||||
public int getMyChoice() { return mychoice; }
|
||||
|
||||
public int setMyChoice(final int choice) {
|
||||
this.mychoice = choice;
|
||||
if (choice == 0) this.leftcount += 1;
|
||||
else if (choice == 1) this.rightcount += 1;
|
||||
return choice;
|
||||
}
|
||||
}
|
@ -8,7 +8,8 @@ public final class StoryModel implements Serializable {
|
||||
private final String storyMediaId, storyUrl, username;
|
||||
private final MediaItemType itemType;
|
||||
private final long timestamp;
|
||||
private String videoUrl, tappableShortCode;
|
||||
private String videoUrl, tappableShortCode, spotify;
|
||||
private PollModel poll;
|
||||
private int position;
|
||||
private boolean isCurrentSlide = false;
|
||||
|
||||
@ -44,6 +45,10 @@ public final class StoryModel implements Serializable {
|
||||
return tappableShortCode;
|
||||
}
|
||||
|
||||
public PollModel getPoll() {
|
||||
return poll;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
@ -52,10 +57,22 @@ public final class StoryModel implements Serializable {
|
||||
this.videoUrl = videoUrl;
|
||||
}
|
||||
|
||||
public String getSpotify() {
|
||||
return spotify;
|
||||
}
|
||||
|
||||
public void setSpotify(final String spotify) {
|
||||
this.spotify = spotify;
|
||||
}
|
||||
|
||||
public void setTappableShortCode(final String tappableShortCode) {
|
||||
this.tappableShortCode = tappableShortCode;
|
||||
}
|
||||
|
||||
public void setPoll(final PollModel poll) {
|
||||
this.poll = poll;
|
||||
}
|
||||
|
||||
public void setPosition(final int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ public enum DownloadMethod {
|
||||
DOWNLOAD_DISCOVER,
|
||||
DOWNLOAD_FEED,
|
||||
DOWNLOAD_POST_VIEWER,
|
||||
DOWNLOAD_DIRECT;
|
||||
DOWNLOAD_DIRECT,
|
||||
DOWNLOAD_SAVED;
|
||||
}
|
@ -6,4 +6,5 @@ public enum ItemGetType implements Serializable {
|
||||
MAIN_ITEMS,
|
||||
DISCOVER_ITEMS,
|
||||
FEED_ITEMS,
|
||||
SAVED_ITEMS
|
||||
}
|
10
app/src/main/java/awais/instagrabber/models/enums/NotificationType.java
Executable file
@ -0,0 +1,10 @@
|
||||
package awais.instagrabber.models.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public enum NotificationType implements Serializable {
|
||||
LIKE,
|
||||
FOLLOW,
|
||||
COMMENT,
|
||||
MENTION
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package awais.instagrabber.models.enums;
|
||||
|
||||
public enum ProfilePictureFetchMode {
|
||||
INSTADP,
|
||||
INSTAFULLSIZE,
|
||||
INSTA_STALKER,
|
||||
}
|
@ -10,7 +10,6 @@ public final class Constants {
|
||||
public static final String APP_THEME = "app_theme";
|
||||
public static final String APP_LANGUAGE = "app_language";
|
||||
public static final String PREV_INSTALL_VERSION = "prevVersion";
|
||||
public static final String PROFILE_FETCH_MODE = "profile_fetch_mode";
|
||||
// boolean prefs
|
||||
public static final String DOWNLOAD_USER_FOLDER = "download_user_folder";
|
||||
public static final String BOTTOM_TOOLBAR = "bottom_toolbar";
|
||||
|
@ -219,7 +219,6 @@ public final class ExportImportUtils {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put(Constants.APP_THEME, settingsHelper.getInteger(Constants.APP_THEME));
|
||||
json.put(Constants.APP_LANGUAGE, settingsHelper.getInteger(Constants.APP_LANGUAGE));
|
||||
json.put(Constants.PROFILE_FETCH_MODE, settingsHelper.getInteger(Constants.PROFILE_FETCH_MODE));
|
||||
|
||||
String str = settingsHelper.getString(Constants.FOLDER_PATH);
|
||||
if (!Utils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str);
|
||||
|
@ -23,7 +23,6 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
|
||||
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
|
||||
import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE;
|
||||
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
|
||||
|
||||
public final class SettingsHelper {
|
||||
@ -109,6 +108,6 @@ public final class SettingsHelper {
|
||||
AUTOLOAD_POSTS, CUSTOM_DATE_TIME_FORMAT_ENABLED})
|
||||
public @interface BooleanSettings {}
|
||||
|
||||
@StringDef({APP_THEME, APP_LANGUAGE, PROFILE_FETCH_MODE, PREV_INSTALL_VERSION})
|
||||
@StringDef({APP_THEME, APP_LANGUAGE, PREV_INSTALL_VERSION})
|
||||
public @interface IntegerSettings {}
|
||||
}
|
@ -62,6 +62,7 @@ import java.util.regex.Pattern;
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.activities.Main;
|
||||
import awais.instagrabber.activities.SavedViewer;
|
||||
import awais.instagrabber.asyncs.DownloadAsync;
|
||||
import awais.instagrabber.asyncs.PostFetcher;
|
||||
import awais.instagrabber.customviews.CommentMentionClickSpan;
|
||||
@ -70,6 +71,7 @@ import awais.instagrabber.models.BasePostModel;
|
||||
import awais.instagrabber.models.FeedStoryModel;
|
||||
import awais.instagrabber.models.HighlightModel;
|
||||
import awais.instagrabber.models.IntentModel;
|
||||
import awais.instagrabber.models.PollModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.models.StoryModel;
|
||||
import awais.instagrabber.models.direct_messages.DirectItemModel;
|
||||
@ -80,6 +82,7 @@ import awais.instagrabber.models.enums.DownloadMethod;
|
||||
import awais.instagrabber.models.enums.InboxReadState;
|
||||
import awais.instagrabber.models.enums.IntentModelType;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.models.enums.NotificationType;
|
||||
import awais.instagrabber.models.enums.RavenExpiringMediaType;
|
||||
import awais.instagrabber.models.enums.RavenMediaViewType;
|
||||
import awaisomereport.LogCollector;
|
||||
@ -126,6 +129,7 @@ public final class Utils {
|
||||
try {
|
||||
final URI uri1 = new URI("https://instagram.com");
|
||||
final URI uri2 = new URI("https://instagram.com/");
|
||||
final URI uri3 = new URI("https://i.instagram.com/");
|
||||
for (final String cookie : cookieRaw.split(";")) {
|
||||
final String[] strings = cookie.split("=", 2);
|
||||
final HttpCookie httpCookie = new HttpCookie(strings[0].trim(), strings[1].trim());
|
||||
@ -134,6 +138,7 @@ public final class Utils {
|
||||
httpCookie.setVersion(0);
|
||||
cookieStore.add(uri1, httpCookie);
|
||||
cookieStore.add(uri2, httpCookie);
|
||||
cookieStore.add(uri3, httpCookie);
|
||||
}
|
||||
} catch (final URISyntaxException e) {
|
||||
if (logCollector != null)
|
||||
@ -730,6 +735,14 @@ public final class Utils {
|
||||
return RavenExpiringMediaType.RAVEN_UNKNOWN;
|
||||
}
|
||||
|
||||
public static NotificationType getNotifType(final String itemType) {
|
||||
if ("GraphLikeAggregatedStory".equals(itemType)) return NotificationType.LIKE;
|
||||
if ("GraphFollowAggregatedStory".equals(itemType)) return NotificationType.FOLLOW;
|
||||
if ("GraphCommentMediaStory".equals(itemType)) return NotificationType.COMMENT;
|
||||
if ("GraphMentionStory".equals(itemType)) return NotificationType.MENTION;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int convertDpToPx(final float dp) {
|
||||
if (displayMetrics == null)
|
||||
displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
@ -825,12 +838,14 @@ public final class Utils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void batchDownload(@NonNull final Context context, @Nullable final String username, final DownloadMethod method,
|
||||
public static void batchDownload(@NonNull final Context context, @Nullable String username, final DownloadMethod method,
|
||||
final List<? extends BasePostModel> itemsToDownload) {
|
||||
if (settingsHelper == null) settingsHelper = new SettingsHelper(context);
|
||||
|
||||
if (itemsToDownload == null || itemsToDownload.size() < 1) return;
|
||||
|
||||
if (username.charAt(0) == '@') username = username.substring(1);
|
||||
|
||||
if (ContextCompat.checkSelfPermission(context, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
|
||||
batchDownloadImpl(context, username, method, itemsToDownload);
|
||||
else if (context instanceof Activity)
|
||||
@ -851,6 +866,7 @@ public final class Utils {
|
||||
|
||||
if (dir.exists() || dir.mkdirs()) {
|
||||
final Main main = method != DownloadMethod.DOWNLOAD_FEED && context instanceof Main ? (Main) context : null;
|
||||
final SavedViewer saved = method == DownloadMethod.DOWNLOAD_SAVED && context instanceof SavedViewer ? (SavedViewer) context : null;
|
||||
|
||||
final int itemsToDownloadSize = itemsToDownload.size();
|
||||
|
||||
@ -858,12 +874,34 @@ public final class Utils {
|
||||
for (int i = itemsToDownloadSize - 1; i >= 0; i--) {
|
||||
final BasePostModel selectedItem = itemsToDownload.get(i);
|
||||
|
||||
if (main == null) {
|
||||
if (main == null && saved == null) {
|
||||
new DownloadAsync(context,
|
||||
selectedItem.getDisplayUrl(),
|
||||
getDownloadSaveFile(finalDir, selectedItem, ""),
|
||||
null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
} else if (saved != null) {
|
||||
new PostFetcher(selectedItem.getShortCode(), result -> {
|
||||
if (result != null) {
|
||||
final int resultsSize = result.length;
|
||||
final boolean multiResult = resultsSize > 1;
|
||||
|
||||
for (int j = 0; j < resultsSize; j++) {
|
||||
final BasePostModel model = result[j];
|
||||
final File saveFile = getDownloadSaveFile(finalDir, model, multiResult ? "_slide_" + (j + 1) : "");
|
||||
|
||||
new DownloadAsync(context,
|
||||
model.getDisplayUrl(),
|
||||
saveFile,
|
||||
file -> {
|
||||
model.setDownloaded(true);
|
||||
saved.deselectSelection(selectedItem);
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
} else {
|
||||
saved.deselectSelection(selectedItem);
|
||||
}
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
new PostFetcher(selectedItem.getShortCode(), result -> {
|
||||
if (result != null) {
|
||||
@ -971,30 +1009,21 @@ public final class Utils {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public static void checkExistence(final File downloadDir, final File customDir, final String username, final boolean isSlider,
|
||||
final int sliderIndex, @NonNull final BasePostModel model) {
|
||||
public static void checkExistence(final File downloadDir, final File customDir, final boolean isSlider,
|
||||
@NonNull final BasePostModel model) {
|
||||
boolean exists = false;
|
||||
|
||||
try {
|
||||
final String displayUrl = model.getDisplayUrl();
|
||||
final int index = displayUrl.indexOf('?');
|
||||
|
||||
final String fileName = model.getPostId() + '_' + model.getPosition();
|
||||
final String fileName = model.getPostId() + '_';
|
||||
final String extension = displayUrl.substring(index - 4, index);
|
||||
|
||||
final String fileWithoutPrefix = fileName + extension;
|
||||
final String fileWithoutPrefix = fileName + '0' + extension;
|
||||
exists = new File(downloadDir, fileWithoutPrefix).exists();
|
||||
if (!exists) {
|
||||
if (customDir != null) exists = new File(customDir, fileWithoutPrefix).exists();
|
||||
if (!exists && !Utils.isEmpty(username)) {
|
||||
exists = new File(new File(downloadDir, username), fileWithoutPrefix).exists();
|
||||
}
|
||||
if (!exists && customDir != null)
|
||||
exists = new File(new File(customDir, username), fileWithoutPrefix).exists();
|
||||
}
|
||||
|
||||
if (!exists && isSlider && sliderIndex != -1) {
|
||||
final String fileWithPrefix = fileName + "_slide_[\\d]+" + extension;
|
||||
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\" + extension + ")";
|
||||
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name);
|
||||
|
||||
File[] files = downloadDir.listFiles(filenameFilter);
|
||||
@ -1007,7 +1036,6 @@ public final class Utils {
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogCollector.LogFile.UTILS, "checkExistence",
|
||||
new Pair<>("isSlider", isSlider),
|
||||
new Pair<>("sliderIndex", sliderIndex),
|
||||
new Pair<>("model", model));
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
||||
}
|
||||
@ -1163,13 +1191,25 @@ public final class Utils {
|
||||
if (isVideo && data.has("video_resources"))
|
||||
storyModels[j].setVideoUrl(Utils.getHighQualityPost(data.getJSONArray("video_resources"), true));
|
||||
|
||||
if (!data.isNull("story_app_attribution"))
|
||||
storyModels[j].setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
|
||||
|
||||
if (hasTappableObjecs) {
|
||||
for (int k = 0; k < tappableLength; ++k) {
|
||||
JSONObject jsonObject = tappableObjects.getJSONObject(k);
|
||||
if (jsonObject.getString("__typename").equals("GraphTappableFeedMedia") && jsonObject.has("media")) {
|
||||
jsonObject = jsonObject.getJSONObject("media");
|
||||
storyModels[j].setTappableShortCode(jsonObject.getString(Constants.EXTRAS_SHORTCODE));
|
||||
break;
|
||||
storyModels[j].setTappableShortCode(jsonObject.getJSONObject("media").getString(Constants.EXTRAS_SHORTCODE));
|
||||
}
|
||||
else if (jsonObject.optString("__typename").equals("GraphTappableStoryPoll") && !jsonObject.isNull("id")) {
|
||||
storyModels[j].setPoll(new PollModel(
|
||||
jsonObject.getString("id"),
|
||||
jsonObject.getString("question"),
|
||||
jsonObject.getJSONArray("tallies").getJSONObject(0).getString("text"),
|
||||
jsonObject.getJSONArray("tallies").getJSONObject(0).getInt("count"),
|
||||
jsonObject.getJSONArray("tallies").getJSONObject(1).getString("text"),
|
||||
jsonObject.getJSONArray("tallies").getJSONObject(1).getInt("count"),
|
||||
jsonObject.optInt("viewer_vote", -1)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,8 +108,10 @@ public final class LogCollector {
|
||||
ASYNC_FEED_FETCHER("async-feed-fetcher.txt"),
|
||||
ASYNC_HASHTAG_FETCHER("async-hashtag-fetcher.txt"),
|
||||
ASYNC_LOCATION_FETCHER("async-location-fetcher.txt"),
|
||||
ASYNC_NOTIFICATION_FETCHER("async-notification-fetcher.txt"),
|
||||
ASYNC_PROFILE_FETCHER("async-profile-fetcher.txt"),
|
||||
ASYNC_PROFILE_PICTURE_FETCHER("async-pfp-fetcher.txt"),
|
||||
ASYNC_SAVED_FETCHER("async-saved-fetcher.txt"),
|
||||
ASYNC_STORY_STATUS_FETCHER("async-story-status-fetcher.txt"),
|
||||
ASYNC_DISCOVER_FETCHER("async-discover-fetcher.txt"),
|
||||
ASYNC_COMMENTS_FETCHER("async-comments-fetcher.txt"),
|
||||
|
10
app/src/main/res/drawable-anydpi/ic_bookmark.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable-anydpi/ic_like.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable-anydpi/ic_notif.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable-hdpi/ic_bookmark.png
Normal file
After Width: | Height: | Size: 210 B |
BIN
app/src/main/res/drawable-hdpi/ic_like.png
Normal file
After Width: | Height: | Size: 333 B |
BIN
app/src/main/res/drawable-hdpi/ic_notif.png
Normal file
After Width: | Height: | Size: 269 B |
BIN
app/src/main/res/drawable-mdpi/ic_bookmark.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
app/src/main/res/drawable-mdpi/ic_like.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
app/src/main/res/drawable-mdpi/ic_notif.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
app/src/main/res/drawable-xhdpi/ic_bookmark.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
app/src/main/res/drawable-xhdpi/ic_like.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
app/src/main/res/drawable-xhdpi/ic_notif.png
Normal file
After Width: | Height: | Size: 321 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_bookmark.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_like.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_notif.png
Normal file
After Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 3.8 KiB |
@ -112,26 +112,25 @@
|
||||
android:id="@+id/profileActions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/mainUrl"
|
||||
android:weightSum="3">
|
||||
android:layout_below="@id/mainUrl">
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnFollow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/follow"
|
||||
android:textColor="@color/btn_pink_text_color"
|
||||
android:textSize="18sp"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="@color/btn_pink_background"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnRestrict"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginStart="1dp"
|
||||
@ -139,31 +138,69 @@
|
||||
android:layout_weight="1"
|
||||
android:text="@string/restrict"
|
||||
android:textColor="@color/btn_orange_text_color"
|
||||
android:textSize="18sp"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="@color/btn_orange_background"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- only for invisible private accounts -->
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnBlock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/block"
|
||||
android:textColor="@color/btn_red_text_color"
|
||||
android:textSize="18sp"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="@color/btn_red_background"
|
||||
android:visibility="gone" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/myActions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/profileActions">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnTagged"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/tagged"
|
||||
android:textColor="@color/btn_blue_text_color"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="@color/btn_blue_background"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- also used as block -->
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnSaved"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginStart="1dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/saved"
|
||||
android:textColor="@color/btn_orange_text_color"
|
||||
android:textSize="16sp"
|
||||
app:backgroundTint="@color/btn_orange_background"
|
||||
android:visibility="gone" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/highlightsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/profileActions"
|
||||
android:layout_below="@id/myActions"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="5dp"
|
||||
|
30
app/src/main/res/layout/activity_notification.xml
Executable file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.NotificationsViewer">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/layout_include_toolbar" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvComments"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingRight="8dp"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/item_notification" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</LinearLayout>
|
31
app/src/main/res/layout/activity_saved.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.SavedViewer">
|
||||
|
||||
<include
|
||||
android:id="@+id/toolbar"
|
||||
layout="@layout/layout_include_toolbar" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:tag="@android:string/yes">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mainPosts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingRight="8dp"
|
||||
tools:listitem="@layout/item_post" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</LinearLayout>
|
@ -38,15 +38,42 @@
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/postActions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.3"
|
||||
android:background="#0000"
|
||||
android:weightSum="3"
|
||||
android:layout_gravity="bottom">
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/viewStoryPost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/view_story_post"
|
||||
android:textColor="@color/btn_green_text_color"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/btn_green_background" />
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/interactStory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/view_story_post"
|
||||
android:textColor="@color/btn_blue_text_color"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/btn_blue_background" />
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/spotify"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/spotify"
|
||||
android:textColor="@color/btn_green_text_color"
|
||||
android:visibility="gone"
|
||||
app:backgroundTint="@color/btn_green_background" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/TextAppearance.AppCompat.Headline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:paddingStart="?attr/dialogPreferredPadding"
|
||||
android:paddingLeft="?attr/dialogPreferredPadding"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
android:paddingRight="?attr/dialogPreferredPadding"
|
||||
android:paddingBottom="6dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/action_settings" />
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:padding="5dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="5dp"
|
||||
android:text="@string/profile_endpoint"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/spProfileFetchMode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/profile_fetch_modes"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
20
app/src/main/res/layout/item_notification.xml
Executable file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<include
|
||||
android:id="@+id/container"
|
||||
layout="@layout/layout_include_notif_item" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="#80888888" />
|
||||
</LinearLayout>
|
@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<awais.instagrabber.customviews.RamboTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:paddingRight="10dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textSize="16sp" />
|
||||
</ScrollView>
|
154
app/src/main/res/layout/layout_include_notif_item.xml
Executable file
@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingLeft="0dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="4dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/simple_item_picture_size"
|
||||
android:layout_height="@dimen/simple_item_picture_size"
|
||||
android:gravity="center">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ivProfilePic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="@dimen/simple_item_picture_size_half" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="@dimen/simple_item_picture_size_half" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tvUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:layout_weight="1.0"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<awais.instagrabber.customviews.RamboTextView
|
||||
android:id="@+id/tvComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:ellipsize="end"
|
||||
android:linksClickable="true"
|
||||
android:textStyle="italic"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat" />
|
||||
|
||||
<awais.instagrabber.customviews.RamboTextView
|
||||
android:id="@+id/tvSubComment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:autoLink="web|email"
|
||||
android:ellipsize="end"
|
||||
android:linksClickable="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tvDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:ellipsize="marquee"
|
||||
android:gravity="right"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:singleLine="true"
|
||||
android:textStyle="italic" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/simple_item_picture_size"
|
||||
android:layout_height="@dimen/simple_item_picture_size"
|
||||
android:gravity="center">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ivPreviewPic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rightContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="@dimen/simple_item_picture_size_half" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="@dimen/simple_item_picture_size_half"
|
||||
android:layout_height="@dimen/simple_item_picture_size_half" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
@ -6,14 +6,4 @@
|
||||
android:layout_height="?actionBarSize"
|
||||
android:background="@null"
|
||||
app:title="@string/app_name">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_circular"
|
||||
android:layout_width="?actionBarSize"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateBehavior="cycle"
|
||||
android:indeterminateOnly="true"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
@ -12,6 +12,14 @@
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_notif"
|
||||
android:icon="@drawable/ic_notif"
|
||||
android:title="@string/action_notif"
|
||||
android:titleCondensed="@string/action_notif"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:icon="@android:drawable/ic_menu_info_details"
|
||||
|
9
app/src/main/res/menu/saved.xml
Executable file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/downloadAction"
|
||||
android:icon="@drawable/ic_download"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
</menu>
|
@ -1,12 +1,13 @@
|
||||
InstaGrabber is an app that allows...
|
||||
|
||||
* Viewing **and downloading** Instagram posts, stories (including highlights)\*, DM\*, and profile pictures, **without** letting people know you viewed it! Works for followed private accounts\*!
|
||||
* Viewing **and downloading** Instagram posts (users: timeline & tagged; account feed/saved\*; explore\*), stories (including highlights)\*, DM\*, and profile pictures (HD), **without** letting people know you viewed it! Works for followed private accounts\*!
|
||||
* Like/bookmark posts\*!
|
||||
* Downloading multiple posts at once (hold & select)!
|
||||
* (Un)follow/restrict/block people\*, and (un)follow hashtags\*! (Or you can add shortcuts to them, without logging in!)
|
||||
* **Copy** post captions, comments, DM messages\*, and profile bios.
|
||||
* **Post, Reply, Like, & Delete** comments!\*
|
||||
* **Compare** follower/following list!
|
||||
* Searching usernames and hashtags.
|
||||
* Searching usernames, hashtags, and locations!
|
||||
|
||||
<sub>* Requires [login](https://github.com/austinhuang0131/instagrabber/blob/master/README.md#how-to-log-in). You must be a current follower of the desired private accounts, this app cannot hack people (which I have to state despite the obvious)!</sub>
|
||||
|
||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 81 KiB |
BIN
fastlane/metadata/android/images/phoneScreenshots/5.jpg
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
fastlane/metadata/android/images/phoneScreenshots/6.jpg
Normal file
After Width: | Height: | Size: 157 KiB |