1
0
Fork 0
mirror of https://github.com/KokaKiwi/BarInsta synced 2026-03-04 03:21:36 +00:00
This commit is contained in:
Austin Huang 2020-07-01 13:08:28 -04:00
commit 13beabf741
No known key found for this signature in database
GPG key ID: 84C23AA04587A91F
275 changed files with 22638 additions and 0 deletions

1
app/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
/build

50
app/build.gradle Executable file
View file

@ -0,0 +1,50 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
defaultConfig {
applicationId 'awais.instagrabber'
minSdkVersion 16
targetSdkVersion 29
versionCode 27
versionName '16.5-a1'
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
vectorDrawables.generatedDensities = []
}
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
buildFeatures { viewBinding true }
aaptOptions { additionalParameters '--no-version-vectors' }
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.recyclerview:recyclerview:1.2.0-alpha03@aar') { transitive true }
implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true }
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation('org.jsoup:jsoup:1.13.1') { transitive true }
implementation('com.github.bumptech.glide:glide:4.11.0') { transitive true }
implementation('com.github.chrisbanes:PhotoView:v2.0.0@aar') { transitive true }
implementation('com.google.android.exoplayer:exoplayer:2.11.1@aar') { transitive true }
}

29
app/lint.xml Executable file
View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="IconColors">
<ignore path="src/main/res/drawable-night/download.png" />
</issue>
<issue id="IconLocation">
<ignore path="src/main/res/drawable/adm.png" />
<ignore path="src/main/res/drawable/check.png" />
<ignore path="src/main/res/drawable/collapse.png" />
<ignore path="src/main/res/drawable/comments.png" />
<ignore path="src/main/res/drawable/download.png" />
<ignore path="src/main/res/drawable/expand.png" />
<ignore path="src/main/res/drawable/lock.png" />
<ignore path="src/main/res/drawable/lw.png" />
<ignore path="src/main/res/drawable/ms.png" />
<ignore path="src/main/res/drawable/mute.png" />
<ignore path="src/main/res/drawable/qdb.png" />
<ignore path="src/main/res/drawable/rev.png" />
<ignore path="src/main/res/drawable/revl.png" />
<ignore path="src/main/res/drawable/settings.png" />
<ignore path="src/main/res/drawable/slider.png" />
<ignore path="src/main/res/drawable/tesv.png" />
<ignore path="src/main/res/drawable/vdz.png" />
<ignore path="src/main/res/drawable/verified.png" />
<ignore path="src/main/res/drawable/video.png" />
<ignore path="src/main/res/drawable/video_views.png" />
<ignore path="src/main/res/drawable/vol.png" />
</issue>
</lint>

BIN
app/play_icon.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

21
app/proguard-rules.pro vendored Executable file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

218
app/src/main/AndroidManifest.xml Executable file
View file

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="awais.instagrabber">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".InstaApp"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_descriptor"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<activity
android:name=".activities.Main"
android:launchMode="singleTop"
android:taskAffinity=".Main"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="ig.me" />
<data android:host="www.ig.me" />
<data android:host="instagram.com" />
<data android:host="www.instagram.com" />
<data android:pathPrefix="/" />
<data android:pathPrefix="/p" />
<data android:pathPrefix="/explore/tags" />
</intent-filter>
</activity>
<activity
android:name="awaisomereport.ErrorReporterActivity"
android:allowEmbedded="false"
android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true"
android:clearTaskOnLaunch="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:documentLaunchMode="never"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleTask"
android:lockTaskMode="never"
android:noHistory="false"
android:screenOrientation="portrait"
android:taskAffinity="awais.instagrabber.errorreport"
android:theme="@android:style/Theme.DeviceDefault.Dialog" />
<activity
android:name=".directdownload.MultiDirectDialog"
android:allowEmbedded="false"
android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true"
android:clearTaskOnLaunch="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:documentLaunchMode="never"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleTask"
android:lockTaskMode="never"
android:noHistory="false"
android:taskAffinity="awais.instagrabber.multidialog"
android:theme="@style/FlyingGayDialog" />
<activity
android:name=".directdownload.DirectDownload"
android:allowEmbedded="false"
android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true"
android:clearTaskOnLaunch="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:description="@string/direct_download_desc"
android:documentLaunchMode="never"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true"
android:label="@string/direct_download"
android:launchMode="singleTask"
android:lockTaskMode="never"
android:noHistory="false"
android:theme="@style/CompletelyTransparent">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="ig.me" />
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
</activity>
<activity
android:name=".activities.PostViewer"
android:parentActivityName=".activities.Main">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.CommentsViewer"
android:parentActivityName=".activities.PostViewer">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.PostViewer" />
</activity>
<activity
android:name=".activities.StoryViewer"
android:parentActivityName=".activities.Main">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.FollowViewer"
android:parentActivityName=".activities.Main">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.ProfileViewer"
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"
android:parentActivityName=".activities.Main">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.DirectMessages"
android:parentActivityName=".activities.Main">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.DirectMessagesUserInbox"
android:parentActivityName=".activities.DirectMessages">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.DirectMessages" />
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -0,0 +1,66 @@
package awais.instagrabber;
import android.content.ClipboardManager;
import android.content.Context;
import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication;
import java.net.CookieHandler;
import java.text.SimpleDateFormat;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.SettingsHelper;
import awaisomereport.CrashReporter;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.NET_COOKIE_MANAGER;
import static awais.instagrabber.utils.Utils.changeTheme;
import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.dataBox;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.getInstalledTelegramPackage;
import static awais.instagrabber.utils.Utils.isInstaInstalled;
import static awais.instagrabber.utils.Utils.isInstagramInstalled;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.notificationManager;
import static awais.instagrabber.utils.Utils.settingsHelper;
import static awais.instagrabber.utils.Utils.telegramPackage;
public final class InstaApp extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
if (!BuildConfig.DEBUG) CrashReporter.get(this).start();
logCollector = new LogCollector(this);
CookieHandler.setDefault(NET_COOKIE_MANAGER);
final Context appContext = getApplicationContext();
isInstagramInstalled = isInstaInstalled(appContext);
telegramPackage = getInstalledTelegramPackage(appContext);
if (dataBox == null)
dataBox = DataBox.getInstance(appContext);
if (settingsHelper == null)
settingsHelper = new SettingsHelper(this);
LocaleUtils.setLocale(getBaseContext());
if (notificationManager == null)
notificationManager = NotificationManagerCompat.from(appContext);
if (clipboardManager == null)
clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (datetimeParser == null)
datetimeParser = new SimpleDateFormat(settingsHelper.getString(Constants.DATE_TIME_FORMAT), LocaleUtils.getCurrentLocale());
changeTheme();
}
}

View file

@ -0,0 +1,864 @@
package awais.instagrabber;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.widget.ImageViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.shape.MaterialShapeDrawable;
import java.util.Arrays;
import awais.instagrabber.activities.FollowViewer;
import awais.instagrabber.activities.Main;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.asyncs.FeedStoriesFetcher;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.StoryStatusFetcher;
import awais.instagrabber.customviews.MouseDrawer;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.IntentModelType;
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.Constants.BOTTOM_TOOLBAR;
import static awais.instagrabber.utils.Utils.logCollector;
public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private static AsyncTask<?, ?, ?> currentlyExecuting;
private AsyncTask<Void, Void, FeedStoryModel[]> prevStoriesFetcher;
private final boolean autoloadPosts;
private boolean hasNextPage = false, feedHasNextPage = false, discoverHasMore = false;
private String endCursor = null, feedEndCursor = null, discoverEndMaxId = null;
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
@Override
public void onResult(final PostModel[] result) {
if (result != null) {
final int oldSize = main.allItems.size();
main.allItems.addAll(Arrays.asList(result));
postsAdapter.notifyItemRangeInserted(oldSize, result.length);
main.mainBinding.mainPosts.post(() -> {
main.mainBinding.mainPosts.setNestedScrollingEnabled(true);
main.mainBinding.mainPosts.setVisibility(View.VISIBLE);
});
final String username;
final String postFix;
if (!isHashtag) {
username = main.profileModel.getUsername();
postFix = "/" + main.profileModel.getPostCount() + ')';
} else {
username = null;
postFix = null;
}
if (isHashtag)
main.mainBinding.toolbar.toolbar.setTitle(main.getString(R.string.title_hashtag_prefix) + main.userQuery);
else main.mainBinding.toolbar.toolbar.setTitle(username + " (" + main.allItems.size() + postFix);
final PostModel model = result[result.length - 1];
if (model != null) {
endCursor = model.getEndCursor();
if (endCursor == null && !isHashtag) {
main.mainBinding.toolbar.toolbar.setTitle(username + " (" + main.profileModel.getPostCount() + postFix);
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
main.mainBinding.toolbar.toolbar.setTitle(username);
handler.removeCallbacks(this);
}
}, 1000);
}
hasNextPage = model.hasNextPage();
if ((autoloadPosts && hasNextPage) && !isHashtag)
currentlyExecuting = new PostsFetcher(main.profileModel.getId(), endCursor, this)
.setUsername(main.profileModel.getUsername()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
main.mainBinding.swipeRefreshLayout.setRefreshing(false);
model.setPageCursor(false, null);
}
}
}
};
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
@Override
public void doBefore() {
main.mainBinding.feedSwipeRefreshLayout.post(() -> main.mainBinding.feedSwipeRefreshLayout.setRefreshing(true));
}
@Override
public void onResult(final FeedModel[] result) {
if (result != null) {
final int oldSize = main.feedItems.size();
main.feedItems.addAll(Arrays.asList(result));
feedAdapter.notifyItemRangeInserted(oldSize, result.length);
main.mainBinding.feedPosts.post(() -> main.mainBinding.feedPosts.setNestedScrollingEnabled(true));
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
}
main.mainBinding.feedSwipeRefreshLayout.setRefreshing(false);
}
};
private final FetchListener<DiscoverItemModel[]> discoverFetchListener = new FetchListener<DiscoverItemModel[]>() {
@Override
public void doBefore() {
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final DiscoverItemModel[] result) {
if (result != null) {
final int oldSize = main.discoverItems.size();
main.discoverItems.addAll(Arrays.asList(result));
discoverAdapter.notifyItemRangeInserted(oldSize, result.length);
final DiscoverItemModel discoverItemModel = result[result.length - 1];
if (discoverItemModel != null) {
discoverEndMaxId = discoverItemModel.getNextMaxId();
discoverHasMore = discoverItemModel.hasMore();
discoverItemModel.setMore(false, null);
}
}
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(false);
}
};
private final FetchListener<FeedStoryModel[]> feedStoriesListener = new FetchListener<FeedStoryModel[]>() {
@Override
public void doBefore() {
main.mainBinding.feedStories.setVisibility(View.GONE);
}
@Override
public void onResult(final FeedStoryModel[] result) {
feedStoriesAdapter.setData(result);
if (result != null && result.length > 0)
main.mainBinding.feedStories.setVisibility(View.VISIBLE);
}
};
private final MentionClickListener mentionClickListener = new MentionClickListener() {
@Override
public void onClick(final RamboTextView view, final String text, final boolean isHashtag) {
new AlertDialog.Builder(main).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (Main.scanHack != null) Main.scanHack.onResult(text);
}).show();
}
};
private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof FeedStoryModel) {
final FeedStoryModel feedStoryModel = (FeedStoryModel) tag;
final StoryModel[] storyModels = feedStoryModel.getStoryModels();
main.startActivity(new Intent(main, StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
);
}
}
});
@NonNull
private final Main main;
private final Resources resources;
private final View collapsingToolbar;
private final RecyclerLazyLoader lazyLoader;
private boolean isHashtag;
private PostsAdapter postsAdapter;
private FeedAdapter feedAdapter;
private RecyclerLazyLoader feedLazyLoader, discoverLazyLoader;
private DiscoverAdapter discoverAdapter;
public SimpleExoPlayer currentFeedPlayer; // hack for remix drawer layout
public MainHelper(@NonNull final Main main) {
stopCurrentExecutor();
this.main = main;
this.resources = main.getResources();
this.autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS);
main.mainBinding.swipeRefreshLayout.setOnRefreshListener(this);
main.mainBinding.mainUrl.setMovementMethod(new LinkMovementMethod());
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE));
final LinearLayout iconSlider = main.findViewById(R.id.iconSlider);
final ImageView iconFeed = (ImageView) iconSlider.getChildAt(0);
final ImageView iconProfile = (ImageView) iconSlider.getChildAt(1);
final ImageView iconDiscover = (ImageView) iconSlider.getChildAt(2);
final boolean isBottomToolbar = Utils.settingsHelper.getBoolean(BOTTOM_TOOLBAR);
if (!isLoggedIn) {
main.mainBinding.drawerLayout.removeView(main.mainBinding.feedLayout);
main.mainBinding.drawerLayout.removeView(main.mainBinding.discoverSwipeRefreshLayout);
iconFeed.setAlpha(0.4f);
iconDiscover.setAlpha(0.4f);
} else {
iconFeed.setAlpha(1f);
iconDiscover.setAlpha(1f);
setupExplore();
final boolean showFeed = Utils.settingsHelper.getBoolean(Constants.SHOW_FEED);
if (showFeed) setupFeed();
else {
iconFeed.setAlpha(0.4f);
main.mainBinding.drawerLayout.removeView(main.mainBinding.feedLayout);
}
final TypedValue resolvedAttr = new TypedValue();
main.getTheme().resolveAttribute(android.R.attr.textColorPrimary, resolvedAttr, true);
final int selectedItem = ContextCompat.getColor(main, resolvedAttr.resourceId != 0 ? resolvedAttr.resourceId : resolvedAttr.data);
final ColorStateList colorStateList = ColorStateList.valueOf(selectedItem);
main.mainBinding.toolbar.toolbar.measure(0, -1);
final int toolbarMeasuredHeight = main.mainBinding.toolbar.toolbar.getMeasuredHeight();
final ViewGroup.LayoutParams layoutParams = main.mainBinding.toolbar.toolbar.getLayoutParams();
final MouseDrawer.DrawerListener simpleDrawerListener = new MouseDrawer.DrawerListener() {
private final String titleDiscover = resources.getString(R.string.title_discover);
@Override
public void onDrawerSlide(final View drawerView, @MouseDrawer.EdgeGravity final int gravity, final float slideOffset) {
final int currentIconAlpha = (int) Math.max(100, 255 - 255 * slideOffset);
final int otherIconAlpha = (int) Math.max(100, 255 * slideOffset);
ImageViewCompat.setImageTintList(iconProfile, colorStateList.withAlpha(currentIconAlpha));
final boolean drawerOpening = slideOffset > 0.0f;
final int alpha;
final ColorStateList imageTintList;
if (gravity == GravityCompat.START) {
// this helps hide the toolbar when opening feed
final int roundedToolbarHeight;
final float toolbarHeight;
if (isBottomToolbar) {
toolbarHeight = toolbarMeasuredHeight * slideOffset;
roundedToolbarHeight = -Math.round(toolbarHeight);
} else {
toolbarHeight = -toolbarMeasuredHeight * slideOffset;
roundedToolbarHeight = Math.round(toolbarHeight);
}
layoutParams.height = Math.max(0, Math.min(toolbarMeasuredHeight, toolbarMeasuredHeight + roundedToolbarHeight));
main.mainBinding.toolbar.toolbar.setLayoutParams(layoutParams);
main.mainBinding.toolbar.toolbar.setTranslationY(toolbarHeight);
imageTintList = ImageViewCompat.getImageTintList(iconDiscover);
alpha = imageTintList != null ? (imageTintList.getDefaultColor() & 0xFF_000000) >> 24 : 0;
if (drawerOpening && alpha > 100)
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(currentIconAlpha));
if (showFeed) ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(otherIconAlpha));
} else {
// this changes toolbar title
main.mainBinding.toolbar.toolbar.setTitle(slideOffset >= 0.466 ? titleDiscover : main.userQuery);
if (showFeed) {
imageTintList = ImageViewCompat.getImageTintList(iconFeed);
alpha = imageTintList != null ? (imageTintList.getDefaultColor() & 0xFF_000000) >> 24 : 0;
if (drawerOpening && alpha > 100)
ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(currentIconAlpha));
}
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(otherIconAlpha));
}
}
@Override
public void onDrawerOpened(@NonNull final View drawerView, @MouseDrawer.EdgeGravity final int gravity) {
if (gravity == GravityCompat.START || drawerView == main.mainBinding.feedLayout) {
if (currentFeedPlayer != null) {
currentFeedPlayer.setPlayWhenReady(true);
currentFeedPlayer.getPlaybackState();
}
} else {
// clear selection
isSelectionCleared();
}
}
@Override
public void onDrawerClosed(@NonNull final View drawerView, @MouseDrawer.EdgeGravity final int gravity) {
if (gravity == GravityCompat.START || drawerView == main.mainBinding.feedLayout) {
if (currentFeedPlayer != null) {
currentFeedPlayer.setPlayWhenReady(false);
currentFeedPlayer.getPlaybackState();
}
} else {
// clear selection
isSelectionCleared();
}
}
};
ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(100)); // to change colors when created
ImageViewCompat.setImageTintList(iconProfile, colorStateList.withAlpha(255)); // to change colors when created
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(100)); // to change colors when created
main.mainBinding.drawerLayout.addDrawerListener(simpleDrawerListener);
}
collapsingToolbar = main.mainBinding.appBarLayout.getChildAt(0);
main.mainBinding.mainPosts.setNestedScrollingEnabled(false);
main.mainBinding.highlightsList.setLayoutManager(new LinearLayoutManager(main, LinearLayoutManager.HORIZONTAL, false));
main.mainBinding.highlightsList.setAdapter(main.highlightsAdapter);
int color = -1;
final Drawable background = main.mainBinding.appBarLayout.getBackground();
if (background instanceof MaterialShapeDrawable) {
final MaterialShapeDrawable drawable = (MaterialShapeDrawable) background;
final ColorStateList fillColor = drawable.getFillColor();
if (fillColor != null) color = fillColor.getDefaultColor();
} else {
final Bitmap bitmap = Bitmap.createBitmap(9, 9, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);
background.draw(canvas);
color = bitmap.getPixel(4, 4);
if (!bitmap.isRecycled()) bitmap.recycle();
}
if (color == -1 || color == 0) color = resources.getBoolean(R.bool.isNight) ? 0xff212121 : 0xfff5f5f5;
main.mainBinding.profileInfo.setBackgroundColor(color);
main.mainBinding.profileInfo.setClickable(true);
if (!isBottomToolbar) main.mainBinding.toolbar.toolbar.setBackgroundColor(color);
main.mainBinding.appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
private int height;
@Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
if (height == 0) {
height = main.mainBinding.profileInfo.getHeight();
collapsingToolbar.setMinimumHeight(height);
}
main.mainBinding.profileInfo.setTranslationY(-Math.min(0, verticalOffset));
}
});
main.setSupportActionBar(main.mainBinding.toolbar.toolbar);
if (isBottomToolbar) {
final LinearLayout linearLayout = (LinearLayout) main.mainBinding.toolbar.toolbar.getParent();
linearLayout.removeView(main.mainBinding.toolbar.toolbar);
linearLayout.addView(main.mainBinding.toolbar.toolbar, linearLayout.getChildCount());
}
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130));
main.mainBinding.mainPosts.setLayoutManager(layoutManager);
main.mainBinding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
main.mainBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(main.allItems, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else main.startActivity(new Intent(main, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition())
.putExtra(Constants.EXTRAS_POST, postModel)
.putExtra(Constants.EXTRAS_USER, main.userQuery)
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS));
}
}, v -> { // long click listener
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
return true;
}));
this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if ((!autoloadPosts || isHashtag) && hasNextPage) {
main.mainBinding.swipeRefreshLayout.setRefreshing(true);
stopCurrentExecutor();
currentlyExecuting = new PostsFetcher(isHashtag ? main.userQuery : main.profileModel.getId(), endCursor, postsFetchListener)
.setUsername(isHashtag ? null : main.profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
}
});
main.mainBinding.mainPosts.addOnScrollListener(lazyLoader);
}
private void setupFeed() {
main.mainBinding.feedStories.setLayoutManager(new LinearLayoutManager(main, LinearLayoutManager.HORIZONTAL, false));
main.mainBinding.feedStories.setAdapter(feedStoriesAdapter);
refreshFeedStories();
final LinearLayoutManager layoutManager = new LinearLayoutManager(main);
main.mainBinding.feedPosts.setLayoutManager(layoutManager);
main.mainBinding.feedPosts.setAdapter(feedAdapter = new FeedAdapter(main, main.feedItems, (view, text, isHashtag) ->
new AlertDialog.Builder(main).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (Main.scanHack != null) {
main.mainBinding.drawerLayout.closeDrawers();
Main.scanHack.onResult(text);
}
}).show()));
main.mainBinding.feedSwipeRefreshLayout.setOnRefreshListener(() -> {
refreshFeedStories();
if (feedLazyLoader != null) feedLazyLoader.resetState();
main.feedItems.clear();
if (feedAdapter != null) feedAdapter.notifyDataSetChanged();
new FeedFetcher(feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
main.mainBinding.feedPosts.addOnScrollListener(feedLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (feedHasNextPage) {
main.mainBinding.feedSwipeRefreshLayout.setRefreshing(true);
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
feedEndCursor = null;
}
}));
main.mainBinding.feedPosts.addOnScrollListener(new VideoAwareRecyclerScroller(main, main.feedItems,
(itemPos, player) -> currentFeedPlayer = player));
new FeedFetcher(feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void refreshFeedStories() {
// todo setup feed stories
if (prevStoriesFetcher != null) {
try {
prevStoriesFetcher.cancel(true);
} catch (final Exception e) {
// ignore
}
}
prevStoriesFetcher = new FeedStoriesFetcher(feedStoriesListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setupExplore() {
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130));
main.mainBinding.discoverPosts.setLayoutManager(layoutManager);
main.mainBinding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
main.mainBinding.discoverSwipeRefreshLayout.setOnRefreshListener(() -> {
if (discoverLazyLoader != null) discoverLazyLoader.resetState();
main.discoverItems.clear();
if (discoverAdapter != null) discoverAdapter.notifyDataSetChanged();
new DiscoverFetcher(null, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
main.mainBinding.discoverPosts.setAdapter(discoverAdapter = new DiscoverAdapter(main.discoverItems, v -> {
final Object tag = v.getTag();
if (tag instanceof DiscoverItemModel) {
final DiscoverItemModel itemModel = (DiscoverItemModel) tag;
if (discoverAdapter.isSelecting) toggleDiscoverSelection(itemModel);
else main.startActivity(new Intent(main, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, itemModel.getPosition())
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.DISCOVER_ITEMS)
.putExtra(Constants.EXTRAS_POST, new PostModel(itemModel.getShortCode())));
}
}, v -> {
final Object tag = v.getTag();
if (tag instanceof DiscoverItemModel) {
discoverAdapter.isSelecting = true;
toggleDiscoverSelection((DiscoverItemModel) tag);
}
return true;
}));
main.mainBinding.discoverPosts.addOnScrollListener(discoverLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (discoverHasMore) {
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(true);
new DiscoverFetcher(discoverEndMaxId, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
discoverEndMaxId = null;
}
}));
new DiscoverFetcher(null, discoverFetchListener, true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public void onIntent(final Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (!Utils.isEmpty(action) && !Intent.ACTION_MAIN.equals(action)) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
boolean error = true;
String data = null;
final Bundle extras = intent.getExtras();
if (extras != null) {
final Object extraData = extras.get(Intent.EXTRA_TEXT);
if (extraData != null) {
error = false;
data = extraData.toString();
}
}
if (error) {
final Uri intentData = intent.getData();
if (intentData != null) data = intentData.toString();
}
if (data != null && !Utils.isEmpty(data)) {
if (data.indexOf('\n') > 0) data = data.substring(data.lastIndexOf('\n') + 1);
final IntentModel model = Utils.stripString(data);
if (model != null) {
final String modelText = model.getText();
final IntentModelType modelType = model.getType();
if (modelType == IntentModelType.POST) {
main.startActivityForResult(new Intent(main, PostViewer.class)
.putExtra(Constants.EXTRAS_USER, main.userQuery)
.putExtra(Constants.EXTRAS_POST, new PostModel(modelText)), 9629);
} else {
main.addToStack();
main.userQuery = modelType == IntentModelType.HASHTAG ? '#' + modelText : modelText;
onRefresh();
}
}
}
}
}
}
@Override
public void onRefresh() {
main.mainBinding.drawerLayout.closeDrawers();
if (lazyLoader != null) lazyLoader.resetState();
stopCurrentExecutor();
main.allItems.clear();
main.selectedItems.clear();
if (postsAdapter != null) {
postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
}
main.mainBinding.appBarLayout.setExpanded(true, true);
main.mainBinding.privatePage.setVisibility(View.GONE);
main.mainBinding.mainProfileImage.setImageBitmap(null);
main.mainBinding.mainProfileImage.setImageDrawable(null);
main.mainBinding.mainUrl.setText(null);
main.mainBinding.mainFullName.setText(null);
main.mainBinding.mainPostCount.setText(null);
main.mainBinding.mainFollowers.setText(null);
main.mainBinding.mainFollowing.setText(null);
main.mainBinding.mainBiography.setText(null);
main.mainBinding.mainBiography.setEnabled(false);
main.mainBinding.mainProfileImage.setEnabled(false);
main.mainBinding.mainBiography.setMentionClickListener(null);
main.mainBinding.mainUrl.setVisibility(View.GONE);
main.mainBinding.isVerified.setVisibility(View.GONE);
main.mainBinding.mainPosts.setNestedScrollingEnabled(false);
main.mainBinding.highlightsList.setVisibility(View.GONE);
collapsingToolbar.setVisibility(View.GONE);
main.highlightsAdapter.setData(null);
main.mainBinding.swipeRefreshLayout.setRefreshing(main.userQuery != null);
if (main.userQuery == null) {
main.mainBinding.toolbar.toolbar.setTitle(R.string.app_name);
return;
}
isHashtag = main.userQuery.charAt(0) == '#';
collapsingToolbar.setVisibility(isHashtag ? View.GONE : View.VISIBLE);
if (isHashtag) {
main.mainBinding.toolbar.toolbar.setTitle(resources.getString(R.string.title_hashtag_prefix) + main.userQuery);
main.mainBinding.infoContainer.setVisibility(View.GONE);
currentlyExecuting = new PostsFetcher(main.userQuery, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
main.mainBinding.toolbar.toolbar.setTitle(main.userQuery);
main.mainBinding.infoContainer.setVisibility(View.VISIBLE);
currentlyExecuting = new ProfileFetcher(main.userQuery, profileModel -> {
main.profileModel = profileModel;
if (profileModel == null) {
main.mainBinding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(main, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
main.mainBinding.toolbar.toolbar.setTitle(R.string.app_name);
return;
}
main.mainBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId();
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE));
if (isLoggedIn) {
new StoryStatusFetcher(profileId, result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, result -> {
if (result != null && result.length > 0) {
main.mainBinding.highlightsList.setVisibility(View.VISIBLE);
main.highlightsAdapter.setData(result);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
main.mainBinding.mainProfileImage.setEnabled(false);
Glide.with(main).load(profileModel.getSdProfilePic()).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
main.mainBinding.mainProfileImage.setEnabled(false);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
main.mainBinding.mainProfileImage.setEnabled(true);
return false;
}
}).into(main.mainBinding.mainProfileImage);
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
final String postCount = String.valueOf(profileModel.getPostCount());
SpannableStringBuilder span = new SpannableStringBuilder(resources.getString(R.string.main_posts_count, postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
main.mainBinding.mainPostCount.setText(span);
final String followersCountStr = String.valueOf(followersCount);
final int followersCountStrLen = followersCountStr.length();
span = new SpannableStringBuilder(resources.getString(R.string.main_posts_followers, followersCountStr));
span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0);
main.mainBinding.mainFollowers.setText(span);
final String followingCountStr = String.valueOf(followingCount);
final int followingCountStrLen = followingCountStr.length();
span = new SpannableStringBuilder(resources.getString(R.string.main_posts_following, followingCountStr));
span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0);
main.mainBinding.mainFollowing.setText(span);
main.mainBinding.mainFullName.setText(profileModel.getName());
CharSequence biography = profileModel.getBiography();
main.mainBinding.mainBiography.setCaptionIsExpandable(true);
main.mainBinding.mainBiography.setCaptionIsExpanded(true);
if (Utils.hasMentions(biography)) {
biography = Utils.getMentionText(biography);
main.mainBinding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
main.mainBinding.mainBiography.setMentionClickListener(mentionClickListener);
} else {
main.mainBinding.mainBiography.setText(biography);
main.mainBinding.mainBiography.setMentionClickListener(null);
}
final String url = profileModel.getUrl();
if (Utils.isEmpty(url)) {
main.mainBinding.mainUrl.setVisibility(View.GONE);
} else {
main.mainBinding.mainUrl.setVisibility(View.VISIBLE);
main.mainBinding.mainUrl.setText(Utils.getSpannableUrl(url));
}
main.mainBinding.mainFullName.setSelected(true);
main.mainBinding.mainBiography.setEnabled(true);
if (!profileModel.isPrivate()) {
main.mainBinding.swipeRefreshLayout.setRefreshing(true);
main.mainBinding.mainPosts.setVisibility(View.VISIBLE);
main.mainBinding.privatePage.setVisibility(View.GONE);
if (isLoggedIn) {
final View.OnClickListener followClickListener = v -> main.startActivity(new Intent(main, FollowViewer.class)
.putExtra(Constants.EXTRAS_FOLLOWERS, v == main.mainBinding.mainFollowers)
.putExtra(Constants.EXTRAS_NAME, profileModel.getUsername())
.putExtra(Constants.EXTRAS_ID, profileId));
main.mainBinding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null);
main.mainBinding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null);
}
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener).setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
main.mainBinding.swipeRefreshLayout.setRefreshing(false);
main.mainBinding.privatePage.setVisibility(View.VISIBLE);
main.mainBinding.mainPosts.setVisibility(View.GONE);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
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()) main.selectedItems.remove(postModel);
else main.selectedItems.add(postModel);
postModel.setSelected(!postModel.isSelected());
notifyAdapter(postModel);
}
}
private void notifyAdapter(final PostModel postModel) {
if (main.selectedItems.size() < 1) postsAdapter.isSelecting = false;
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (main.downloadAction != null) main.downloadAction.setVisible(postsAdapter.isSelecting);
}
///////////////////////////////////////////////////
private void toggleDiscoverSelection(final DiscoverItemModel itemModel) {
if (itemModel != null && discoverAdapter != null) {
if (itemModel.isSelected()) main.selectedDiscoverItems.remove(itemModel);
else main.selectedDiscoverItems.add(itemModel);
itemModel.setSelected(!itemModel.isSelected());
notifyDiscoverAdapter(itemModel);
}
}
private void notifyDiscoverAdapter(final DiscoverItemModel itemModel) {
if (main.selectedDiscoverItems.size() < 1) discoverAdapter.isSelecting = false;
if (itemModel.getPosition() < 0) discoverAdapter.notifyDataSetChanged();
else discoverAdapter.notifyItemChanged(itemModel.getPosition(), itemModel);
if (main.downloadAction != null) main.downloadAction.setVisible(discoverAdapter.isSelecting);
}
public boolean isSelectionCleared() {
if (postsAdapter != null && postsAdapter.isSelecting) {
for (final PostModel postModel : main.selectedItems) postModel.setSelected(false);
main.selectedItems.clear();
postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
if (main.downloadAction != null) main.downloadAction.setVisible(false);
return false;
} else if (discoverAdapter != null && discoverAdapter.isSelecting) {
for (final DiscoverItemModel itemModel : main.selectedDiscoverItems) itemModel.setSelected(false);
main.selectedDiscoverItems.clear();
discoverAdapter.isSelecting = false;
discoverAdapter.notifyDataSetChanged();
if (main.downloadAction != null) main.downloadAction.setVisible(false);
return false;
}
return true;
}
public void deselectSelection(final BasePostModel postModel) {
if (postModel instanceof PostModel) {
main.selectedItems.remove(postModel);
postModel.setSelected(false);
if (postsAdapter != null) notifyAdapter((PostModel) postModel);
} else if (postModel instanceof DiscoverItemModel) {
main.selectedDiscoverItems.remove(postModel);
postModel.setSelected(false);
if (discoverAdapter != null) notifyDiscoverAdapter((DiscoverItemModel) postModel);
}
}
public void onPause() {
if (currentFeedPlayer != null) {
currentFeedPlayer.setPlayWhenReady(false);
currentFeedPlayer.getPlaybackState();
}
}
public void onResume() {
if (currentFeedPlayer != null) {
currentFeedPlayer.setPlayWhenReady(true);
currentFeedPlayer.getPlaybackState();
}
}
}

View file

@ -0,0 +1,11 @@
package awais.instagrabber.activities;
import androidx.appcompat.app.AppCompatActivity;
import awais.instagrabber.utils.LocaleUtils;
public abstract class BaseLanguageActivity extends AppCompatActivity {
protected BaseLanguageActivity() {
LocaleUtils.updateConfig(this);
}
}

View file

@ -0,0 +1,146 @@
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.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.asyncs.CommentsFetcher;
import awais.instagrabber.databinding.ActivityCommentsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class CommentsViewer extends AppCompatActivity {
private CommentsAdapter commentsAdapter;
private CommentModel commentModel;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityCommentsBinding commentsBinding = ActivityCommentsBinding.inflate(getLayoutInflater());
setContentView(commentsBinding.getRoot());
final String shortCode;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_SHORTCODE)
|| Utils.isEmpty((shortCode = intent.getStringExtra(Constants.EXTRAS_SHORTCODE)))) {
Utils.errorFinish(this);
return;
}
setSupportActionBar(commentsBinding.toolbar.toolbar);
commentsBinding.toolbar.toolbar.setTitle(R.string.title_comments);
commentsBinding.toolbar.toolbar.setSubtitle(shortCode);
final Resources resources = getResources();
final ArrayAdapter<String> commmentDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
new String[]{resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment)});
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
final ProfileModel profileModel = commentModel.getProfileModel();
if (which == 0) {
searchUsername(profileModel.getUsername());
} else if (which == 1) {
startActivity(new Intent(this, ProfileViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel));
} else if (which == 2) {
Utils.copyText(this, profileModel.getUsername());
} else if (which == 3) {
Utils.copyText(this, commentModel.getText().toString());
}
};
final View.OnClickListener clickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof CommentModel) {
commentModel = (CommentModel) tag;
final String username = commentModel.getProfileModel().getUsername();
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText());
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
new AlertDialog.Builder(this).setTitle(title)
.setAdapter(commmentDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null)
.show();
}
};
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();
new CommentsFetcher(shortCode, new FetchListener<CommentModel[]>() {
@Override
public void doBefore() {
commentsBinding.toolbar.progressCircular.setVisibility(View.VISIBLE);
}
@Override
public void onResult(final CommentModel[] commentModels) {
commentsBinding.toolbar.progressCircular.setVisibility(View.GONE);
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
commentsBinding.rvComments.setAdapter(commentsAdapter);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void searchUsername(final String text) {
if (Main.scanHack != null) {
Main.scanHack.onResult(text);
setResult(6969);
finish();
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.follow, menu);
final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(final String query) {
return false;
}
@Override
public boolean onQueryTextChange(final String query) {
if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
return true;
}
});
menu.findItem(R.id.action_compare).setVisible(false);
return true;
}
}

View file

@ -0,0 +1,113 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Arrays;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessagesAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class DirectMessages extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
private final ArrayList<InboxThreadModel> inboxThreadModelList = new ArrayList<>();
private final DirectMessagesAdapter messagesAdapter = new DirectMessagesAdapter(inboxThreadModelList, v -> {
final Object tag = v.getTag();
if (tag instanceof InboxThreadModel) {
startActivity(new Intent(this, DirectMessagesUserInbox.class)
.putExtra(Constants.EXTRAS_THREAD_MODEL, (InboxThreadModel) tag)
);
}
});
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxModel inboxModel) {
if (inboxModel != null) {
endCursor = inboxModel.getOldestCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
// todo get request / unseen count from inboxModel
final InboxThreadModel[] threads = inboxModel.getThreads();
if (threads != null) {
final int oldSize = inboxThreadModelList.size();
inboxThreadModelList.addAll(Arrays.asList(threads));
messagesAdapter.notifyItemRangeInserted(oldSize, threads.length);
}
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor();
}
};
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private AsyncTask<Void, Void, InboxModel> currentlyRunning;
private ActivityDmsBinding dmsBinding;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
dmsBinding.swipeRefreshLayout.setOnRefreshListener(this);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
dmsBinding.rvDirectMessages.setAdapter(messagesAdapter);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor))
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
dmsBinding.rvDirectMessages.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
inboxThreadModelList.clear();
messagesAdapter.notifyDataSetChanged();
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
}
}

View file

@ -0,0 +1,101 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class DirectMessagesUserInbox extends AppCompatActivity {
private final ArrayList<ProfileModel> users = new ArrayList<>();
private final ArrayList<DirectItemModel> directItemModels = new ArrayList<>();
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxThreadModel result) {
if (result == null && "MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor) || Utils.isEmpty(endCursor))
Toast.makeText(DirectMessagesUserInbox.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (result != null) {
endCursor = result.getPrevCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
users.clear();
users.addAll(Arrays.asList(result.getUsers()));
final int oldSize = directItemModels.size();
final List<DirectItemModel> itemModels = Arrays.asList(result.getItems());
directItemModels.addAll(itemModels);
messageItemsAdapter.notifyItemRangeInserted(oldSize, itemModels.size());
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
}
};
private String endCursor;
private ActivityDmsBinding dmsBinding;
private MessageItemsAdapter messageItemsAdapter;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
final InboxThreadModel threadModel;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_THREAD_MODEL) ||
(threadModel = (InboxThreadModel) intent.getSerializableExtra(Constants.EXTRAS_THREAD_MODEL)) == null) {
Utils.errorFinish(this);
return;
}
dmsBinding.swipeRefreshLayout.setEnabled(false);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, true);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor)) {
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER,
endCursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}
}));
dmsBinding.rvDirectMessages.setAdapter(messageItemsAdapter = new MessageItemsAdapter(directItemModels, users, v -> {
// todo do something with clicked message
Log.d("AWAISKING_APP", "--> " + v.getTag());
}, (view, text, isHashtag) -> {
// todo mention click stuff
}));
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

View file

@ -0,0 +1,348 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
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.ArrayAdapter;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
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.FollowAdapter;
import awais.instagrabber.asyncs.FollowFetcher;
import awais.instagrabber.databinding.ActivityFollowBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FollowModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import thoughtbot.expandableadapter.ExpandableGroup;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FollowViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
private final ArrayList<FollowModel> followModels = new ArrayList<>();
private final ArrayList<FollowModel> followingModels = new ArrayList<>();
private final ArrayList<FollowModel> followersModels = new ArrayList<>();
private final ArrayList<FollowModel> allFollowing = new ArrayList<>();
private boolean followers, isCompare = false;
private String id, name, namePost, type;
private Resources resources;
private FollowModel model;
private FollowAdapter adapter;
private View.OnClickListener clickListener;
private ActivityFollowBinding followBinding;
private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
followBinding = ActivityFollowBinding.inflate(getLayoutInflater());
setContentView(followBinding.getRoot());
final Intent intent = getIntent();
if (intent == null || Utils.isEmpty(id = intent.getStringExtra(Constants.EXTRAS_ID))) {
Utils.errorFinish(this);
return;
}
setSupportActionBar(followBinding.toolbar.toolbar);
followers = intent.getBooleanExtra(Constants.EXTRAS_FOLLOWERS, false);
name = intent.getStringExtra(Constants.EXTRAS_NAME);
namePost = name + " is";
if (Utils.isEmpty(name)) {
name = "You";
namePost = "You're";
}
followBinding.toolbar.toolbar.setTitle(name);
resources = getResources();
final ArrayAdapter<Object> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{
resources.getString(R.string.open_profile), resources.getString(R.string.followers_open_in_insta)});
final AlertDialog alertDialog = new AlertDialog.Builder(this).setAdapter(adapter, (dialog, which) -> {
if (model != null) {
if (which == 0) {
if (Main.scanHack != null) {
Main.scanHack.onResult(model.getUsername());
finish();
}
} else {
final Intent actIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://instagram.com/" + model.getUsername()));
if (Utils.isInstagramInstalled) actIntent.setPackage("com.instagram.android");
startActivity(actIntent);
}
}
}).setTitle(R.string.what_to_do_dialog).create();
clickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof FollowModel) {
model = (FollowModel) tag;
if (!alertDialog.isShowing()) alertDialog.show();
}
};
followBinding.swipeRefreshLayout.setOnRefreshListener(this);
onRefresh();
}
@Override
public void onRefresh() {
if (isCompare) listCompare();
else listFollows();
}
private void listFollows() {
stopCurrentExecutor();
type = resources.getString(followers ? R.string.followers_type_followers : R.string.followers_type_following);
followBinding.toolbar.toolbar.setSubtitle(type);
followModels.clear();
final FetchListener<FollowModel[]> fetchListener = new FetchListener<FollowModel[]>() {
@Override
public void doBefore() {
followBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final FollowModel[] result) {
if (result == null) followBinding.swipeRefreshLayout.setRefreshing(false);
else {
followModels.addAll(Arrays.asList(result));
final FollowModel model = result[result.length - 1];
if (model != null && model.hasNextPage()) {
stopCurrentExecutor();
currentlyExecuting = new FollowFetcher(id, followers, model.getEndCursor(), this)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
model.setPageCursor(false, null);
} else {
followBinding.swipeRefreshLayout.setRefreshing(false);
refreshAdapter(followModels, null, null, null);
}
}
}
};
currentlyExecuting = new FollowFetcher(id, followers, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void listCompare() {
stopCurrentExecutor();
followBinding.toolbar.toolbar.setSubtitle(R.string.followers_compare);
allFollowing.clear();
followersModels.clear();
followingModels.clear();
final FetchListener<FollowModel[]> followingFetchListener = new FetchListener<FollowModel[]>() {
@Override
public void onResult(final FollowModel[] result) {
if (result != null) {
followingModels.addAll(Arrays.asList(result));
final FollowModel model = result[result.length - 1];
if (model != null && model.hasNextPage()) {
stopCurrentExecutor();
currentlyExecuting = new FollowFetcher(id, false, model.getEndCursor(), this)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
model.setPageCursor(false, null);
} else {
allFollowing.addAll(followersModels);
allFollowing.retainAll(followingModels);
for (final FollowModel followModel : allFollowing) {
followersModels.remove(followModel);
followingModels.remove(followModel);
}
allFollowing.trimToSize();
followersModels.trimToSize();
followingModels.trimToSize();
followBinding.swipeRefreshLayout.setRefreshing(false);
refreshAdapter(null, followingModels, followersModels, allFollowing);
}
} else followBinding.swipeRefreshLayout.setRefreshing(false);
}
};
final FetchListener<FollowModel[]> followersFetchListener = new FetchListener<FollowModel[]>() {
@Override
public void doBefore() {
followBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final FollowModel[] result) {
if (result != null) {
followersModels.addAll(Arrays.asList(result));
final FollowModel model = result[result.length - 1];
if (model == null || !model.hasNextPage()) {
stopCurrentExecutor();
currentlyExecuting = new FollowFetcher(id, false, followingFetchListener)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
stopCurrentExecutor();
currentlyExecuting = new FollowFetcher(id, true, model.getEndCursor(), this)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
model.setPageCursor(false, null);
}
}
}
};
currentlyExecuting = new FollowFetcher(id, true, followersFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.follow, menu);
final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
// private final Filter filter = new Filter() {
// private final ArrayList<FollowModel> searchFollowModels = new ArrayList<>(followModels.size() / 2);
// private final ArrayList<FollowModel> searchFollowingModels = new ArrayList<>(followingModels.size() / 2);
// private final ArrayList<FollowModel> searchFollowersModels = new ArrayList<>(followersModels.size() / 2);
// private final ArrayList<FollowModel> searchAllFollowing = new ArrayList<>(allFollowing.size() / 2);
//
// @Nullable
// @Override
// protected FilterResults performFiltering(@NonNull final CharSequence constraint) {
// searchFollowModels.clear();
// searchFollowingModels.clear();
// searchFollowersModels.clear();
// searchAllFollowing.clear();
//
// final int followModelsSize = followModels.size();
// final int followingModelsSize = followingModels.size();
// final int followersModelsSize = followersModels.size();
// final int allFollowingSize = allFollowing.size();
//
// int maxSize = followModelsSize;
// if (maxSize < followingModelsSize) maxSize = followingModelsSize;
// if (maxSize < followersModelsSize) maxSize = followersModelsSize;
// if (maxSize < allFollowingSize) maxSize = allFollowingSize;
//
// final String query = constraint.toString().toLowerCase();
// FollowModel followModel;
// while (maxSize != -1) {
// if (maxSize < followModelsSize) {
// followModel = followModels.get(maxSize);
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName()))
// searchFollowModels.add(followModel);
// }
//
// if (maxSize < followingModelsSize) {
// followModel = followingModels.get(maxSize);
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName()))
// searchFollowingModels.add(followModel);
// }
//
// if (maxSize < followersModelsSize) {
// followModel = followersModels.get(maxSize);
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName()))
// searchFollowersModels.add(followModel);
// }
//
// if (maxSize < allFollowingSize) {
// followModel = allFollowing.get(maxSize);
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName()))
// searchAllFollowing.add(followModel);
// }
//
// --maxSize;
// }
//
// return null;
// }
//
// @Override
// protected void publishResults(final CharSequence query, final FilterResults results) {
// refreshAdapter(searchFollowModels, searchFollowingModels, searchFollowersModels, searchAllFollowing);
// }
// };
@Override
public boolean onQueryTextSubmit(final String query) {
return false;
}
@Override
public boolean onQueryTextChange(final String query) {
// if (Utils.isEmpty(query)) refreshAdapter(followModels, followingModels, followersModels, allFollowing);
// else filter.filter(query.toLowerCase());
if (adapter != null) adapter.getFilter().filter(query);
return true;
}
});
final MenuItem menuCompare = menu.findItem(R.id.action_compare);
menuCompare.setOnMenuItemClickListener(item -> {
followBinding.rvFollow.setAdapter(null);
if (isCompare) listFollows();
else listCompare();
isCompare = !isCompare;
return true;
});
return true;
}
private void refreshAdapter(final ArrayList<FollowModel> followModels, final ArrayList<FollowModel> followingModels,
final ArrayList<FollowModel> followersModels, final ArrayList<FollowModel> allFollowing) {
final ArrayList<ExpandableGroup> groups = new ArrayList<>(1);
if (isCompare) {
if (followingModels.size() > 0)
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_following, name), followingModels));
if (followersModels.size() > 0)
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_follower, namePost), followersModels));
if (allFollowing.size() > 0)
groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing));
} else {
final ExpandableGroup group = new ExpandableGroup(type, followModels);
groups.add(group);
}
adapter = new FollowAdapter(this, clickListener, groups);
adapter.toggleGroup(0);
followBinding.rvFollow.setAdapter(adapter);
}
public 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);
}
}
}
}

View file

@ -0,0 +1,130 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.CompoundButton;
import android.widget.Toast;
import androidx.annotation.Nullable;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ActivityLoginBinding;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private final WebViewClient webViewClient = new WebViewClient() {
@Override
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
webViewUrl = url;
}
@Override
public void onPageFinished(final WebView view, final String url) {
webViewUrl = url;
}
};
private final WebChromeClient webChromeClient = new WebChromeClient();
private String webViewUrl, defaultUserAgent;
private ActivityLoginBinding loginBinding;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(loginBinding.getRoot());
initWebView();
loginBinding.desktopMode.setOnCheckedChangeListener(this);
loginBinding.cookies.setOnClickListener(this);
loginBinding.refresh.setOnClickListener(this);
}
@Override
public void onClick(final View v) {
if (v == loginBinding.refresh) {
loginBinding.webView.loadUrl("https://instagram.com/");
} else if (v == loginBinding.cookies) {
final String mainCookie = Utils.getCookie(webViewUrl);
if (Utils.isEmpty(mainCookie))
Toast.makeText(this, R.string.login_error_loading_cookies, Toast.LENGTH_SHORT).show();
else {
Utils.setupCookies(mainCookie);
settingsHelper.putString(Constants.COOKIE, mainCookie);
Toast.makeText(this, R.string.login_success_loading_cookies, Toast.LENGTH_SHORT).show();
finish();
}
}
}
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
final WebSettings webSettings = loginBinding.webView.getSettings();
final String newUserAgent = isChecked ? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
: defaultUserAgent;
webSettings.setUserAgentString(newUserAgent);
webSettings.setUseWideViewPort(isChecked);
webSettings.setLoadWithOverviewMode(isChecked);
webSettings.setSupportZoom(isChecked);
webSettings.setBuiltInZoomControls(isChecked);
loginBinding.webView.loadUrl("https://instagram.com/");
}
@SuppressLint("SetJavaScriptEnabled")
private void initWebView() {
if (loginBinding != null) {
loginBinding.webView.setWebChromeClient(webChromeClient);
loginBinding.webView.setWebViewClient(webViewClient);
final WebSettings webSettings = loginBinding.webView.getSettings();
if (webSettings != null) {
if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString();
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setDisplayZoomControls(false);
webSettings.setLoadWithOverviewMode(true);
webSettings.setUseWideViewPort(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
loginBinding.webView.loadUrl("https://instagram.com/");
}
}
@Override
protected void onPause() {
if (loginBinding != null) loginBinding.webView.onPause();
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (loginBinding != null) loginBinding.webView.onResume();
}
@Override
protected void onDestroy() {
if (loginBinding != null) loginBinding.webView.destroy();
super.onDestroy();
}
}

View file

@ -0,0 +1,480 @@
package awais.instagrabber.activities;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.MatrixCursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.provider.BaseColumns;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.GridLayoutManager;
import java.util.ArrayList;
import java.util.Stack;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.MainHelper;
import awais.instagrabber.R;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.customviews.MouseDrawer;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.dialogs.AboutDialog;
import awais.instagrabber.dialogs.QuickAccessDialog;
import awais.instagrabber.dialogs.SettingsDialog;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.ItemGetter;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.MyApps;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class Main extends BaseLanguageActivity {
public static FetchListener<String> scanHack;
public static ItemGetter itemGetter;
// -------- items --------
public final ArrayList<PostModel> allItems = new ArrayList<>();
public final ArrayList<FeedModel> feedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> discoverItems = new ArrayList<>();
// -------- items --------
public final ArrayList<PostModel> selectedItems = new ArrayList<>();
public final ArrayList<DiscoverItemModel> selectedDiscoverItems = new ArrayList<>();
// -------- items --------
public final HighlightsAdapter highlightsAdapter = new HighlightsAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof HighlightModel) {
final HighlightModel highlightModel = (HighlightModel) tag;
startActivity(new Intent(Main.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery)
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, highlightModel.getStoryModels()));
}
}
});
private SuggestionsAdapter suggestionAdapter;
private MenuItem searchAction;
public ActivityMainBinding mainBinding;
public SearchView searchView;
public MenuItem downloadAction, settingsAction, dmsAction;
public StoryModel[] storyModels;
public String userQuery = null;
public MainHelper mainHelper;
public ProfileModel profileModel;
private AutoCompleteTextView searchAutoComplete;
private ArrayAdapter<String> profileDialogAdapter;
private DialogInterface.OnClickListener profileDialogListener;
private Stack<String> queriesStack;
public Main() {
super();
Utils.changeTheme();
}
@Override
protected void onCreate(@Nullable final Bundle bundle) {
super.onCreate(bundle);
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this);
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String uid = Utils.getUserIdFromCookie(cookie);
Utils.setupCookies(cookie);
MainHelper.stopCurrentExecutor();
mainHelper = new MainHelper(this);
if (bundle == null) {
queriesStack = new Stack<>();
userQuery = null;
} else {
setStack(bundle);
userQuery = bundle.getString("query");
}
itemGetter = itemGetType -> {
if (itemGetType == ItemGetType.MAIN_ITEMS) return allItems;
if (itemGetType == ItemGetType.DISCOVER_ITEMS) return discoverItems;
if (itemGetType == ItemGetType.FEED_ITEMS) return feedItems;
return null;
};
scanHack = result -> {
if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer();
addToStack();
userQuery = result;
mainHelper.onRefresh();
}
};
// searches for your userid and returns username
if (uid != null) {
final FetchListener<String> fetchListener = username -> {
if (!Utils.isEmpty(username)) {
if (!BuildConfig.DEBUG) {
userQuery = username;
if (mainHelper != null && !mainBinding.swipeRefreshLayout.isRefreshing()) mainHelper.onRefresh();
}
// adds cookies to database for quick access
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid);
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername()))
Utils.dataBox.addUserCookie(new DataBox.CookieModel(uid, username, cookie));
}
};
boolean found = false;
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid);
if (cookieModel != null) {
final String username = cookieModel.getUsername();
if (username != null) {
found = true;
fetchListener.onResult(username);
}
}
if (!found) // if not in database, fetch info from instagram
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
suggestionAdapter = new SuggestionsAdapter(this, v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
addToStack();
userQuery = tag.toString();
mainHelper.onRefresh();
}
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
}
});
final Resources resources = getResources();
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
new String[]{resources.getString(R.string.view_pfp), resources.getString(R.string.show_stories)});
profileDialogListener = (dialog, which) -> {
final Intent intent;
if (which == 0 || storyModels == null || storyModels.length < 1)
intent = new Intent(this, ProfileViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel);
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery)
.putExtra(Constants.EXTRAS_STORIES, storyModels);
startActivity(intent);
};
final View.OnClickListener onClickListener = v -> {
if (v == mainBinding.mainBiography) {
Utils.copyText(this, mainBinding.mainBiography.getText().toString());
} else if (v == mainBinding.mainProfileImage) {
if (storyModels == null || storyModels.length <= 0) {
profileDialogListener.onClick(null, 0);
} else {
// because sometimes configuration changes made this crash on some phones
new AlertDialog.Builder(this).setAdapter(profileDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null).show();
}
}
};
mainBinding.mainBiography.setOnClickListener(onClickListener);
mainBinding.mainProfileImage.setOnClickListener(onClickListener);
mainBinding.mainBiography.setEnabled(false);
mainBinding.mainProfileImage.setEnabled(false);
final boolean isQueryNull = userQuery == null;
if (isQueryNull) allItems.clear();
if (BuildConfig.DEBUG && isQueryNull) userQuery = "the.badak"; // todo
if (!mainBinding.swipeRefreshLayout.isRefreshing() && userQuery != null) mainHelper.onRefresh();
mainHelper.onIntent(getIntent());
}
private void downloadSelectedItems() {
if (selectedItems.size() > 0) {
Utils.batchDownload(this, userQuery, DownloadMethod.DOWNLOAD_MAIN, selectedItems);
} else if (selectedDiscoverItems.size() > 0) {
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_DISCOVER, selectedDiscoverItems);
}
}
@Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
mainHelper.onIntent(intent);
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState, @NonNull final PersistableBundle outPersistentState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
public void onRestoreInstanceState(@Nullable final Bundle savedInstanceState, @Nullable final PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
if (savedInstanceState != null) {
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
}
}
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putString("query", userQuery);
outState.putSerializable("stack", queriesStack);
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
userQuery = savedInstanceState.getString("query");
setStack(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
final FragmentManager fragmentManager = getSupportFragmentManager();
final MenuItem quickAccessAction = menu.findItem(R.id.action_quickaccess).setVisible(true);
final MenuItem.OnMenuItemClickListener clickListener = item -> {
if (item == downloadAction) {
downloadSelectedItems();
} else if (item == dmsAction)
startActivity(new Intent(this, DirectMessages.class));
else if (item == settingsAction)
new SettingsDialog().show(fragmentManager, "settings");
else if (item == quickAccessAction)
new QuickAccessDialog().setQuery(userQuery).show(fragmentManager, "quickAccess");
else
new AboutDialog().show(fragmentManager, "about");
return true;
};
quickAccessAction.setOnMenuItemClickListener(clickListener);
menu.findItem(R.id.action_about).setVisible(true).setOnMenuItemClickListener(clickListener);
dmsAction = menu.findItem(R.id.action_dms).setOnMenuItemClickListener(clickListener);
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);
dmsAction.setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
searchAction = menu.findItem(R.id.action_search);
searchView = (SearchView) searchAction.getActionView();
final View searchText = searchView.findViewById(R.id.search_src_text);
if (searchText instanceof AutoCompleteTextView)
searchAutoComplete = (AutoCompleteTextView) searchText;
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> searchView.setQuery(userQuery, false));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
private boolean searchUser, searchHash;
private AsyncTask<?, ?, ?> prevSuggestionAsync;
private final String[] COLUMNS = {BaseColumns._ID, Constants.EXTRAS_USERNAME, Constants.EXTRAS_NAME,
Constants.EXTRAS_TYPE, "pfp", "verified"};
private final FetchListener<SuggestionModel[]> fetchListener = new FetchListener<SuggestionModel[]>() {
@Override
public void doBefore() {
suggestionAdapter.changeCursor(null);
}
@Override
public void onResult(final SuggestionModel[] result) {
final MatrixCursor cursor;
if (result == null) cursor = null;
else {
cursor = new MatrixCursor(COLUMNS, 0);
for (int i = 0; i < result.length; i++) {
final SuggestionModel suggestionModel = result[i];
if (suggestionModel != null) {
final SuggestionType suggestionType = suggestionModel.getSuggestionType();
final Object[] objects = {i, suggestionModel.getUsername(), suggestionModel.getName(),
suggestionType, suggestionModel.getProfilePic(), suggestionModel.isVerified()};
if (!searchHash && !searchUser) cursor.addRow(objects);
else {
final boolean isCurrHash = suggestionType == SuggestionType.TYPE_HASHTAG;
if (searchHash && isCurrHash || !searchHash && !isCurrHash)
cursor.addRow(objects);
}
}
}
}
suggestionAdapter.changeCursor(cursor);
}
};
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try { prevSuggestionAsync.cancel(true); } catch (final Exception ignored) { }
}
@Override
public boolean onQueryTextSubmit(final String query) {
cancelSuggestionsAsync();
closeAnyOpenDrawer();
addToStack();
userQuery = query;
searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
mainHelper.onRefresh();
return false;
}
@Override
public boolean onQueryTextChange(final String newText) {
cancelSuggestionsAsync();
if (!Utils.isEmpty(newText)) {
searchUser = newText.charAt(0) == '@';
searchHash = newText.charAt(0) == '#';
if (newText.length() == 1 && (searchHash || searchUser)) {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(2);
} else {
if (searchAutoComplete != null) searchAutoComplete.setThreshold(1);
prevSuggestionAsync = new SuggestionsFetcher(fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
searchUser || searchHash ? newText.substring(1) : newText);
}
}
return true;
}
});
return true;
}
@Override
public void onBackPressed() {
if (closeAnyOpenDrawer()) return;
if (searchView != null && !searchView.isIconified()) {
if (searchAction != null) searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
return;
}
if (!mainHelper.isSelectionCleared()) return;
final GridLayoutManager layoutManager = (GridLayoutManager) mainBinding.mainPosts.getLayoutManager();
if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() >= layoutManager.getSpanCount()) {
mainBinding.mainPosts.smoothScrollToPosition(0);
mainBinding.appBarLayout.setExpanded(true, true);
return;
}
if (queriesStack != null && queriesStack.size() > 0) {
userQuery = queriesStack.pop();
if (userQuery != null) {
mainHelper.onRefresh();
return;
}
}
MyApps.showAlertDialog(this, (parent, view, position, id) -> {
if (id == -1 && position == -1 && parent == null) super.onBackPressed();
else MyApps.openAppStore(this, position);
});
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
downloadSelectedItems();
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 9629 && (resultCode == 1692 || resultCode == RESULT_CANCELED))
finish();
else if (requestCode == 6007)
Utils.showImportExportDialog(this);
else if (requestCode == 6969 && mainHelper.currentFeedPlayer != null)
mainHelper.currentFeedPlayer.setPlayWhenReady(true);
}
@Override
protected void onPause() {
if (mainHelper != null) mainHelper.onPause();
super.onPause();
}
@Override
protected void onResume() {
if (mainHelper != null) mainHelper.onResume();
super.onResume();
}
private void setStack(final Bundle bundle) {
final Object stack = bundle != null ? bundle.get("stack") : null;
if (stack instanceof Stack) //noinspection unchecked
queriesStack = (Stack<String>) stack;
}
public void addToStack() {
if (userQuery != null) {
if (queriesStack == null) queriesStack = new Stack<>();
queriesStack.add(userQuery);
}
}
private boolean closeAnyOpenDrawer() {
final int childCount = mainBinding.drawerLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mainBinding.drawerLayout.getChildAt(i);
final MouseDrawer.LayoutParams childLp = (MouseDrawer.LayoutParams) child.getLayoutParams();
if ((childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENED) == 1 ||
(childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENING) == 2 ||
childLp.onScreen >= 0.6 || childLp.isPeeking) {
mainBinding.drawerLayout.closeDrawer(child);
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,639 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsMediaAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.customviews.CommentMentionClickSpan;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.ActivityViewerBinding;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class PostViewer extends BaseLanguageActivity {
private ActivityViewerBinding viewerBinding;
private String url, prevUsername, commentsEndCursor;
private ProfileModel profileModel;
private BasePostModel postModel;
private ViewerPostModel viewerPostModel;
private SimpleExoPlayer player;
private ArrayAdapter<String> profileDialogAdapter;
private View viewsContainer, viewerCaptionParent;
private GestureDetectorCompat gestureDetector;
private SwipeEvent swipeEvent;
private CharSequence postCaption = null, postShortCode;
private Resources resources;
private boolean session = false, isFromShare;
private int slidePos = 0, lastSlidePos = 0;
private ItemGetType itemGetType;
@SuppressLint("ClickableViewAccessibility")
final View.OnTouchListener gestureTouchListener = new View.OnTouchListener() {
private float startX;
private float startY;
@Override
public boolean onTouch(final View v, final MotionEvent event) {
if (v == viewerCaptionParent) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
if (!(Utils.isEmpty(postCaption) ||
Math.abs(startX - event.getX()) > 50 || Math.abs(startY - event.getY()) > 50)) {
Utils.copyText(PostViewer.this, postCaption);
return false;
}
}
}
return gestureDetector.onTouchEvent(event);
}
};
private final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
final String username = viewerPostModel.getUsername();
if (which == 0) {
searchUsername(username);
} else if (profileModel != null && which == 1) {
startActivity(new Intent(this, ProfileViewer.class)
.putExtra(Constants.EXTRAS_PROFILE, profileModel));
}
};
private final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
if (v == viewerBinding.topPanel.ivProfilePic) {
new AlertDialog.Builder(PostViewer.this).setAdapter(profileDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null).setTitle(viewerPostModel.getUsername()).show();
} else if (v == viewerBinding.ivToggleFullScreen) {
toggleFullscreen();
final LinearLayout topPanelRoot = viewerBinding.topPanel.getRoot();
final int iconRes;
if (containerLayoutParams.height == 0) {
containerLayoutParams.height = LinearLayout.LayoutParams.MATCH_PARENT;
iconRes = R.drawable.ic_fullscreen_exit;
topPanelRoot.setVisibility(View.GONE);
viewerBinding.btnDownload.setVisibility(View.VISIBLE);
} else {
containerLayoutParams.height = 0;
iconRes = R.drawable.ic_fullscreen;
topPanelRoot.setVisibility(View.VISIBLE);
viewerBinding.btnDownload.setVisibility(View.GONE);
}
viewerBinding.ivToggleFullScreen.setImageResource(iconRes);
viewerBinding.container.setLayoutParams(containerLayoutParams);
} else if (v == viewerBinding.bottomPanel.btnMute) {
if (player != null) {
final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol);
viewerBinding.bottomPanel.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute);
Utils.sessionVolumeFull = intVol == 1f;
}
} else {
final Object tag = v.getTag();
if (tag instanceof ViewerPostModel) {
viewerPostModel = (ViewerPostModel) tag;
slidePos = Math.max(0, viewerPostModel.getPosition());
refreshPost();
}
}
}
};
private final View.OnClickListener downloadClickListener = v -> {
if (ContextCompat.checkSelfPermission(this, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
showDownloadDialog();
else
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020);
};
private final PostsMediaAdapter mediaAdapter = new PostsMediaAdapter(null, onClickListener);
private RequestManager glideRequestManager;
private LinearLayout.LayoutParams containerLayoutParams;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewerBinding = ActivityViewerBinding.inflate(getLayoutInflater());
setContentView(viewerBinding.getRoot());
glideRequestManager = Glide.with(this);
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST)
|| (postModel = (PostModel) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) {
Utils.errorFinish(this);
return;
}
containerLayoutParams = (LinearLayout.LayoutParams) viewerBinding.container.getLayoutParams();
if (intent.hasExtra(Constants.EXTRAS_TYPE))
itemGetType = (ItemGetType) intent.getSerializableExtra(Constants.EXTRAS_TYPE);
resources = getResources();
final View viewStoryPost = findViewById(R.id.viewStoryPost);
if (viewStoryPost != null) viewStoryPost.setVisibility(View.GONE);
viewerBinding.topPanel.title.setMovementMethod(new LinkMovementMethod());
viewerBinding.topPanel.title.setMentionClickListener((view, text, isHashtag) ->
onClickListener.onClick(viewerBinding.topPanel.ivProfilePic));
viewerBinding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
viewerBinding.ivToggleFullScreen.setOnClickListener(onClickListener);
viewerBinding.btnDownload.setOnClickListener(downloadClickListener);
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
new String[]{resources.getString(R.string.open_profile), resources.getString(R.string.view_pfp)});
postModel.setPosition(intent.getIntExtra(Constants.EXTRAS_INDEX, -1));
postShortCode = postModel.getShortCode();
final boolean postIdNull = postModel.getPostId() == null;
if (!postIdNull)
setupPostInfoBar(intent.getStringExtra(Constants.EXTRAS_USER), postModel.getItemType());
isFromShare = postModel.getPosition() == -1 || postIdNull;
viewerCaptionParent = (View) viewerBinding.bottomPanel.viewerCaption.getParent();
viewsContainer = (View) viewerBinding.bottomPanel.tvVideoViews.getParent();
viewerBinding.mediaList.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
viewerBinding.mediaList.setAdapter(mediaAdapter);
viewerBinding.mediaList.setVisibility(View.GONE);
swipeEvent = isRight -> {
final List<? extends BasePostModel> itemGetterItems;
final boolean isMainSwipe;
if (itemGetType != null && Main.itemGetter != null) {
itemGetterItems = Main.itemGetter.get(itemGetType);
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare);
} else {
itemGetterItems = null;
isMainSwipe = false;
}
final BasePostModel[] basePostModels = mediaAdapter != null ? mediaAdapter.getPostModels() : null;
final int slides = basePostModels != null ? basePostModels.length : 0;
int position = postModel.getPosition();
if (isRight) {
--slidePos;
if (!isMainSwipe && slidePos < 0) slidePos = 0;
if (slides > 0 && slidePos >= 0) {
if (basePostModels[slidePos] instanceof ViewerPostModel) {
viewerPostModel = (ViewerPostModel) basePostModels[slidePos];
}
refreshPost();
return;
}
if (isMainSwipe && --position < 0) position = itemGetterItems.size() - 1;
} else {
++slidePos;
if (!isMainSwipe && slidePos >= slides) slidePos = slides - 1;
if (slides > 0 && slidePos < slides) {
if (basePostModels[slidePos] instanceof ViewerPostModel) {
viewerPostModel = (ViewerPostModel) basePostModels[slidePos];
}
refreshPost();
return;
}
if (isMainSwipe && ++position >= itemGetterItems.size()) position = 0;
}
if (isMainSwipe) {
slidePos = 0;
Log.d("AWAISKING_APP", "swipe left <<< post[" + position + "]: " + postModel + " -- " + slides);
postModel = itemGetterItems.get(position);
postModel.setPosition(position);
viewPost();
}
};
gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent));
viewPost();
}
private void viewPost() {
lastSlidePos = 0;
mediaAdapter.setData(null);
viewsContainer.setVisibility(View.GONE);
viewerCaptionParent.setVisibility(View.GONE);
viewerBinding.mediaList.setVisibility(View.GONE);
viewerBinding.btnDownload.setVisibility(View.GONE);
viewerBinding.bottomPanel.btnMute.setVisibility(View.GONE);
viewerBinding.bottomPanel.tvPostDate.setVisibility(View.GONE);
viewerBinding.bottomPanel.btnComments.setVisibility(View.GONE);
viewerBinding.bottomPanel.btnDownload.setVisibility(View.INVISIBLE);
viewerBinding.bottomPanel.viewerCaption.setText(null);
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener(null);
viewerBinding.playerView.setVisibility(View.GONE);
viewerBinding.playerView.setPlayer(null);
viewerBinding.imageViewer.setImageResource(0);
viewerBinding.imageViewer.setImageDrawable(null);
new PostFetcher(postModel.getShortCode(), result -> {
if (result == null || result.length < 1) {
Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
viewerPostModel = result[0];
commentsEndCursor = viewerPostModel.getCommentsEndCursor();
mediaAdapter.setData(result);
if (result.length > 1) {
viewerBinding.mediaList.setVisibility(View.VISIBLE);
}
viewerCaptionParent.setOnTouchListener(gestureTouchListener);
viewerBinding.playerView.setOnTouchListener(gestureTouchListener);
viewerBinding.imageViewer.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> {
final float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SwipeGestureListener.SWIPE_THRESHOLD
&& Math.abs(velocityX) > SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD) {
swipeEvent.onSwipe(diffX > 0);
return true;
}
return false;
});
final long commentsCount = viewerPostModel.getCommentsCount();
viewerBinding.bottomPanel.commentsCount.setText(String.valueOf(commentsCount));
viewerBinding.bottomPanel.btnComments.setVisibility(View.VISIBLE);
if (commentsCount > 0) {
viewerBinding.bottomPanel.btnComments.setOnClickListener(v ->
startActivityForResult(new Intent(this, CommentsViewer.class)
.putExtra(Constants.EXTRAS_END_CURSOR, commentsEndCursor)
.putExtra(Constants.EXTRAS_SHORTCODE, postShortCode), 6969));
viewerBinding.bottomPanel.btnComments.setClickable(true);
viewerBinding.bottomPanel.btnComments.setEnabled(true);
} else {
viewerBinding.bottomPanel.btnComments.setOnClickListener(null);
viewerBinding.bottomPanel.btnComments.setClickable(false);
viewerBinding.bottomPanel.btnComments.setEnabled(false);
}
if (postModel instanceof PostModel) {
final PostModel postModel = (PostModel) this.postModel;
postModel.setPostId(viewerPostModel.getPostId());
postModel.setTimestamp(viewerPostModel.getTimestamp());
postModel.setPostCaption(viewerPostModel.getPostCaption());
}
setupPostInfoBar(viewerPostModel.getUsername(), viewerPostModel.getItemType());
postCaption = postModel.getPostCaption();
viewerCaptionParent.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.btnDownload.setOnClickListener(downloadClickListener);
refreshPost();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void searchUsername(final String text) {
if (Main.scanHack != null) {
Main.scanHack.onResult(text);
finish();
}
}
private void setupVideo() {
viewerBinding.playerView.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.btnDownload.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.btnMute.setVisibility(View.VISIBLE);
viewsContainer.setVisibility(View.VISIBLE);
viewerBinding.progressView.setVisibility(View.GONE);
viewerBinding.imageViewer.setVisibility(View.GONE);
viewerBinding.imageViewer.setImageDrawable(null);
viewerBinding.bottomPanel.tvVideoViews.setText(String.valueOf(viewerPostModel.getVideoViews()));
player = new SimpleExoPlayer.Builder(this).build();
viewerBinding.playerView.setPlayer(player);
float vol = Utils.settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, "instagram"))
.createMediaSource(Uri.parse(url));
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
viewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
viewerBinding.progressView.setVisibility(View.VISIBLE);
}
@Override
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
viewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) {
viewerBinding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
player.setVolume(vol);
viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener);
}
private void setupImage() {
viewsContainer.setVisibility(View.GONE);
viewerBinding.playerView.setVisibility(View.GONE);
viewerBinding.progressView.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.btnMute.setVisibility(View.GONE);
viewerBinding.bottomPanel.btnDownload.setVisibility(View.VISIBLE);
viewerBinding.imageViewer.setImageDrawable(null);
viewerBinding.imageViewer.setVisibility(View.VISIBLE);
viewerBinding.imageViewer.setZoomable(true);
viewerBinding.imageViewer.setZoomTransitionDuration(420);
viewerBinding.imageViewer.setMaximumScale(7.2f);
glideRequestManager.load(url).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
viewerBinding.progressView.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
viewerBinding.progressView.setVisibility(View.GONE);
return false;
}
}).into(viewerBinding.imageViewer);
}
private void showDownloadDialog() {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
if (!session && viewerBinding.mediaList.getVisibility() == View.VISIBLE) {
final DialogInterface.OnClickListener clickListener = (dialog, which) -> {
postModels.clear();
if (which == DialogInterface.BUTTON_NEGATIVE) {
final BasePostModel[] adapterPostModels = mediaAdapter.getPostModels();
for (int i = 0, size = mediaAdapter.getItemCount(); i < size; ++i) {
if (adapterPostModels[i] instanceof ViewerPostModel)
postModels.add(adapterPostModels[i]);
}
} else if (which == DialogInterface.BUTTON_POSITIVE) {
postModels.add(viewerPostModel);
} else {
session = true;
postModels.add(viewerPostModel);
}
if (postModels.size() > 0)
Utils.batchDownload(this, viewerPostModel.getUsername(), DownloadMethod.DOWNLOAD_POST_VIEWER, postModels);
};
new AlertDialog.Builder(this).setTitle(R.string.post_viewer_download_dialog_title)
.setMessage(R.string.post_viewer_download_message)
.setNeutralButton(R.string.post_viewer_download_session, clickListener).setPositiveButton(R.string.post_viewer_download_current, clickListener)
.setNegativeButton(R.string.post_viewer_download_album, clickListener).show();
} else {
Utils.batchDownload(this, viewerPostModel.getUsername(), DownloadMethod.DOWNLOAD_POST_VIEWER, Collections.singletonList(viewerPostModel));
}
}
@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)
showDownloadDialog();
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == 6969) {
setResult(RESULT_OK);
finish();
}
}
@Override
public void onPause() {
super.onPause();
if (Build.VERSION.SDK_INT < 24) releasePlayer();
}
@Override
public void onStop() {
super.onStop();
if (Build.VERSION.SDK_INT >= 24) releasePlayer();
}
@Override
protected void onResume() {
super.onResume();
if (player == null && viewerPostModel != null && viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO)
setupVideo();
else if (player != null) {
player.setPlayWhenReady(true);
player.getPlaybackState();
}
}
private void refreshPost() {
postShortCode = postModel.getShortCode();
if (viewerBinding.mediaList.getVisibility() == View.VISIBLE) {
ViewerPostModel item = mediaAdapter.getItemAt(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
mediaAdapter.notifyItemChanged(lastSlidePos, item);
}
item = mediaAdapter.getItemAt(slidePos);
if (item != null) {
item.setCurrentSlide(true);
mediaAdapter.notifyItemChanged(slidePos, item);
}
}
lastSlidePos = slidePos;
postCaption = viewerPostModel.getPostCaption();
if (Utils.hasMentions(postCaption)) {
viewerBinding.bottomPanel.viewerCaption.setText(Utils.getMentionText(postCaption), TextView.BufferType.SPANNABLE);
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener((view, text, isHashtag) ->
new AlertDialog.Builder(PostViewer.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());
} else {
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener(null);
viewerBinding.bottomPanel.viewerCaption.setText(postCaption);
}
setupPostInfoBar(viewerPostModel.getUsername(), viewerPostModel.getItemType());
if (postModel instanceof PostModel) {
final PostModel postModel = (PostModel) this.postModel;
postModel.setPostId(viewerPostModel.getPostId());
postModel.setTimestamp(viewerPostModel.getTimestamp());
postModel.setPostCaption(viewerPostModel.getPostCaption());
}
viewerBinding.bottomPanel.tvPostDate.setText(viewerPostModel.getPostDate());
viewerBinding.bottomPanel.tvPostDate.setVisibility(View.VISIBLE);
viewerBinding.bottomPanel.tvPostDate.setSelected(true);
url = viewerPostModel.getDisplayUrl();
releasePlayer();
viewerBinding.btnDownload.setVisibility(containerLayoutParams.height == 0 ? View.GONE : View.VISIBLE);
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
}
private void releasePlayer() {
if (player != null) {
player.release();
player = null;
}
}
private void setupPostInfoBar(final String from, final MediaItemType mediaItemType) {
if (prevUsername == null || !prevUsername.equals(from)) {
viewerBinding.topPanel.ivProfilePic.setImageBitmap(null);
viewerBinding.topPanel.ivProfilePic.setImageDrawable(null);
viewerBinding.topPanel.ivProfilePic.setImageResource(0);
if (from.charAt(0) != '#')
new ProfileFetcher(from, result -> {
profileModel = result;
if (result != null) {
final String hdProfilePic = result.getHdProfilePic();
final String sdProfilePic = result.getSdProfilePic();
final boolean hdPicEmpty = Utils.isEmpty(hdProfilePic);
glideRequestManager.load(hdPicEmpty ? sdProfilePic : hdProfilePic).listener(new RequestListener<Drawable>() {
private boolean loaded = true;
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
viewerBinding.topPanel.ivProfilePic.setEnabled(false);
viewerBinding.topPanel.ivProfilePic.setOnClickListener(null);
if (loaded) {
loaded = false;
if (!Utils.isEmpty(sdProfilePic)) glideRequestManager.load(sdProfilePic).listener(this)
.into(viewerBinding.topPanel.ivProfilePic);
}
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
viewerBinding.topPanel.ivProfilePic.setEnabled(true);
viewerBinding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
return false;
}
}).into(viewerBinding.topPanel.ivProfilePic);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
prevUsername = from;
}
final String titlePrefix = resources.getString(mediaItemType == MediaItemType.MEDIA_TYPE_VIDEO ?
R.string.post_viewer_video_post : R.string.post_viewer_image_post);
if (Utils.isEmpty(from)) viewerBinding.topPanel.title.setText(titlePrefix);
else {
final CharSequence titleText = resources.getString(R.string.post_viewer_post_from, titlePrefix, from) + " ";
final int titleLen = titleText.length();
final SpannableString spannableString = new SpannableString(titleText);
spannableString.setSpan(new CommentMentionClickSpan(), titleLen - from.length() - 1, titleLen - 1, 0);
viewerBinding.topPanel.title.setText(spannableString);
}
}
private void toggleFullscreen() {
final View decorView = getWindow().getDecorView();
int newUiOptions = decorView.getSystemUiVisibility();
newUiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
newUiOptions ^= View.SYSTEM_UI_FLAG_FULLSCREEN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
newUiOptions ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(newUiOptions);
}
}

View file

@ -0,0 +1,215 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import java.io.File;
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.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.INSTA_STALKER,
ProfilePictureFetchMode.INSTAFULLSIZE,
};
private ActivityProfileBinding profileBinding;
private ProfileModel profileModel;
private MenuItem menuItemDownload;
private String profilePicUrl;
private FragmentManager fragmentManager;
private FetchListener<String> fetchListener;
private boolean errorHandled = false;
private boolean fallbackToProfile = false;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
profileBinding = ActivityProfileBinding.inflate(getLayoutInflater());
setContentView(profileBinding.getRoot());
setSupportActionBar(profileBinding.toolbar.toolbar);
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_PROFILE)
|| (profileModel = (ProfileModel) intent.getSerializableExtra(Constants.EXTRAS_PROFILE)) == null) {
Utils.errorFinish(this);
return;
}
fragmentManager = getSupportFragmentManager();
final String id = profileModel.getId();
final String username = profileModel.getUsername();
profileBinding.toolbar.toolbar.setTitle(username);
profileBinding.progressView.setVisibility(View.VISIBLE);
profileBinding.imageViewer.setVisibility(View.VISIBLE);
profileBinding.imageViewer.setZoomable(true);
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).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return;
}
if (errorHandled && fallbackToProfile || Utils.isEmpty(profilePicUrl))
profilePicUrl = profileModel.getHdProfilePic();
final RequestManager glideRequestManager = Glide.with(this);
glideRequestManager.load(profilePicUrl).addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
fallbackToProfile = true;
if (!errorHandled) {
errorHandled = true;
new ProfilePictureFetcher(username, id, fetchListener, fetchModes[Math.min(2, Math.max(0, fetchIndex + 1))])
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
glideRequestManager.load(profileModel.getHdProfilePic()).into(profileBinding.imageViewer);
showImageInfo();
}
profileBinding.progressView.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
if (menuItemDownload != null) menuItemDownload.setEnabled(true);
showImageInfo();
profileBinding.progressView.setVisibility(View.GONE);
return false;
}
private void showImageInfo() {
final Drawable drawable = profileBinding.imageViewer.getDrawable();
if (drawable != null) {
final StringBuilder info = new StringBuilder(getString(R.string.profile_viewer_imageinfo, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()));
if (drawable instanceof BitmapDrawable) {
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
if (bitmap != null) {
final String colorDepthPrefix = getString(R.string.profile_viewer_colordepth_prefix);
switch (bitmap.getConfig()) {
case ALPHA_8:
info.append(colorDepthPrefix).append(" 8-bits(A)");
break;
case RGB_565:
info.append(colorDepthPrefix).append(" 16-bits-A");
break;
case ARGB_4444:
info.append(colorDepthPrefix).append(" 16-bits+A");
break;
case ARGB_8888:
info.append(colorDepthPrefix).append(" 32-bits+A");
break;
case RGBA_F16:
info.append(colorDepthPrefix).append(" 64-bits+A");
break;
case HARDWARE:
info.append(colorDepthPrefix).append(" auto");
break;
}
}
}
profileBinding.imageInfo.setText(info);
profileBinding.imageInfo.setVisibility(View.VISIBLE);
}
}
}).into(profileBinding.imageViewer);
};
new ProfilePictureFetcher(username, id, fetchListener, fetchMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void downloadProfilePicture() {
int error = 0;
if (profileModel != null) {
final File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (dir.exists() || dir.mkdirs()) {
final File saveFile = new File(dir, profileModel.getUsername() + '_' + System.currentTimeMillis()
+ Utils.getExtensionFromModel(profilePicUrl, profileModel));
new DownloadAsync(this,
profilePicUrl,
saveFile,
result -> {
final int toastRes = result != null && result.exists() ?
R.string.downloader_downloaded_in_folder : R.string.downloader_error_download_file;
Toast.makeText(this, toastRes, Toast.LENGTH_SHORT).show();
}).setItems(null, profileModel.getUsername()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1) Toast.makeText(this, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2) Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
final MenuItem.OnMenuItemClickListener menuItemClickListener = item -> {
if (item == menuItemDownload) {
downloadProfilePicture();
} else {
new ProfileSettingsDialog().show(fragmentManager, "settings");
}
return true;
};
menu.findItem(R.id.action_search).setVisible(false);
menuItemDownload = menu.findItem(R.id.action_download);
menuItemDownload.setVisible(true);
menuItemDownload.setEnabled(false);
menuItemDownload.setOnMenuItemClickListener(menuItemClickListener);
final MenuItem menuItemSettings = menu.findItem(R.id.action_settings);
menuItemSettings.setVisible(true);
menuItemSettings.setOnMenuItemClickListener(menuItemClickListener);
return true;
}
}

View file

@ -0,0 +1,354 @@
package awais.instagrabber.activities;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.ActivityStoryViewerBinding;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class StoryViewer extends BaseLanguageActivity {
private final StoriesAdapter storiesAdapter = new StoriesAdapter(null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
final Object tag = v.getTag();
if (tag instanceof StoryModel) {
currentStory = (StoryModel) tag;
slidePos = currentStory.getPosition();
refreshStory();
}
}
});
private ActivityStoryViewerBinding storyViewerBinding;
private StoryModel[] storyModels;
private GestureDetectorCompat gestureDetector;
private SimpleExoPlayer player;
private SwipeEvent swipeEvent;
private MenuItem menuDownload;
private StoryModel currentStory;
private String url, username;
private int slidePos = 0, lastSlidePos = 0;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
storyViewerBinding = ActivityStoryViewerBinding.inflate(getLayoutInflater());
setContentView(storyViewerBinding.getRoot());
setSupportActionBar(storyViewerBinding.toolbar.toolbar);
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_STORIES)
|| (storyModels = (StoryModel[]) intent.getSerializableExtra(Constants.EXTRAS_STORIES)) == null) {
Utils.errorFinish(this);
return;
}
username = intent.getStringExtra(Constants.EXTRAS_USERNAME);
final String highlight = intent.getStringExtra(Constants.EXTRAS_HIGHLIGHT);
final boolean hasUsername = !Utils.isEmpty(username);
final boolean hasHighlight = !Utils.isEmpty(highlight);
if (hasUsername) {
storyViewerBinding.toolbar.toolbar.setTitle(username);
if (hasHighlight) storyViewerBinding.toolbar.toolbar.setSubtitle(getString(R.string.title_highlight, highlight));
else storyViewerBinding.toolbar.toolbar.setSubtitle(R.string.title_user_story);
}
storyViewerBinding.storiesList.setVisibility(View.GONE);
storyViewerBinding.storiesList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
storyViewerBinding.storiesList.setAdapter(storiesAdapter);
swipeEvent = new SwipeEvent() {
private final int storiesLen = storyModels != null ? storyModels.length : 0;
@Override
public void onSwipe(final boolean isRightSwipe) {
if (storyModels != null && storiesLen > 0) {
if (isRightSwipe) {
if (--slidePos <= 0) slidePos = 0;
} else if (++slidePos >= storiesLen) slidePos = storiesLen - 1;
currentStory = storyModels[slidePos];
slidePos = currentStory.getPosition();
refreshStory();
}
}
};
gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent));
viewPost();
}
@SuppressLint("ClickableViewAccessibility")
private void viewPost() {
lastSlidePos = 0;
storyViewerBinding.storiesList.setVisibility(View.GONE);
storiesAdapter.setData(null);
if (menuDownload != null) menuDownload.setVisible(false);
storyViewerBinding.playerView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event));
storyViewerBinding.imageViewer.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> {
final float diffX = e2.getX() - e1.getX();
try {
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
swipeEvent.onSwipe(diffX > 0);
return true;
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "viewPost",
new Pair<>("swipeEvent", swipeEvent),
new Pair<>("diffX", diffX));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return false;
});
storyViewerBinding.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())));
});
storiesAdapter.setData(storyModels);
if (storyModels.length > 1) storyViewerBinding.storiesList.setVisibility(View.VISIBLE);
currentStory = storyModels[0];
refreshStory();
}
private void setupVideo() {
storyViewerBinding.playerView.setVisibility(View.VISIBLE);
storyViewerBinding.progressView.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setImageDrawable(null);
player = new SimpleExoPlayer.Builder(this).build();
storyViewerBinding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, "instagram"))
.createMediaSource(Uri.parse(url));
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.VISIBLE);
}
@Override
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
storyViewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) {
if (menuDownload != null) menuDownload.setVisible(false);
storyViewerBinding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
storyViewerBinding.playerView.setOnClickListener(v -> {
if (player != null) {
if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0);
player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying());
}
});
}
private void setupImage() {
storyViewerBinding.progressView.setVisibility(View.VISIBLE);
storyViewerBinding.playerView.setVisibility(View.GONE);
storyViewerBinding.imageViewer.setImageDrawable(null);
storyViewerBinding.imageViewer.setVisibility(View.VISIBLE);
storyViewerBinding.imageViewer.setZoomable(true);
storyViewerBinding.imageViewer.setZoomTransitionDuration(420);
storyViewerBinding.imageViewer.setMaximumScale(7.2f);
Glide.with(this).load(url).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) {
storyViewerBinding.progressView.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
if (menuDownload != null) menuDownload.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
return false;
}
}).into(storyViewerBinding.imageViewer);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_search).setVisible(false);
menuDownload = menu.findItem(R.id.action_download);
menuDownload.setVisible(true);
menuDownload.setOnMenuItemClickListener(item -> {
if (ContextCompat.checkSelfPermission(this, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED)
downloadStory();
else
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020);
return true;
});
return true;
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) downloadStory();
}
@Override
public void onPause() {
super.onPause();
if (Build.VERSION.SDK_INT < 24) releasePlayer();
}
@Override
public void onStop() {
super.onStop();
if (Build.VERSION.SDK_INT >= 24) releasePlayer();
}
private void downloadStory() {
int error = 0;
if (currentStory != null) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = settingsHelper.getString(FOLDER_PATH);
if (!Utils.isEmpty(customPath)) dir = new File(customPath);
}
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !Utils.isEmpty(username))
dir = new File(dir, username);
if (dir.exists() || dir.mkdirs()) {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp()
+ Utils.getExtensionFromModel(storyUrl, currentStory));
new DownloadAsync(this, storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
: R.string.downloader_error_download_file;
Toast.makeText(this, toastRes, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else error = 1;
} else error = 2;
if (error == 1) Toast.makeText(this, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show();
else if (error == 2) Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
private void refreshStory() {
if (storyViewerBinding.storiesList.getVisibility() == View.VISIBLE) {
StoryModel item = storiesAdapter.getItemAt(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
storiesAdapter.notifyItemChanged(lastSlidePos, item);
}
item = storiesAdapter.getItemAt(slidePos);
if (item != null) {
item.setCurrentSlide(true);
storiesAdapter.notifyItemChanged(slidePos, item);
}
}
lastSlidePos = slidePos;
final MediaItemType itemType = currentStory.getItemType();
if (menuDownload != null) menuDownload.setVisible(false);
url = itemType == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl();
final String shortCode = currentStory.getTappableShortCode();
storyViewerBinding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
storyViewerBinding.viewStoryPost.setTag(shortCode);
releasePlayer();
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
}
private void releasePlayer() {
if (player != null) {
try { player.stop(true); } catch (Exception ignored) { }
try { player.release(); } catch (Exception ignored) { }
player = null;
}
}
}

View file

@ -0,0 +1,136 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.Utils;
public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolder> implements Filterable {
private final boolean isParent;
private final Filter filter = new Filter() {
@NonNull
@Override
protected FilterResults performFiltering(final CharSequence filter) {
final FilterResults results = new FilterResults();
results.values = commentModels;
final int commentsLen = commentModels == null ? 0 : commentModels.length;
if (commentModels != null && commentsLen > 0 && !Utils.isEmpty(filter)) {
final String query = filter.toString().toLowerCase();
final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen);
for (final CommentModel commentModel : commentModels) {
final String commentText = commentModel.getText().toString().toLowerCase();
if (commentText.contains(query)) filterList.add(commentModel);
else {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
for (final CommentModel childCommentModel : childCommentModels) {
final String childCommentText = childCommentModel.getText().toString().toLowerCase();
if (childCommentText.contains(query)) filterList.add(commentModel);
}
}
}
}
filterList.trimToSize();
results.values = filterList.toArray(new CommentModel[0]);
}
return results;
}
@Override
protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) {
if (results.values instanceof CommentModel[]) {
filteredCommentModels = (CommentModel[]) results.values;
notifyDataSetChanged();
}
}
};
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private final CommentModel[] commentModels;
private final String[] quantityStrings = new String[2];
private LayoutInflater layoutInflater;
private CommentModel[] filteredCommentModels;
public CommentsAdapter(final CommentModel[] commentModels, final boolean isParent, final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) {
this.commentModels = this.filteredCommentModels = commentModels;
this.isParent = isParent;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
}
@Override
public Filter getFilter() {
return filter;
}
@NonNull
@Override
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final Context context = parent.getContext();
if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like);
if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
return new CommentViewHolder(layoutInflater.inflate(
isParent ? R.layout.item_comment // parent
: R.layout.item_comment_small, // child
parent, false), onClickListener, mentionClickListener);
}
@Override
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
final CommentModel commentModel = filteredCommentModels[position];
if (commentModel != null) {
holder.setCommentModel(commentModel);
holder.setCommment(commentModel.getText());
holder.setDate(commentModel.getDateTime());
final long likes = commentModel.getLikes();
holder.setLikes(String.format(LocaleUtils.getCurrentLocale(), "%d %s", likes, quantityStrings[likes == 1 ? 0 : 1]));
final ProfileModel profileModel = commentModel.getProfileModel();
if (profileModel != null) {
holder.setUsername(profileModel.getUsername());
Glide.with(layoutInflater.getContext())
.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true))
.load(profileModel.getSdProfilePic()).into(holder.getProfilePicView());
}
if (holder.isParent()) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null && childCommentModels.length > 0)
holder.setChildAdapter(new CommentsAdapter(childCommentModels, false, onClickListener, mentionClickListener));
else holder.hideChildComments();
}
}
}
@Override
public int getItemCount() {
return filteredCommentModels == null ? 0 : filteredCommentModels.length;
}
}

View file

@ -0,0 +1,116 @@
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.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DirectMessageViewHolder;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemActionLogModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
public final class DirectMessagesAdapter extends RecyclerView.Adapter<DirectMessageViewHolder> {
private final ArrayList<InboxThreadModel> inboxThreadModels;
private final View.OnClickListener onClickListener;
private LayoutInflater layoutInflater;
public DirectMessagesAdapter(final ArrayList<InboxThreadModel> inboxThreadModels, final View.OnClickListener onClickListener) {
this.inboxThreadModels = inboxThreadModels;
this.onClickListener = onClickListener;
}
@NonNull
@Override
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new DirectMessageViewHolder(layoutInflater.inflate(R.layout.layout_include_simple_item, parent, false),
onClickListener);
}
@Override
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final InboxThreadModel threadModel = inboxThreadModels.get(position);
final DirectItemModel[] itemModels;
holder.itemView.setTag(threadModel);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (threadModel != null && (itemModels = threadModel.getItems()) != null) {
final ProfileModel[] users = threadModel.getUsers();
if (users.length > 1) {
holder.ivProfilePic.setVisibility(View.GONE);
holder.multipleProfilePicsContainer.setVisibility(View.VISIBLE);
for (int i = 0; i < Math.min(3, users.length); ++i)
glideRequestManager.load(users[i].getSdProfilePic()).into(holder.multipleProfilePics[i]);
} else {
holder.ivProfilePic.setVisibility(View.VISIBLE);
holder.multipleProfilePicsContainer.setVisibility(View.GONE);
glideRequestManager.load(users[0].getSdProfilePic()).into(holder.ivProfilePic);
}
holder.tvUsername.setText(threadModel.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1];
final DirectItemType itemType = lastItemModel.getItemType();
holder.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = layoutInflater.getContext();
final CharSequence messageText;
if (itemType == DirectItemType.TEXT)
messageText = lastItemModel.getText();
else if (itemType == DirectItemType.LINK)
messageText = context.getString(R.string.direct_messages_sent_link);
else if (itemType == DirectItemType.MEDIA || itemType == DirectItemType.MEDIA_SHARE)
messageText = context.getString(R.string.direct_messages_sent_media);
else if (itemType == DirectItemType.ACTION_LOG) {
final DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
messageText = logModel != null ? logModel.getDescription() : "...";
} else if (itemType == DirectItemType.REEL_SHARE) {
final DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
if (reelShare == null)
messageText = context.getString(R.string.direct_messages_sent_media);
else {
final String reelType = reelShare.getType();
final int textRes;
if ("reply".equals(reelType)) textRes = R.string.direct_messages_replied_story;
else if ("mention".equals(reelType)) textRes = R.string.direct_messages_mention_story;
else if ("reaction".equals(reelType)) textRes = R.string.direct_messages_reacted_story;
else textRes = R.string.direct_messages_sent_media;
messageText = context.getString(textRes) + " : " + reelShare.getText();
}
} else messageText = null;
holder.tvMessage.setText(messageText);
holder.tvDate.setText(lastItemModel.getDateTime());
}
}
@Override
public int getItemCount() {
return inboxThreadModels == null ? 0 : inboxThreadModels.size();
}
}

View file

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

View file

@ -0,0 +1,486 @@
package awais.instagrabber.adapters;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.github.chrisbanes.photoview.PhotoView;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.util.ArrayList;
import java.util.Collections;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.adapters.viewholder.FeedItemViewHolder;
import awais.instagrabber.customviews.CommentMentionClickSpan;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class FeedAdapter extends RecyclerView.Adapter<FeedItemViewHolder> {
private final static String ellipsize = "… more";
private final Activity activity;
private final LayoutInflater layoutInflater;
private final ArrayList<FeedModel> feedModels;
private final MentionClickListener mentionClickListener;
private final View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(@NonNull final View v) {
final Object tag = v.getTag();
if (tag instanceof FeedModel) {
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked())
feedModel.toggleCaption();
feedModel.setMentionClicked(false);
if (!expandCollapseTextView((RamboTextView) v, feedModel))
feedModel.toggleCaption();
} else {
final int id = v.getId();
switch (id) {
case R.id.btnComments:
activity.startActivityForResult(new Intent(activity, CommentsViewer.class)
.putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode()), 6969);
break;
case R.id.viewStoryPost:
activity.startActivity(new Intent(activity, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, feedModel.getPosition())
.putExtra(Constants.EXTRAS_POST, new PostModel(feedModel.getShortCode()))
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.FEED_ITEMS));
break;
case R.id.btnDownload:
final Context context = v.getContext();
ProfileModel profileModel = feedModel.getProfileModel();
final String username = profileModel != null ? profileModel.getUsername() : null;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
Utils.batchDownload(context, username, DownloadMethod.DOWNLOAD_FEED, Collections.singletonList(feedModel));
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected) postModels.add(sliderItem);
else if (sliderItem.isSelected()) {
postModels.add(sliderItem);
break;
}
}
}
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
if (breakWhenFoundSelected && postModels.size() == 0)
postModels.add(sliderItems[0]);
if (postModels.size() > 0)
Utils.batchDownload(context, username, DownloadMethod.DOWNLOAD_FEED, postModels);
};
new AlertDialog.Builder(context).setTitle(R.string.post_viewer_download_dialog_title)
.setPositiveButton(R.string.post_viewer_download_current, clickListener)
.setNegativeButton(R.string.post_viewer_download_album, clickListener).show();
}
break;
case R.id.ivProfilePic:
if (mentionClickListener != null) {
profileModel = feedModel.getProfileModel();
if (profileModel != null)
mentionClickListener.onClick(null, profileModel.getUsername(), false);
}
break;
}
}
}
}
};
private final View.OnLongClickListener longClickListener = v -> {
final Object tag;
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel)
Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption());
return true;
};
public SimpleExoPlayer pagerPlayer;
private final PlayerChangeListener playerChangeListener = (childPos, player) -> {
// todo
pagerPlayer = player;
};
public FeedAdapter(final Activity activity, final ArrayList<FeedModel> FeedModels, final MentionClickListener mentionClickListener) {
this.activity = activity;
this.feedModels = FeedModels;
this.mentionClickListener = mentionClickListener;
this.layoutInflater = LayoutInflater.from(activity);
}
@NonNull
@Override
public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final View view;
if (viewType == MediaItemType.MEDIA_TYPE_VIDEO.ordinal())
view = layoutInflater.inflate(R.layout.item_feed_video, parent, false);
else if (viewType == MediaItemType.MEDIA_TYPE_SLIDER.ordinal())
view = layoutInflater.inflate(R.layout.item_feed_slider, parent, false);
else
view = layoutInflater.inflate(R.layout.item_feed, parent, false);
return new FeedItemViewHolder(view);
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) {
final FeedModel feedModel = feedModels.get(position);
if (feedModel != null) {
final RequestManager glideRequestManager = Glide.with(viewHolder.itemView);
feedModel.setPosition(position);
viewHolder.viewPost.setTag(feedModel);
viewHolder.profilePic.setTag(feedModel);
viewHolder.btnDownload.setTag(feedModel);
viewHolder.viewerCaption.setTag(feedModel);
final ProfileModel profileModel = feedModel.getProfileModel();
if (profileModel != null) {
glideRequestManager.load(profileModel.getSdProfilePic()).into(viewHolder.profilePic);
viewHolder.username.setText(profileModel.getUsername());
}
viewHolder.viewPost.setOnClickListener(clickListener);
viewHolder.profilePic.setOnClickListener(clickListener);
viewHolder.btnDownload.setOnClickListener(clickListener);
viewHolder.tvPostDate.setText(feedModel.getPostDate());
final long commentsCount = feedModel.getCommentsCount();
viewHolder.commentsCount.setText(String.valueOf(commentsCount));
if (commentsCount <= 0) {
viewHolder.btnComments.setTag(null);
viewHolder.btnComments.setOnClickListener(null);
viewHolder.btnComments.setEnabled(false);
} else {
viewHolder.btnComments.setTag(feedModel);
viewHolder.btnComments.setOnClickListener(clickListener);
viewHolder.btnComments.setEnabled(true);
}
final String thumbnailUrl = feedModel.getThumbnailUrl();
final String displayUrl = feedModel.getDisplayUrl();
CharSequence postCaption = feedModel.getPostCaption();
final boolean captionEmpty = Utils.isEmpty(postCaption);
viewHolder.viewerCaption.setOnClickListener(clickListener);
viewHolder.viewerCaption.setOnLongClickListener(longClickListener);
viewHolder.viewerCaption.setVisibility(captionEmpty ? View.GONE : View.VISIBLE);
if (!captionEmpty && Utils.hasMentions(postCaption)) {
postCaption = Utils.getMentionText(postCaption);
feedModel.setPostCaption(postCaption);
viewHolder.viewerCaption.setText(postCaption, TextView.BufferType.SPANNABLE);
viewHolder.viewerCaption.setMentionClickListener(mentionClickListener);
} else {
viewHolder.viewerCaption.setText(postCaption);
}
expandCollapseTextView(viewHolder.viewerCaption, feedModel);
final MediaItemType itemType = feedModel.getItemType();
final View viewToChangeHeight;
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) {
viewToChangeHeight = viewHolder.playerView;
viewHolder.videoViewsParent.setVisibility(View.VISIBLE);
viewHolder.videoViews.setText(String.valueOf(feedModel.getViewCount()));
} else {
viewHolder.videoViewsParent.setVisibility(View.GONE);
viewHolder.btnMute.setVisibility(View.GONE);
if (itemType == MediaItemType.MEDIA_TYPE_SLIDER) {
viewToChangeHeight = viewHolder.mediaList;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0;
if (sliderItemLen > 0) {
viewHolder.mediaCounter.setText("1/" + sliderItemLen);
viewHolder.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen));
final ViewPager.SimpleOnPageChangeListener simpleOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
private int prevPos = 0;
@Override
public void onPageSelected(final int position) {
ViewerPostModel sliderItem = sliderItems[prevPos];
if (sliderItem != null) sliderItem.setSelected(false);
sliderItem = sliderItems[position];
if (sliderItem != null) sliderItem.setSelected(true);
View childAt = viewHolder.mediaList.getChildAt(prevPos);
if (childAt instanceof PlayerView) {
pagerPlayer = (SimpleExoPlayer) ((PlayerView) childAt).getPlayer();
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
}
childAt = viewHolder.mediaList.getChildAt(position);
if (childAt instanceof PlayerView) {
pagerPlayer = (SimpleExoPlayer) ((PlayerView) childAt).getPlayer();
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(true);
}
prevPos = position;
viewHolder.mediaCounter.setText((position + 1) + "/" + sliderItemLen);
}
};
//noinspection deprecation
viewHolder.mediaList.setOnPageChangeListener(simpleOnPageChangeListener); // cause add listeners might add to recycled holders
final View.OnClickListener muteClickListener = v -> {
Player player = null;
if (v instanceof PlayerView) player = ((PlayerView) v).getPlayer();
else if (v instanceof ImageView || v == viewHolder.btnMute) {
final int currentItem = viewHolder.mediaList.getCurrentItem();
if (currentItem < viewHolder.mediaList.getChildCount()) {
final View childAt = viewHolder.mediaList.getChildAt(currentItem);
if (childAt instanceof PlayerView) player = ((PlayerView) childAt).getPlayer();
}
} else {
final Object tag = v.getTag();
if (tag instanceof Player) player = (Player) tag;
}
if (player instanceof SimpleExoPlayer) {
final SimpleExoPlayer exoPlayer = (SimpleExoPlayer) player;
final float intVol = exoPlayer.getVolume() == 0f ? 1f : 0f;
exoPlayer.setVolume(intVol);
viewHolder.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute);
Utils.sessionVolumeFull = intVol == 1f;
}
};
viewHolder.btnMute.setOnClickListener(muteClickListener);
viewHolder.mediaList.setAdapter(new ChildMediaItemsAdapter(sliderItems, viewHolder.btnMute, muteClickListener, playerChangeListener));
}
} else {
viewToChangeHeight = viewHolder.imageView;
String url = displayUrl;
if (Utils.isEmpty(url)) url = thumbnailUrl;
glideRequestManager.load(url).into(viewHolder.imageView);
}
}
if (viewToChangeHeight != null) {
final ViewGroup.LayoutParams layoutParams = viewToChangeHeight.getLayoutParams();
layoutParams.height = Utils.displayMetrics.widthPixels + 1;
viewToChangeHeight.setLayoutParams(layoutParams);
}
}
}
@Override
public int getItemCount() {
return feedModels == null ? 0 : feedModels.size();
}
@Override
public int getItemViewType(final int position) {
if (feedModels != null) return feedModels.get(position).getItemType().ordinal();
return MediaItemType.MEDIA_TYPE_IMAGE.ordinal();
}
/**
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation]
*
* @param textView the {@link RamboTextView} view, to expand and collapse
* @param feedModel the {@link FeedModel} model to check wether model is collapsed to expanded
*
* @return true if expanded/collapsed, false if empty or text size is <= 255 chars
*/
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, @NonNull final FeedModel feedModel) {
final CharSequence caption = feedModel.getPostCaption();
if (Utils.isEmpty(caption)) return false;
final TextView.BufferType bufferType = caption instanceof Spanned ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL;
if (!feedModel.isCaptionExpanded()) {
int i = Utils.indexOfChar(caption, '\r', 0);
if (i == -1) i = Utils.indexOfChar(caption, '\n', 0);
if (i == -1) i = 255;
final int captionLen = caption.length();
final int minTrim = Math.min(255, i);
if (captionLen <= minTrim) return false;
final CharSequence mentionText = caption.subSequence(0, Math.min(captionLen, minTrim));
final SpannableStringBuilder stringBuilder = new SpannableStringBuilder(mentionText).append(ellipsize);
final int spanLen = stringBuilder.length();
// fixed @mention...more merging into one span
final CommentMentionClickSpan[] spans = stringBuilder.getSpans(0, mentionText.length(), CommentMentionClickSpan.class);
if (spans != null) {
for (final CommentMentionClickSpan span : spans) {
final int spanStart = stringBuilder.getSpanStart(span);
stringBuilder.removeSpan(span);
stringBuilder.setSpan(span, spanStart, mentionText.length(), 0);
}
}
stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), spanLen - ellipsize.length(), spanLen, 0);
textView.setText(stringBuilder, bufferType);
textView.setCaptionIsExpandable(true);
textView.setCaptionIsExpanded(true);
} else {
textView.setText(caption, bufferType);
textView.setCaptionIsExpanded(false);
}
return true;
}
private interface PlayerChangeListener {
void playerChanged(final int childPos, final SimpleExoPlayer player);
}
private static final class ChildMediaItemsAdapter extends PagerAdapter {
private final PlayerChangeListener playerChangeListener;
private final View.OnClickListener muteClickListener;
private final ViewerPostModel[] sliderItems;
private final View btnMute;
private SimpleExoPlayer player;
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems, final View btnMute, final View.OnClickListener muteClickListener,
final PlayerChangeListener playerChangeListener) {
this.muteClickListener = muteClickListener;
this.sliderItems = sliderItems;
this.btnMute = btnMute;
if (BuildConfig.DEBUG) this.playerChangeListener = playerChangeListener;
else this.playerChangeListener = null;
}
@NonNull
@Override
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
if (BuildConfig.DEBUG) container.setBackgroundColor(0xFF_0a_c0_09); // todo remove
final Context context = container.getContext();
final ViewerPostModel sliderItem = sliderItems[position];
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
if (btnMute != null) btnMute.setVisibility(View.VISIBLE);
final PlayerView playerView = new PlayerView(context);
player = new SimpleExoPlayer.Builder(context).build();
playerView.setPlayer(player);
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram"))
.createMediaSource(Uri.parse(sliderItem.getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setVolume(vol);
playerView.setTag(player);
playerView.setOnClickListener(muteClickListener);
if (playerChangeListener != null) {
//todo
// playerChangeListener.playerChanged(position, player);
Log.d("AWAISKING_APP", "playerChangeListener: " + playerChangeListener);
}
container.addView(playerView);
return playerView;
} else {
if (btnMute != null) btnMute.setVisibility(View.GONE);
final PhotoView photoView = new PhotoView(context);
photoView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Glide.with(context).load(sliderItem.getDisplayUrl()).into(photoView);
container.addView(photoView);
return photoView;
}
}
@Override
public void destroyItem(@NonNull final ViewGroup container, final int position, @NonNull final Object object) {
final Player player = object instanceof PlayerView ? ((PlayerView) object).getPlayer() : this.player;
if (player == this.player && this.player != null) {
this.player.stop(true);
this.player.release();
} else if (player != null) {
player.stop(true);
player.release();
}
container.removeView((View) object);
}
@Override
public int getCount() {
return sliderItems != null ? sliderItems.length : 0;
}
@Override
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
return view == object;
}
}
}

View file

@ -0,0 +1,57 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.HighlightViewHolder;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.ProfileModel;
public final class FeedStoriesAdapter extends RecyclerView.Adapter<HighlightViewHolder> {
private final View.OnClickListener clickListener;
private LayoutInflater layoutInflater;
private FeedStoryModel[] feedStoryModels;
public FeedStoriesAdapter(final FeedStoryModel[] feedStoryModels, final View.OnClickListener clickListener) {
this.feedStoryModels = feedStoryModels;
this.clickListener = clickListener;
}
@NonNull
@Override
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false));
}
@Override
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) {
final FeedStoryModel feedStoryModel = feedStoryModels[position];
if (feedStoryModel != null) {
holder.itemView.setTag(feedStoryModel);
holder.itemView.setOnClickListener(clickListener);
final ProfileModel profileModel = feedStoryModel.getProfileModel();
holder.title.setText(profileModel.getUsername());
Glide.with(layoutInflater.getContext()).load(profileModel.getSdProfilePic()).into(holder.icon);
}
}
public void setData(final FeedStoryModel[] feedStoryModels) {
this.feedStoryModels = feedStoryModels;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return feedStoryModels == null ? 0 : feedStoryModels.length;
}
}

View file

@ -0,0 +1,144 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.FollowsViewHolder;
import awais.instagrabber.interfaces.OnGroupClickListener;
import awais.instagrabber.models.FollowModel;
import awais.instagrabber.utils.Utils;
import thoughtbot.expandableadapter.ExpandableGroup;
import thoughtbot.expandableadapter.ExpandableList;
import thoughtbot.expandableadapter.ExpandableListPosition;
import thoughtbot.expandableadapter.GroupViewHolder;
// thanks to ThoughtBot's ExpandableRecyclerViewAdapter
// https://github.com/thoughtbot/expandable-recycler-view
public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements OnGroupClickListener, Filterable {
private final Filter filter = new Filter() {
@Nullable
@Override
protected FilterResults performFiltering(final CharSequence filter) {
if (expandableList.groups != null) {
final boolean isFilterEmpty = Utils.isEmpty(filter);
final String query = isFilterEmpty ? null : filter.toString().toLowerCase();
for (int x = 0; x < expandableList.groups.size(); ++x) {
final ExpandableGroup expandableGroup = expandableList.groups.get(x);
final List<FollowModel> items = expandableGroup.getItems(false);
final int itemCount = expandableGroup.getItemCount(false);
for (int i = 0; i < itemCount; ++i) {
final FollowModel followModel = items.get(i);
if (isFilterEmpty) followModel.setShown(true);
else followModel.setShown(Utils.hasKey(query, followModel.getUsername(), followModel.getFullName()));
}
}
}
return null;
}
@Override
protected void publishResults(final CharSequence constraint, final FilterResults results) {
notifyDataSetChanged();
}
};
private final View.OnClickListener onClickListener;
private final LayoutInflater layoutInflater;
private final ExpandableList expandableList;
private final boolean hasManyGroups;
public FollowAdapter(final Context context, final View.OnClickListener onClickListener, @NonNull final ArrayList<ExpandableGroup> groups) {
this.layoutInflater = LayoutInflater.from(context);
this.expandableList = new ExpandableList(groups);
this.onClickListener = onClickListener;
this.hasManyGroups = groups.size() > 1;
}
@Override
public Filter getFilter() {
return filter;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final boolean isGroup = hasManyGroups && viewType == ExpandableListPosition.GROUP;
final View view = layoutInflater.inflate(isGroup ? R.layout.header_follow : R.layout.item_follow, parent, false);
return isGroup ? new GroupViewHolder(view, this) : new FollowsViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
final ExpandableListPosition listPos = expandableList.getUnflattenedPosition(position);
final ExpandableGroup group = expandableList.getExpandableGroup(listPos);
if (hasManyGroups && listPos.type == ExpandableListPosition.GROUP) {
final GroupViewHolder gvh = (GroupViewHolder) holder;
gvh.setTitle(group.getTitle());
gvh.toggle(isGroupExpanded(group));
} else {
final FollowModel model = group.getItems(true).get(hasManyGroups ? listPos.childPos : position);
final FollowsViewHolder followHolder = (FollowsViewHolder) holder;
if (model != null) {
followHolder.itemView.setTag(model);
followHolder.itemView.setOnClickListener(onClickListener);
followHolder.tvUsername.setText(model.getUsername());
followHolder.tvFullName.setText(model.getFullName());
Glide.with(layoutInflater.getContext()).load(model.getProfilePicUrl()).into(followHolder.profileImage);
}
}
}
@Override
public int getItemCount() {
return expandableList.getVisibleItemCount() - (hasManyGroups ? 0 : 1);
}
@Override
public int getItemViewType(final int position) {
return !hasManyGroups ? 0 : expandableList.getUnflattenedPosition(position).type;
}
@Override
public void toggleGroup(final int flatPos) {
final ExpandableListPosition listPosition = expandableList.getUnflattenedPosition(flatPos);
final int groupPos = listPosition.groupPos;
final int positionStart = expandableList.getFlattenedGroupIndex(listPosition) + 1;
final int positionEnd = expandableList.groups.get(groupPos).getItemCount(true);
final boolean isExpanded = expandableList.expandedGroupIndexes[groupPos];
expandableList.expandedGroupIndexes[groupPos] = !isExpanded;
notifyItemChanged(positionStart - 1);
if (positionEnd > 0) {
if (isExpanded) notifyItemRangeRemoved(positionStart, positionEnd);
else notifyItemRangeInserted(positionStart, positionEnd);
}
}
public boolean isGroupExpanded(final ExpandableGroup group) {
return expandableList.expandedGroupIndexes[expandableList.groups.indexOf(group)];
}
}

View file

@ -0,0 +1,53 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.HighlightViewHolder;
import awais.instagrabber.models.HighlightModel;
public final class HighlightsAdapter extends RecyclerView.Adapter<HighlightViewHolder> {
private final View.OnClickListener clickListener;
private LayoutInflater layoutInflater;
private HighlightModel[] highlightModels;
public HighlightsAdapter(final HighlightModel[] highlightModels, final View.OnClickListener clickListener) {
this.highlightModels = highlightModels;
this.clickListener = clickListener;
}
@NonNull
@Override
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false));
}
@Override
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) {
final HighlightModel highlightModel = highlightModels[position];
if (highlightModel != null) {
holder.itemView.setTag(highlightModel);
holder.itemView.setOnClickListener(clickListener);
holder.title.setText(highlightModel.getTitle());
Glide.with(holder.itemView).load(highlightModel.getThumbnailUrl()).into(holder.icon);
}
}
public void setData(final HighlightModel[] highlightModels) {
this.highlightModels = highlightModels;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return highlightModels == null ? 0 : highlightModels.length;
}
}

View file

@ -0,0 +1,354 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.text.Spanned;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.directmessages.TextMessageViewHolder;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemMediaModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemRavenMediaModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkContext;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemVoiceMediaModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.RavenExpiringMediaActionSummaryModel;
public final class MessageItemsAdapter extends RecyclerView.Adapter<TextMessageViewHolder> {
private static final int MESSAGE_INCOMING = 69, MESSAGE_OUTGOING = 420;
private final ProfileModel myProfileHolder = new ProfileModel(false, false, null, null, null, null, null, null, null, 0, 0, 0);
private final ArrayList<DirectItemModel> directItemModels;
private final ArrayList<ProfileModel> users;
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
// todo do profile stuff
final ProfileModel profileModel = (ProfileModel) tag;
Log.d("AWAISKING_APP", "--> " + profileModel);
}
};
private final int itemMargin;
private DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
if (v instanceof ViewGroup && tag instanceof DirectItemVoiceMediaModel) {
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
final DirectItemVoiceMediaModel voiceMediaModel = (DirectItemVoiceMediaModel) tag;
final boolean voicePlaying = voiceMediaModel.isPlaying();
voiceMediaModel.setPlaying(!voicePlaying);
if (voiceMediaModel == prevVoiceModel) {
// todo pause / resume
} else {
// todo release prev audio, start new voice
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
if (prevPlayIcon != null) prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
}
if (voicePlaying) {
playIcon.setImageResource(android.R.drawable.ic_media_play);
} else {
playIcon.setImageResource(android.R.drawable.ic_media_pause);
}
prevVoiceModel = voiceMediaModel;
prevPlayIcon = playIcon;
}
};
private Context context;
private LayoutInflater layoutInflater;
private String strDmYou;
public MessageItemsAdapter(final ArrayList<DirectItemModel> directItemModels, final ArrayList<ProfileModel> users,
final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) {
this.users = users;
this.directItemModels = directItemModels;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
}
@NonNull
@Override
public TextMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (context == null) context = parent.getContext();
if (strDmYou == null) strDmYou = context.getString(R.string.direct_messages_you);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
return new TextMessageViewHolder(layoutInflater.inflate(R.layout.item_message_item, parent, false),
onClickListener, mentionClickListener);
}
@Override
public void onBindViewHolder(@NonNull final TextMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = directItemModels.get(position);
holder.itemView.setTag(directItemModel);
if (directItemModel != null) {
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
type == MESSAGE_INCOMING ? itemMargin : 0, 0);
holder.tvMessage.setVisibility(View.GONE);
holder.voiceMessageContainer.setVisibility(View.GONE);
holder.ivAnimatedMessage.setVisibility(View.GONE);
holder.linkMessageContainer.setVisibility(View.GONE);
holder.mediaMessageContainer.setVisibility(View.GONE);
holder.mediaTypeIcon.setVisibility(View.GONE);
holder.mediaExpiredIcon.setVisibility(View.GONE);
holder.profileMessageContainer.setVisibility(View.GONE);
holder.isVerified.setVisibility(View.GONE);
holder.btnOpenProfile.setVisibility(View.GONE);
holder.btnOpenProfile.setOnClickListener(null);
holder.btnOpenProfile.setTag(null);
CharSequence text = "?";
if (user != null && user != myProfileHolder) text = user.getUsername();
else if (user == myProfileHolder) text = strDmYou;
text = text + " - " + directItemModel.getDateTime();
holder.tvUsername.setText(text);
holder.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (type == MESSAGE_INCOMING && user != null)
glideRequestManager.load(user.getSdProfilePic()).into(holder.ivProfilePic);
DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
switch (itemType) {
case PLACEHOLDER:
case TEXT:
text = directItemModel.getText();
text = Utils.getSpannableUrl(text.toString()); // for urls
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions
if (text instanceof Spanned) holder.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
else if (text == "") holder.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
else holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
break;
case LINK: {
final DirectItemLinkModel link = directItemModel.getLinkModel();
final DirectItemLinkContext linkContext = link.getLinkContext();
final String linkImageUrl = linkContext.getLinkImageUrl();
if (!Utils.isEmpty(linkImageUrl)) {
glideRequestManager.load(linkImageUrl).into(holder.ivLinkPreview);
holder.tvLinkTitle.setText(linkContext.getLinkTitle());
holder.tvLinkSummary.setText(linkContext.getLinkSummary());
holder.ivLinkPreview.setVisibility(View.VISIBLE);
holder.linkMessageContainer.setVisibility(View.VISIBLE);
}
holder.tvMessage.setText(Utils.getSpannableUrl(link.getText()));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
case MEDIA_SHARE: {
final ProfileModel modelUser = mediaModel.getUser();
if (modelUser != null) {
holder.tvMessage.setText(context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername()));
holder.tvMessage.setVisibility(View.VISIBLE);
}
}
case MEDIA: {
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
final MediaItemType modelMediaType = mediaModel.getMediaType();
holder.mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case RAVEN_MEDIA: {
final DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
final RavenExpiringMediaActionSummaryModel mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
mediaModel = ravenMediaModel.getMedia();
final boolean isExpired = mediaModel == null ||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
holder.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
int textRes = R.string.dms_inbox_raven_media_unknown;
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
if (!isExpired && mediaActionSummary != null) {
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
textRes = R.string.dms_inbox_raven_media_delivered;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
textRes = R.string.dms_inbox_raven_media_sent;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
textRes = R.string.dms_inbox_raven_media_opened;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
textRes = R.string.dms_inbox_raven_media_replayed;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
textRes = R.string.dms_inbox_raven_media_sending;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
textRes = R.string.dms_inbox_raven_media_blocked;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
textRes = R.string.dms_inbox_raven_media_suggested;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
textRes = R.string.dms_inbox_raven_media_screenshot;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
textRes = R.string.dms_inbox_raven_media_cant_deliver;
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
final MediaItemType mediaType = mediaModel.getMediaType();
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
holder.tvMessage.setText(context.getText(textRes));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
case REEL_SHARE: {
final DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (!Utils.isEmpty(text = reelShare.getText())) {
holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
Log.d("austin_debug", "media: " + reelShareMedia);
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VOICE_MEDIA: {
final DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
if (voiceMediaModel != null) {
final int[] waveformData = voiceMediaModel.getWaveformData();
if (waveformData != null) holder.waveformSeekBar.setSample(waveformData);
final long durationMs = voiceMediaModel.getDurationMs();
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs));
holder.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
holder.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
// todo progress audio player
voiceMediaModel.setProgress(progress);
if (fromUser)
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100));
});
holder.btnPlayVoice.setTag(voiceMediaModel);
holder.btnPlayVoice.setOnClickListener(voicePlayClickListener);
} else {
holder.waveformSeekBar.setProgress(0);
}
holder.voiceMessageContainer.setVisibility(View.VISIBLE);
}
break;
case ANIMATED_MEDIA: {
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl())
.into(holder.ivAnimatedMessage);
holder.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
break;
case PROFILE: {
final ProfileModel profileModel = directItemModel.getProfileModel();
Glide.with(holder.ivMessageProfilePic).load(profileModel.getSdProfilePic())
.into(holder.ivMessageProfilePic);
holder.btnOpenProfile.setTag(profileModel);
holder.btnOpenProfile.setOnClickListener(openProfileClickListener);
holder.tvProfileName.setText(profileModel.getName());
holder.tvProfileUsername.setText(profileModel.getUsername());
holder.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
holder.btnOpenProfile.setVisibility(View.VISIBLE);
holder.profileMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VIDEO_CALL_EVENT: {
// todo add call event info
holder.tvMessage.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(0xFF_1F90E6); // blue bitch
}
break;
}
}
}
@Override
public int getItemViewType(final int position) {
return directItemModels.get(position).getItemType().ordinal();
}
@Override
public int getItemCount() {
return directItemModels == null ? 0 : directItemModels.size();
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
for (final ProfileModel user : users)
if (Long.toString(userId).equals(user.getId())) return user;
return myProfileHolder;
}
return null;
}
}

View file

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

View file

@ -0,0 +1,68 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.PostMediaViewHolder;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.ViewerPostModel;
public final class PostsMediaAdapter extends RecyclerView.Adapter<PostMediaViewHolder> {
private final View.OnClickListener clickListener;
private LayoutInflater layoutInflater;
private ViewerPostModel[] postModels;
public PostsMediaAdapter(final ViewerPostModel[] postModels, final View.OnClickListener clickListener) {
this.postModels = postModels;
this.clickListener = clickListener;
}
@NonNull
@Override
public PostMediaViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new PostMediaViewHolder(layoutInflater.inflate(R.layout.item_child_post, parent, false));
}
@Override
public void onBindViewHolder(@NonNull final PostMediaViewHolder holder, final int position) {
final ViewerPostModel postModel = postModels[position];
if (postModel != null) {
postModel.setPosition(position);
holder.itemView.setTag(postModel);
holder.itemView.setOnClickListener(clickListener);
holder.selectedView.setVisibility(postModel.isCurrentSlide() ? View.VISIBLE : View.GONE);
holder.isDownloaded.setVisibility(postModel.isDownloaded() ? View.VISIBLE : View.GONE);
Glide.with(layoutInflater.getContext()).load(postModel.getSliderDisplayUrl()).into(holder.icon);
}
}
public void setData(final ViewerPostModel[] postModels) {
this.postModels = postModels;
notifyDataSetChanged();
}
public ViewerPostModel getItemAt(final int position) {
return postModels == null ? null : postModels[position];
}
@Override
public int getItemCount() {
return postModels == null ? 0 : postModels.length;
}
public BasePostModel[] getPostModels() {
return postModels;
}
}

View file

@ -0,0 +1,75 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.utils.DataBox;
public final class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> {
private List<T> items;
private final LayoutInflater layoutInflater;
private final View.OnClickListener onClickListener;
private final View.OnLongClickListener longClickListener;
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener) {
this(context, items, onClickListener, null);
}
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
this.layoutInflater = LayoutInflater.from(context);
this.items = items;
this.onClickListener = onClickListener;
this.longClickListener = longClickListener;
}
public void setItems(final List<T> items) {
this.items = items;
notifyDataSetChanged();
}
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
return new SimpleViewHolder(layoutInflater.
inflate(R.layout.item_dir_list, parent, false), onClickListener, longClickListener);
}
@Override
public void onBindViewHolder(@NonNull final SimpleViewHolder holder, final int position) {
final T item = items.get(position);
holder.itemView.setTag(item);
holder.text.setText(item.toString());
if (item instanceof DataBox.CookieModel && ((DataBox.CookieModel) item).isSelected() ||
item instanceof String && ((String) item).toLowerCase().endsWith(".zaai"))
holder.itemView.setBackgroundColor(0xF0_125687);
else
holder.itemView.setBackground(null);
}
@Override
public int getItemCount() {
return items != null ? items.size() : 0;
}
static final class SimpleViewHolder extends RecyclerView.ViewHolder {
private final TextView text;
private SimpleViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
super(itemView);
text = itemView.findViewById(android.R.id.text1);
itemView.setOnClickListener(onClickListener);
if (longClickListener != null) itemView.setOnLongClickListener(longClickListener);
}
}
}

View file

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

View file

@ -0,0 +1,60 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.CursorAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.request.RequestOptions;
import awais.instagrabber.R;
public final class SuggestionsAdapter extends CursorAdapter {
private final LayoutInflater layoutInflater;
private final View.OnClickListener onClickListener;
private final RequestManager glideRequestManager;
public SuggestionsAdapter(final Context context, final View.OnClickListener onClickListener) {
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER);
this.glideRequestManager = Glide.with(context);
this.layoutInflater = LayoutInflater.from(context);
this.onClickListener = onClickListener;
}
@Override
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
return layoutInflater.inflate(R.layout.item_suggestion, parent, false);
}
@Override
public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) {
// i, username, fullname, type, picUrl, verified
// 0, 1 , 2 , 3 , 4 , 5
final String fullname = cursor.getString(2);
String username = cursor.getString(1);
final String picUrl = cursor.getString(4);
final boolean verified = cursor.getString(5).charAt(0) == 't';
if ("TYPE_HASHTAG".equals(cursor.getString(3))) username = '#' + username;
view.setOnClickListener(onClickListener);
view.setTag(username);
view.findViewById(R.id.isVerified).setVisibility(verified ? View.VISIBLE : View.GONE);
((TextView) view.findViewById(R.id.tvUsername)).setText(username);
((TextView) view.findViewById(R.id.tvFullName)).setText(fullname);
glideRequestManager.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true))
.load(picUrl).into((ImageView) view.findViewById(R.id.ivProfilePic));
}
}

View file

@ -0,0 +1,85 @@
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.CommentModel;
public final class CommentViewHolder extends RecyclerView.ViewHolder {
private final MentionClickListener mentionClickListener;
private final RecyclerView rvChildComments;
private final ImageView ivProfilePic;
private final TextView tvUsername, tvDate, tvComment, tvLikes;
private final View container;
public CommentViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) {
super(itemView);
container = itemView.findViewById(R.id.container);
if (onClickListener != null) container.setOnClickListener(onClickListener);
this.mentionClickListener = mentionClickListener;
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
tvUsername = itemView.findViewById(R.id.tvUsername);
tvDate = itemView.findViewById(R.id.tvDate);
tvLikes = itemView.findViewById(R.id.tvLikes);
tvComment = itemView.findViewById(R.id.tvComment);
tvUsername.setSelected(true);
tvDate.setSelected(true);
rvChildComments = itemView.findViewById(R.id.rvChildComments);
}
public final ImageView getProfilePicView() {
return ivProfilePic;
}
public final boolean isParent() {
return rvChildComments != null;
}
public final void setCommentModel(final CommentModel commentModel) {
if (container != null) container.setTag(commentModel);
}
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 setLikes(final String likes) {
if (tvLikes != null) tvLikes.setText(likes);
}
public final void setCommment(final CharSequence commment) {
if (tvComment != null) {
tvComment.setText(commment, commment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL);
((RamboTextView) tvComment).setMentionClickListener(mentionClickListener);
}
}
public final void setChildAdapter(final CommentsAdapter adapter) {
if (isParent()) {
rvChildComments.setAdapter(adapter);
rvChildComments.setVisibility(View.VISIBLE);
}
}
public final void hideChildComments() {
if (isParent()) rvChildComments.setVisibility(View.GONE);
}
}

View file

@ -0,0 +1,43 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
public final LinearLayout multipleProfilePicsContainer;
public final ImageView[] multipleProfilePics;
public final ImageView ivProfilePic, notTextType;
public final TextView tvUsername, tvDate, tvMessage;
public DirectMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
itemView.findViewById(R.id.tvLikes).setVisibility(View.GONE);
tvDate = itemView.findViewById(R.id.tvDate);
tvMessage = itemView.findViewById(R.id.tvComment);
tvUsername = itemView.findViewById(R.id.tvUsername);
notTextType = itemView.findViewById(R.id.notTextType);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
multipleProfilePicsContainer = itemView.findViewById(R.id.container);
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
multipleProfilePics = new ImageView[]{
(ImageView) multipleProfilePicsContainer.getChildAt(0),
(ImageView) containerChild.getChildAt(0),
(ImageView) containerChild.getChildAt(1)
};
tvDate.setSelected(true);
tvUsername.setSelected(true);
}
}

View file

@ -0,0 +1,22 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
public final ImageView postImage, typeIcon;
public final View selectedView, progressView;
public DiscoverViewHolder(@NonNull final View itemView) {
super(itemView);
typeIcon = itemView.findViewById(R.id.typeIcon);
postImage = itemView.findViewById(R.id.postImage);
selectedView = itemView.findViewById(R.id.selectedView);
progressView = itemView.findViewById(R.id.progressView);
}
}

View file

@ -0,0 +1,52 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import com.github.chrisbanes.photoview.PhotoView;
import com.google.android.exoplayer2.ui.PlayerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.RamboTextView;
public final class FeedItemViewHolder extends RecyclerView.ViewHolder {
public final ImageView profilePic, btnMute, btnDownload;
public final TextView username, commentsCount, videoViews, mediaCounter, tvPostDate;
public final RamboTextView viewerCaption;
public final View btnComments, videoViewsParent, viewPost;
public final ViewPager mediaList;
public final PhotoView imageView;
public final PlayerView playerView;
public FeedItemViewHolder(@NonNull final View itemView) {
super(itemView);
// common
viewerCaption = itemView.findViewById(R.id.viewerCaption);
btnDownload = itemView.findViewById(R.id.btnDownload);
btnComments = itemView.findViewById(R.id.btnComments);
profilePic = itemView.findViewById(R.id.ivProfilePic);
tvPostDate = itemView.findViewById(R.id.tvPostDate);
viewPost = itemView.findViewById(R.id.viewStoryPost);
username = itemView.findViewById(R.id.title);
// video view
btnMute = itemView.findViewById(R.id.btnMute);
videoViews = itemView.findViewById(R.id.tvVideoViews);
commentsCount = btnComments.findViewById(R.id.commentsCount);
videoViewsParent = videoViews != null ? (View) videoViews.getParent() : null;
// slider view
mediaCounter = itemView.findViewById(R.id.mediaCounter);
// different types
mediaList = itemView.findViewById(R.id.media_list);
imageView = itemView.findViewById(R.id.imageViewer);
playerView = itemView.findViewById(R.id.playerView);
}
}

View file

@ -0,0 +1,22 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class FollowsViewHolder extends RecyclerView.ViewHolder {
public final ImageView profileImage;
public final TextView tvFullName, tvUsername;
public FollowsViewHolder(@NonNull final View itemView) {
super(itemView);
profileImage = itemView.findViewById(R.id.ivProfilePic);
tvFullName = itemView.findViewById(R.id.tvFullName);
tvUsername = itemView.findViewById(R.id.tvUsername);
}
}

View file

@ -0,0 +1,21 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class HighlightViewHolder extends RecyclerView.ViewHolder {
public final ImageView icon;
public final TextView title;
public HighlightViewHolder(@NonNull final View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.icon);
title = itemView.findViewById(R.id.title);
}
}

View file

@ -0,0 +1,20 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class PostMediaViewHolder extends RecyclerView.ViewHolder {
public final ImageView icon, isDownloaded, selectedView;
public PostMediaViewHolder(@NonNull final View itemView) {
super(itemView);
selectedView = itemView.findViewById(R.id.selectedView);
isDownloaded = itemView.findViewById(R.id.isDownloaded);
icon = itemView.findViewById(R.id.icon);
}
}

View file

@ -0,0 +1,23 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class PostViewHolder extends RecyclerView.ViewHolder {
public final ImageView postImage, typeIcon;
public final View selectedView, progressView, isDownloaded;
public PostViewHolder(@NonNull final View itemView) {
super(itemView);
typeIcon = itemView.findViewById(R.id.typeIcon);
postImage = itemView.findViewById(R.id.postImage);
isDownloaded = itemView.findViewById(R.id.isDownloaded);
selectedView = itemView.findViewById(R.id.selectedView);
progressView = itemView.findViewById(R.id.progressView);
}
}

View file

@ -0,0 +1,91 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.masoudss_waveform.WaveformSeekBar;
import awais.instagrabber.interfaces.MentionClickListener;
public final class TextMessageViewHolder extends RecyclerView.ViewHolder {
public final CardView rootCardView;
public final TextView tvUsername;
public final ImageView ivProfilePic;
// text message
public final RamboTextView tvMessage;
// expired message icon
public final View mediaExpiredIcon;
// media message
public final View mediaMessageContainer;
public final ImageView ivMediaPreview, mediaTypeIcon;
// profile messag
public final View profileMessageContainer, isVerified, btnOpenProfile;
public final TextView tvProfileUsername, tvProfileName;
public final ImageView ivMessageProfilePic;
// animated message
public final ImageView ivAnimatedMessage;
// link message
public final View linkMessageContainer;
public final ImageView ivLinkPreview;
public final TextView tvLinkTitle, tvLinkSummary;
// voice message
public final View voiceMessageContainer, btnPlayVoice;
public final WaveformSeekBar waveformSeekBar;
public final TextView tvVoiceDuration;
public TextMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
tvUsername = itemView.findViewById(R.id.tvUsername);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
// text message
tvMessage = itemView.findViewById(R.id.tvMessage);
tvMessage.setCaptionIsExpandable(true);
tvMessage.setCaptionIsExpanded(true);
if (mentionClickListener != null) tvMessage.setMentionClickListener(mentionClickListener);
// root view
rootCardView = (CardView) tvMessage.getParent().getParent();
// expired message icon
mediaExpiredIcon = itemView.findViewById(R.id.mediaExpiredIcon);
// media message
ivMediaPreview = itemView.findViewById(R.id.ivMediaPreview);
mediaMessageContainer = (View) ivMediaPreview.getParent();
mediaTypeIcon = mediaMessageContainer.findViewById(R.id.typeIcon);
// profile message
btnOpenProfile = itemView.findViewById(R.id.btnInfo);
ivMessageProfilePic = itemView.findViewById(R.id.profileInfo);
profileMessageContainer = (View) ivMessageProfilePic.getParent();
isVerified = profileMessageContainer.findViewById(R.id.isVerified);
tvProfileName = profileMessageContainer.findViewById(R.id.tvFullName);
tvProfileUsername = profileMessageContainer.findViewById(R.id.profileInfoText);
// animated message
ivAnimatedMessage = itemView.findViewById(R.id.ivAnimatedMessage);
// link message
ivLinkPreview = itemView.findViewById(R.id.ivLinkPreview);
linkMessageContainer = (View) ivLinkPreview.getParent();
tvLinkTitle = linkMessageContainer.findViewById(R.id.tvLinkTitle);
tvLinkSummary = linkMessageContainer.findViewById(R.id.tvLinkSummary);
// voice message
waveformSeekBar = itemView.findViewById(R.id.waveformSeekBar);
voiceMessageContainer = (View) waveformSeekBar.getParent();
btnPlayVoice = voiceMessageContainer.findViewById(R.id.btnPlayVoice);
tvVoiceDuration = voiceMessageContainer.findViewById(R.id.tvVoiceDuration);
}
}

View file

@ -0,0 +1,265 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]> {
private final String shortCode;
private final FetchListener<CommentModel[]> fetchListener;
/*
* i fucking spent the whole day on this and fixing all the fucking problems in this class.
* DO NO FUCK WITH THIS CODE!
* -AWAiS (The Badak) @the.badak
*/
public CommentsFetcher(final String shortCode, final FetchListener<CommentModel[]> fetchListener) {
this.shortCode = shortCode;
this.fetchListener = fetchListener;
}
@NonNull
@Override
protected CommentModel[] doInBackground(final Void... voids) {
/*
"https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + "{\"shortcode\":\"" + shortcode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
97b41c52301f77ce508f55e66d17620e -> for comments
51fdd02b67508306ad4484ff574a0b62 -> for child comments
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
*/
final ArrayList<CommentModel> commentModels = getParentComments();
for (final CommentModel commentModel : commentModels) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.length;
final CommentModel lastChild = childCommentModels[childCommentsLen - 1];
if (lastChild != null && lastChild.hasNextPage() && !Utils.isEmpty(lastChild.getEndCursor())) {
final CommentModel[] remoteChildComments = getChildComments(commentModel.getId());
commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null);
}
}
}
return commentModels.toArray(new CommentModel[0]);
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final CommentModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
@NonNull
private synchronized CommentModel[] getChildComments(final String commentId) {
final ArrayList<CommentModel> commentModels = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else {
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("comment").getJSONObject("edge_threaded_comments");
final JSONObject pageInfo = data.getJSONObject("page_info");
endCursor = pageInfo.getString("end_cursor");
if (Utils.isEmpty(endCursor)) endCursor = null;
final JSONArray childComments = data.optJSONArray("edges");
if (childComments != null) {
final int length = childComments.length();
for (int i = 0; i < length; ++i) {
final JSONObject childComment = childComments.getJSONObject(i).optJSONObject("node");
if (childComment != null) {
final JSONObject owner = childComment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false,
false,
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null, null, null,
owner.getString("profile_pic_url"),
null, 0, 0, 0);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
profileModel));
}
}
}
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getChildComments",
new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
break;
}
}
return commentModels.toArray(new CommentModel[0]);
}
@NonNull
private synchronized ArrayList<CommentModel> getParentComments() {
final ArrayList<CommentModel> commentModelsList = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" +
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else {
final JSONObject parentComments = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("shortcode_media").getJSONObject("edge_media_to_parent_comment");
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
endCursor = pageInfo.optString("end_cursor");
if (Utils.isEmpty(endCursor)) endCursor = null;
// final boolean containsToken = endCursor.contains("bifilter_token");
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) {
// final JSONObject endCursorObject = new JSONObject(endCursor);
// endCursor = endCursorObject.optString("cached_comments_cursor");
//
// if (!Utils.isEmpty(endCursor))
// endCursor = "{\\\"cached_comments_cursor\\\": \\\"" + endCursor + "\\\", ";
// else
// endCursor = "{";
//
// endCursor = endCursor + "\\\"bifilter_token\\\": \\\"" + endCursorObject.getString("bifilter_token") + "\\\"}";
// }
// else if (containsToken) endCursor = null;
final JSONArray comments = parentComments.getJSONArray("edges");
final int commentsLen = comments.length();
final CommentModel[] commentModels = new CommentModel[commentsLen];
for (int i = 0; i < commentsLen; ++i) {
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
final JSONObject owner = comment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false,
owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
null, null, null,
owner.getString("profile_pic_url"),
null, 0, 0, 0);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
final String commentId = comment.getString(Constants.EXTRAS_ID);
commentModels[i] = new CommentModel(commentId,
comment.getString("text"),
comment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0,
profileModel);
JSONObject tempJsonObject;
final JSONArray childCommentsArray;
final int childCommentsLen;
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null &&
(childCommentsArray = tempJsonObject.optJSONArray("edges")) != null
&& (childCommentsLen = childCommentsArray.length()) > 0) {
final String childEndCursor;
final boolean hasNextPage;
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) {
childEndCursor = tempJsonObject.optString("end_cursor");
hasNextPage = tempJsonObject.optBoolean("has_next_page", !Utils.isEmpty(childEndCursor));
} else {
childEndCursor = null;
hasNextPage = false;
}
final CommentModel[] childCommentModels = new CommentModel[childCommentsLen];
for (int j = 0; j < childCommentsLen; ++j) {
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
tempJsonObject = childComment.getJSONObject("owner");
final ProfileModel childProfileModel = new ProfileModel(false, false,
tempJsonObject.getString(Constants.EXTRAS_ID),
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
null, null, null,
tempJsonObject.getString("profile_pic_url"),
null, 0, 0, 0);
tempJsonObject = childComment.optJSONObject("edge_liked_by");
childCommentModels[j] = new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
childComment.getLong("created_at"),
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
childProfileModel);
}
childCommentModels[childCommentsLen - 1].setPageCursor(hasNextPage, childEndCursor);
commentModels[i].setChildCommentModels(childCommentModels);
}
}
Collections.addAll(commentModelsList, commentModels);
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
new Pair<>("commentModelsList.size", commentModelsList.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
break;
}
}
return commentModelsList;
}
}

View file

@ -0,0 +1,194 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemModel[]> {
private final String maxId;
private final FetchListener<DiscoverItemModel[]> fetchListener;
private int lastId = 0;
private boolean isFirst, moreAvailable;
private String nextMaxId;
public DiscoverFetcher(final String maxId, final FetchListener<DiscoverItemModel[]> fetchListener, final boolean isFirst) {
this.maxId = maxId == null ? "" : "&max_id=" + maxId;
this.fetchListener = fetchListener;
this.isFirst = isFirst;
}
@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);
if (discoverItemModels != null) {
result = discoverItemModels.toArray(new DiscoverItemModel[0]);
if (result.length > 0) {
final DiscoverItemModel lastModel = result[result.length - 1];
if (lastModel != null) lastModel.setMore(moreAvailable, nextMaxId);
}
}
return result;
}
private ArrayList<DiscoverItemModel> fetchItems(final File downloadDir, final File customDir,
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;
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Instagram 72.0.0.21.98 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 132081645)");
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject discoverResponse = new JSONObject(Utils.readFromConnection(urlConnection));
moreAvailable = discoverResponse.getBoolean("more_available");
nextMaxId = discoverResponse.getString("next_max_id");
final JSONArray sectionalItems = discoverResponse.getJSONArray("sectional_items");
if (discoverItemModels == null) discoverItemModels = new ArrayList<>(sectionalItems.length() * 2);
for (int i = 0; i < sectionalItems.length(); ++i) {
final JSONObject sectionItem = sectionalItems.getJSONObject(i);
final String feedType = sectionItem.getString("feed_type");
final String layoutType = sectionItem.getString("layout_type");
if (sectionItem.has("layout_content") && feedType.equals("media")) {
final JSONObject layoutContent = sectionItem.getJSONObject("layout_content");
if ("media_grid".equals(layoutType)) {
final JSONArray medias = layoutContent.getJSONArray("medias");
for (int j = 0; j < medias.length(); ++j)
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir,
medias.getJSONObject(j).getJSONObject("media")));
} else {
final boolean isOneSide = "one_by_two_left".equals(layoutType);
if (isOneSide || "two_by_two_right".equals(layoutType)) {
final JSONObject layoutItem = layoutContent.getJSONObject(isOneSide ? "one_by_two_item" : "two_by_two_item");
if (layoutItem.has("media"))
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir,
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.trimToSize();
urlConnection.disconnect();
// hack to fetch 50+ items
if (this.isFirst) {
final int size = discoverItemModels.size();
if (size > 50) this.isFirst = false;
discoverItemModels = fetchItems(downloadDir, customDir, discoverItemModels,
"&max_id=" + (lastId++));
}
} else {
urlConnection.disconnect();
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DISCOVER_FETCHER, "fetchItems",
new Pair<>("maxId", maxId),
new Pair<>("lastId", lastId),
new Pair<>("isFirst", isFirst),
new Pair<>("nextMaxId", nextMaxId));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return discoverItemModels;
}
@NonNull
private DiscoverItemModel makeDiscoverModel(final File downloadDir, final File customDir,
@NonNull final JSONObject media) throws Exception {
final JSONObject user = media.getJSONObject(Constants.EXTRAS_USER);
final String username = user.getString(Constants.EXTRAS_USERNAME);
// final ProfileModel userProfileModel = new ProfileModel(user.getBoolean("is_private"),
// user.getBoolean("is_verified"),
// String.valueOf(user.get("pk")),
// username,
// user.getString("full_name"),
// null,
// user.getString("profile_pic_url"), null,
// 0, 0, 0);
// final String comment;
// if (!media.has("caption")) comment = null;
// else {
// final Object caption = media.get("caption");
// comment = caption instanceof JSONObject ? ((JSONObject) caption).getString("text") : null;
// }
final MediaItemType mediaType = Utils.getMediaItemType(media.getInt("media_type"));
final DiscoverItemModel model = new DiscoverItemModel(mediaType,
media.getString(Constants.EXTRAS_ID),
media.getString("code"),
Utils.getThumbnailUrl(media, mediaType));
Utils.checkExistence(downloadDir, customDir, username,
mediaType == MediaItemType.MEDIA_TYPE_SLIDER, -1, model);
return model;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final DiscoverItemModel[] discoverItemModels) {
if (fetchListener != null) fetchListener.onResult(discoverItemModels);
}
}

View file

@ -0,0 +1,248 @@
package awais.instagrabber.asyncs;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.FileProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.atomic.AtomicReference;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.CHANNEL_ID;
import static awais.instagrabber.utils.Utils.CHANNEL_NAME;
import static awais.instagrabber.utils.Utils.NOTIF_GROUP_NAME;
import static awais.instagrabber.utils.Utils.isChannelCreated;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.notificationManager;
import static awaisomereport.LogCollector.LogFile;
public final class DownloadAsync extends AsyncTask<Void, Float, File> {
private static int lastNotifId = 1;
private final int currentNotifId;
private final AtomicReference<Context> context;
private final File outFile;
private final String url;
private final FetchListener<File> fetchListener;
private final Resources resources;
private final NotificationCompat.Builder downloadNotif;
private String shortCode, username;
public DownloadAsync(final Context context, final String url, final File outFile, final FetchListener<File> fetchListener) {
this.context = new AtomicReference<>(context);
this.resources = context.getResources();
this.url = url;
this.outFile = outFile;
this.fetchListener = fetchListener;
this.shortCode = this.username = resources.getString(R.string.downloader_started);
this.currentNotifId = ++lastNotifId;
if (++lastNotifId + 1 == Integer.MAX_VALUE) lastNotifId = 1;
if (notificationManager == null)
notificationManager = NotificationManagerCompat.from(context.getApplicationContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isChannelCreated) {
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
isChannelCreated = true;
}
@StringRes final int titleRes = context instanceof ProfileViewer ? R.string.downloader_downloading_pfp : R.string.downloader_downloading_post;
downloadNotif = new NotificationCompat.Builder(context, CHANNEL_ID).setCategory(NotificationCompat.CATEGORY_STATUS)
.setSmallIcon(R.mipmap.ic_launcher).setContentText(shortCode == null ? username : shortCode).setOngoing(true)
.setProgress(100, 0, false).setAutoCancel(false).setOnlyAlertOnce(true)
.setContentTitle(resources.getString(titleRes));
notificationManager.notify(currentNotifId, downloadNotif.build());
}
public DownloadAsync setItems(final String shortCode, final String username) {
this.shortCode = shortCode;
this.username = username;
if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode);
return this;
}
@Nullable
@Override
protected File doInBackground(final Void... voids) {
try {
final URLConnection urlConnection = new URL(url).openConnection();
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() :
urlConnection.getContentLength();
float totalRead = 0;
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream());
final FileOutputStream fos = new FileOutputStream(outFile)) {
final byte[] buffer = new byte[0x2000];
int count;
boolean deletedIPTC = false;
while ((count = bis.read(buffer, 0, 0x2000)) != -1) {
totalRead = totalRead + count;
if (!deletedIPTC) {
int iptcStart = -1;
int fbmdStart = -1;
int fbmdBytesLen = -1;
for (int i = 0; i < buffer.length; ++i) {
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED)
iptcStart = i;
else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B'
&& buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') {
fbmdStart = i;
fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 |
(buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) |
(buffer[i - 6] & 0xFF);
break;
}
}
if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) {
final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4;
fos.write(buffer, 0, iptcStart);
fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart);
publishProgress(totalRead * 100f / fileSize);
deletedIPTC = true;
continue;
}
}
fos.write(buffer, 0, count);
publishProgress(totalRead * 100f / fileSize);
}
fos.flush();
}
return outFile;
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground",
new Pair<>("context", context.get()),
new Pair<>("resources", resources),
new Pair<>("lastNotifId", lastNotifId),
new Pair<>("downloadNotif", downloadNotif),
new Pair<>("currentNotifId", currentNotifId),
new Pair<>("notificationManager", notificationManager));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return null;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onProgressUpdate(@NonNull final Float... values) {
if (downloadNotif != null) {
downloadNotif.setProgress(100, values[0].intValue(), false);
notificationManager.notify(currentNotifId, downloadNotif.build());
}
}
@Override
protected void onPostExecute(final File result) {
if (result != null) {
final Context context = this.context.get();
context.sendBroadcast(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ?
new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())) :
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile()))
);
MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null);
if (notificationManager != null) {
final Uri uri = FileProvider.getUriForFile(context, "awais.instagrabber.provider", result);
final ContentResolver contentResolver = context.getContentResolver();
Bitmap bitmap = null;
if (Utils.isImage(uri, contentResolver)) {
try {
bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri));
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
if (bitmap == null) {
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
try {
retriever.setDataSource(context, uri);
} catch (final Exception e) {
retriever.setDataSource(result.getAbsolutePath());
}
bitmap = retriever.getFrameAtTime();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
try {
retriever.close();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2");
}
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3");
}
}
final String downloadComplete = resources.getString(R.string.downloader_complete);
downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false)
.setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true)
.setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent(
PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT));
if (bitmap != null)
downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap))
.setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL);
notificationManager.cancel(currentNotifId);
notificationManager.notify(currentNotifId + 1, downloadNotif.build());
}
}
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,194 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts, setting more than 30 is gay
private final String endCursor;
private final FetchListener<FeedModel[]> fetchListener;
public FeedFetcher(final FetchListener<FeedModel[]> fetchListener) {
this.endCursor = "";
this.fetchListener = fetchListener;
}
public FeedFetcher(final String endCursor, final FetchListener<FeedModel[]> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected final FeedModel[] doInBackground(final Void... voids) {
FeedModel[] result = null;
try {
//
// stories: 04334405dbdef91f2c4e207b84c204d7 && https://i.instagram.com/api/v1/feed/reels_tray/
// https://www.instagram.com/graphql/query/?query_hash=04334405dbdef91f2c4e207b84c204d7&variables={"only_stories":true,"stories_prefetch":false,"stories_video_dash_manifest":false}
// ///////////////////////////////////////////////
// feed:
// https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=
// {"cached_feed_item_ids":[],"fetch_media_item_count":12,"fetch_media_item_cursor":"<end_cursor>","fetch_comment_count":4,"fetch_like":3,"has_stories":false,"has_threaded_comments":true}
// only used: fetch_media_item_cursor, fetch_media_item_count: 100 (max 50), has_threaded_comments = true
// //////////////////////////////////////////////
// more unknowns: https://github.com/qsniyg/rssit/blob/master/rssit/generators/instagram.py
//
final String url = "https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=" +
"{\"fetch_media_item_count\":" + maxItemsToLoad + ",\"has_threaded_comments\":true,\"fetch_media_item_cursor\":\"" + endCursor + "\"}";
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject timelineFeed = new JSONObject(Utils.readFromConnection(urlConnection)).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_web_feed_timeline");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
final int feedLen = feedItems.length();
final ArrayList<FeedModel> feedModelsList = new ArrayList<>(feedLen);
for (int i = 0; i < feedLen; ++i) {
final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node");
final String mediaType = feedItem.optString("__typename");
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) continue;
final boolean isVideo = feedItem.optBoolean("is_video");
final long videoViews = feedItem.optLong("video_view_count", 0);
final String displayUrl = feedItem.getString("display_url");
final String resourceUrl;
if (isVideo) resourceUrl = feedItem.getString("video_url");
else resourceUrl = feedItem.has("display_resources") ? Utils.getHighQualityImage(feedItem) : displayUrl;
ProfileModel profileModel = null;
if (feedItem.has("owner")) {
final JSONObject owner = feedItem.getJSONObject("owner");
profileModel = new ProfileModel(owner.optBoolean("is_private"),
owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_ID),
owner.getString(Constants.EXTRAS_USERNAME),
owner.optString("full_name"),
null, null,
owner.getString("profile_pic_url"),
null, 0, 0, 0);
}
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment");
final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0;
tempJsonObject = feedItem.optJSONObject("edge_media_to_caption");
final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null;
String captionText = null;
if (captions != null && captions.length() > 0) {
if ((tempJsonObject = captions.optJSONObject(0)) != null &&
(tempJsonObject = tempJsonObject.optJSONObject("node")) != null)
captionText = tempJsonObject.getString("text");
}
final FeedModel feedModel = new FeedModel(profileModel,
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
videoViews,
feedItem.getString(Constants.EXTRAS_ID),
resourceUrl,
displayUrl,
feedItem.getString(Constants.EXTRAS_SHORTCODE),
captionText,
commentsCount,
feedItem.optLong("taken_at_timestamp", -1));
final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children");
if (isSlider) {
final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children");
if (sidecar != null) {
final JSONArray children = sidecar.optJSONArray("edges");
if (children != null) {
final ViewerPostModel[] sliderItems = new ViewerPostModel[children.length()];
for (int j = 0; j < sliderItems.length; ++j) {
final JSONObject node = children.optJSONObject(j).getJSONObject("node");
final boolean isChildVideo = node.optBoolean("is_video");
sliderItems[j] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
node.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : Utils.getHighQualityImage(node),
null, null, null,
node.optLong("video_view_count", -1), -1);
sliderItems[j].setSliderDisplayUrl(node.getString("display_url"));
}
feedModel.setSliderItems(sliderItems);
}
}
}
feedModelsList.add(feedModel);
}
feedModelsList.trimToSize();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
if (feedModels[feedModels.length - 1] != null)
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor);
result = feedModels;
}
urlConnection.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FEED_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 FeedModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View file

@ -0,0 +1,103 @@
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.FeedStoryModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector.LogFile;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FeedStoriesFetcher extends AsyncTask<Void, Void, FeedStoryModel[]> {
private final FetchListener<FeedStoryModel[]> fetchListener;
public FeedStoriesFetcher(final FetchListener<FeedStoryModel[]> fetchListener) {
this.fetchListener = fetchListener;
}
@Override
protected FeedStoryModel[] doInBackground(final Void... voids) {
FeedStoryModel[] result = null;
String url = "https://www.instagram.com/graphql/query/?query_hash=b7b84d884400bc5aa7cfe12ae843a091&variables=" +
"{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONArray feedStoriesReel = new JSONObject(Utils.readFromConnection(conn))
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("feed_reels_tray")
.getJSONObject("edge_reels_tray_to_reel")
.getJSONArray("edges");
conn.disconnect();
final int storiesLen = feedStoriesReel.length();
final FeedStoryModel[] feedStoryModels = new FeedStoryModel[storiesLen];
final String[] feedStoryIDs = new String[storiesLen];
for (int i = 0; i < storiesLen; ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
final ProfileModel profileModel = new ProfileModel(false, false,
user.getString("id"),
user.getString("username"),
null, null, null,
user.getString("profile_pic_url"),
null, 0, 0, 0);
final String id = node.getString("id");
feedStoryIDs[i] = id;
feedStoryModels[i] = new FeedStoryModel(id, profileModel);
}
url = "https://www.instagram.com/graphql/query/?query_hash=0a85e6ea60a4c99edc58ab2f3d17cfdf&variables=" +
"{\"reel_ids\":" + Utils.highlightIdsMerger(feedStoryIDs) + ",\"precomposed_overlay\":false}";
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
Utils.putHighlightModels(conn, feedStoryModels);
}
result = feedStoryModels;
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_FEED_STORY_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 FeedStoryModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,101 @@
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.FollowModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FollowFetcher extends AsyncTask<Void, Void, FollowModel[]> {
private final String endCursor, id;
private final boolean isFollowers;
private final FetchListener<FollowModel[]> fetchListener;
public FollowFetcher(final String id, final boolean isFollowers, final FetchListener<FollowModel[]> fetchListener) {
this.id = id;
this.endCursor = "";
this.isFollowers = isFollowers;
this.fetchListener = fetchListener;
}
public FollowFetcher(final String id, final boolean isFollowers, final String endCursor, final FetchListener<FollowModel[]> fetchListener) {
this.id = id;
this.endCursor = endCursor == null ? "" : endCursor;
this.isFollowers = isFollowers;
this.fetchListener = fetchListener;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected FollowModel[] doInBackground(final Void... voids) {
FollowModel[] result = null;
final String url = "https://www.instagram.com/graphql/query/?query_id=" + (isFollowers ? "17851374694183129" : "17874545323001329")
+ "&id=" + id + "&first=50&after=" + endCursor;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject(isFollowers ? "edge_followed_by" : "edge_follow");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = data.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray edges = data.getJSONArray("edges");
final FollowModel[] models = new FollowModel[edges.length()];
for (int i = 0; i < models.length; ++i) {
final JSONObject followNode = edges.getJSONObject(i).getJSONObject("node");
models[i] = new FollowModel(followNode.getString(Constants.EXTRAS_ID), followNode.getString(Constants.EXTRAS_USERNAME),
followNode.getString("full_name"), followNode.getString("profile_pic_url"));
}
if (models[models.length - 1] != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models;
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FOLLOW_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final FollowModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,87 @@
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.HighlightModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class HighlightsFetcher extends AsyncTask<Void, Void, HighlightModel[]> {
private final String id;
private final FetchListener<HighlightModel[]> fetchListener;
public HighlightsFetcher(final String id, final FetchListener<HighlightModel[]> fetchListener) {
this.id = id;
this.fetchListener = fetchListener;
}
@Override
protected HighlightModel[] doInBackground(final Void... voids) {
HighlightModel[] result = null;
String url = "https://www.instagram.com/graphql/query/?query_hash=7c16654f22c819fb63d1183034a5162f&variables=" +
"{\"user_id\":\"" + id + "\",\"include_chaining\":false,\"include_reel\":true,\"include_suggested_users\":false," +
"\"include_logged_out_extras\":false,\"include_highlight_reels\":true}";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONArray highlightsReel = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_highlight_reels").getJSONArray("edges");
final int length = highlightsReel.length();
final HighlightModel[] highlightModels = new HighlightModel[length];
final String[] highlightIds = new String[length];
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i).getJSONObject("node");
final String id = highlightNode.getString(Constants.EXTRAS_ID);
highlightIds[i] = id;
highlightModels[i] = new HighlightModel(
highlightNode.getString("title"),
highlightNode.getJSONObject("cover_media").getString("thumbnail_src")
);
}
conn.disconnect();
// a22a50ce4582220909e302d6eb84d259
// 45246d3fe16ccc6577e0bd297a5db1ab
url = "https://www.instagram.com/graphql/query/?query_hash=a22a50ce4582220909e302d6eb84d259&variables=" +
"{\"highlight_reel_ids\":" + Utils.highlightIdsMerger(highlightIds) + ",\"reel_ids\":[],\"location_ids\":[],\"precomposed_overlay\":false}";
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
Utils.putHighlightModels(conn, highlightModels);
}
result = highlightModels;
}
conn.disconnect();
} catch (Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final HighlightModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,146 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> {
private final String shortCode;
private final FetchListener<ViewerPostModel[]> fetchListener;
public PostFetcher(final String shortCode, final FetchListener<ViewerPostModel[]> fetchListener) {
this.shortCode = shortCode;
this.fetchListener = fetchListener;
}
@Override
protected ViewerPostModel[] doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
// to check if file exists
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
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;
final long timestamp = media.getLong("taken_at_timestamp");
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video");
final boolean isSlider = media.has("edge_sidecar_to_children");
final MediaItemType mediaItemType;
if (isSlider) mediaItemType = MediaItemType.MEDIA_TYPE_SLIDER;
else if (isVideo) mediaItemType = MediaItemType.MEDIA_TYPE_VIDEO;
else mediaItemType = MediaItemType.MEDIA_TYPE_IMAGE;
final String postCaption;
final JSONObject mediaToCaption = media.optJSONObject("edge_media_to_caption");
if (mediaToCaption == null) postCaption = null;
else {
final JSONArray captions = mediaToCaption.optJSONArray("edges");
postCaption = captions != null && captions.length() > 0 ?
captions.getJSONObject(0).getJSONObject("node").optString("text") : null;
}
JSONObject commentObject = media.optJSONObject("edge_media_to_parent_comment");
final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0;
String endCursor = null;
if (commentObject != null && (commentObject = commentObject.optJSONObject("page_info")) != null)
endCursor = commentObject.optString("end_cursor");
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) {
final ViewerPostModel postModel = new ViewerPostModel(mediaItemType,
media.getString(Constants.EXTRAS_ID),
isVideo ? media.getString("video_url") : Utils.getHighQualityImage(media),
shortCode,
Utils.isEmpty(postCaption) ? null : postCaption,
username,
isVideo && media.has("video_view_count") ? media.getLong("video_view_count") : -1,
timestamp);
postModel.setCommentsCount(commentsCount);
postModel.setCommentsEndCursor(endCursor);
Utils.checkExistence(downloadDir, customDir, username, false, -1, postModel);
result = new ViewerPostModel[]{postModel};
} else {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()];
for (int i = 0; i < postModels.length; ++i) {
final JSONObject node = children.getJSONObject(i).getJSONObject("node");
final boolean isChildVideo = node.getBoolean("is_video");
postModels[i] = new ViewerPostModel(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
node.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : Utils.getHighQualityImage(node),
node.getString(Constants.EXTRAS_SHORTCODE),
postCaption,
username,
isChildVideo && node.has("video_view_count") ? node.getLong("video_view_count") : -1,
timestamp);
postModels[i].setSliderDisplayUrl(node.getString("display_url"));
Utils.checkExistence(downloadDir, customDir, username, true, i, postModels[i]);
}
postModels[0].setCommentsCount(commentsCount);
postModels[0].setCommentsEndCursor(endCursor);
result = postModels;
}
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_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 ViewerPostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View file

@ -0,0 +1,134 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
private final String endCursor;
private final String id;
private final FetchListener<PostModel[]> fetchListener;
private String username;
public PostsFetcher(final String id, final FetchListener<PostModel[]> fetchListener) {
this.id = id;
this.endCursor = "";
this.fetchListener = fetchListener;
}
public PostsFetcher(final String id, final String endCursor, final FetchListener<PostModel[]> fetchListener) {
this.id = id;
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
public PostsFetcher setUsername(final String username) {
this.username = username;
return this;
}
@Override
protected PostModel[] doInBackground(final Void... voids) {
final boolean isHashTag = id.charAt(0) == '#';
final String url;
if (isHashTag)
url = "https://www.instagram.com/graphql/query/?query_hash=ded47faa9a1aaded10161a2ff32abb6b&variables=" +
"{\"tag_name\":\"" + id.substring(1) + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
else
url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor;
PostModel[] result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
// to check if file exists
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download");
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 mediaPosts = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject(isHashTag ? "hashtag" : Constants.EXTRAS_USER)
.getJSONObject(isHashTag ? "edge_hashtag_to_media" : "edge_owner_to_timeline_media");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = mediaPosts.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray edges = mediaPosts.getJSONArray("edges");
final PostModel[] models = new PostModel[edges.length()];
for (int i = 0; i < models.length; ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges");
final boolean isSlider = mediaNode.has("__typename") && mediaNode.getString("__typename").equals("GraphSidecar");
final boolean isVideo = mediaNode.getBoolean("is_video");
final MediaItemType itemType;
if (isSlider) itemType = MediaItemType.MEDIA_TYPE_SLIDER;
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO;
else itemType = MediaItemType.MEDIA_TYPE_IMAGE;
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID),
mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"),
mediaNode.getString(Constants.EXTRAS_SHORTCODE),
captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null,
mediaNode.getLong("taken_at_timestamp"));
Utils.checkExistence(downloadDir, customDir, username, isSlider, -1, models[i]);
}
if (models[models.length - 1] != null)
models[models.length - 1].setPageCursor(hasNextPage, endCursor);
result = models;
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final PostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View file

@ -0,0 +1,83 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
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.ProfileModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class ProfileFetcher extends AsyncTask<Void, Void, ProfileModel> {
private final FetchListener<ProfileModel> fetchListener;
private final String userName;
public ProfileFetcher(String userName, FetchListener<ProfileModel> fetchListener) {
this.userName = userName;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected ProfileModel doInBackground(final Void... voids) {
ProfileModel result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/" + userName + "/?__a=1").openConnection();
conn.setUseCaches(true);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject user = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_USER);
boolean isPrivate = user.getBoolean("is_private");
final JSONObject timelineMedia = user.getJSONObject("edge_owner_to_timeline_media");
if (timelineMedia.has("edges")) {
final JSONArray edges = timelineMedia.getJSONArray("edges");
if (edges.length() > 0) isPrivate = false;
}
String url = user.optString("external_url");
if (Utils.isEmpty(url)) url = null;
result = new ProfileModel(isPrivate,
user.getBoolean("is_verified"),
user.getString(Constants.EXTRAS_ID),
userName,
user.getString("full_name"),
user.getString("biography"),
url,
user.getString("profile_pic_url"),
user.getString("profile_pic_url_hd"),
timelineMedia.getLong("count"),
user.getJSONObject("edge_followed_by").getLong("count"),
user.getJSONObject("edge_follow").getLong("count"));
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final ProfileModel result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,120 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
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.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;
private final ProfilePictureFetchMode fetchMode;
public ProfilePictureFetcher(final String userName, final String userId, final FetchListener<String> fetchListener,
final ProfilePictureFetchMode fetchMode) {
this.fetchListener = fetchListener;
this.fetchMode = fetchMode;
this.userName = userName;
this.userId = userId;
}
@Override
protected String doInBackground(final Void... voids) {
String out = null;
try {
final String url;
if (fetchMode == ProfilePictureFetchMode.INSTADP)
url = "https://instadp.com/fullsize/" + userName;
else if (fetchMode == ProfilePictureFetchMode.INSTA_STALKER)
url = "https://insta-stalker.co/instadp_fullsize/?id=" + 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 HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
if (fetchMode == ProfilePictureFetchMode.INSTAFULLSIZE) {
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "fjgt842ff582a");
}
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;
}
} else {
final Elements elements = doc.select("img[data-src]");
if (elements.size() > 0) out = elements.get(0).attr("data-src");
else 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");
}
}
}
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_PICTURE_FETCHER, "doInBackground",
new Pair<>("fetchMode", fetchMode));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return out;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final String result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,102 @@
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.enums.MediaItemType;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> {
private final String id;
private final FetchListener<StoryModel[]> fetchListener;
public StoryStatusFetcher(final String id, final FetchListener<StoryModel[]> fetchListener) {
this.id = id;
this.fetchListener = fetchListener;
}
@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,\"reel_ids\":[\"" + id + "\"]}";
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("data");
JSONArray media;
if ((media = data.optJSONArray("reels_media")) != null && media.length() > 0 &&
(data = media.optJSONObject(0)) != null &&
(media = data.optJSONArray("items")) != null) {
final int mediaLen = media.length();
final StoryModel[] models = new StoryModel[mediaLen];
for (int i = 0; i < mediaLen; ++i) {
data = media.getJSONObject(i);
final boolean isVideo = data.getBoolean("is_video");
final JSONArray tappableObjects = data.optJSONArray("tappable_objects");
final int tappableLength = tappableObjects != null ? tappableObjects.length() : 0;
models[i] = new StoryModel(data.getString(Constants.EXTRAS_ID),
data.getString("display_url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at_timestamp", 0));
final JSONArray videoResources = data.optJSONArray("video_resources");
if (isVideo && videoResources != null)
models[i].setVideoUrl(Utils.getHighQualityPost(videoResources, true));
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;
}
}
}
result = models;
}
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_STORY_STATUS_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 StoryModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,98 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.InterruptedIOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.UrlEncoder;
import awais.instagrabber.utils.Utils;
public final class SuggestionsFetcher extends AsyncTask<String, String, SuggestionModel[]> {
private final FetchListener<SuggestionModel[]> fetchListener;
public SuggestionsFetcher(final FetchListener<SuggestionModel[]> fetchListener) {
this.fetchListener = fetchListener;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected SuggestionModel[] doInBackground(final String... params) {
SuggestionModel[] result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/web/search/topsearch/?context=blended&count=50&query="
+ UrlEncoder.encodeUrl(params[0])).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String defaultHashTagPic = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
final JSONObject jsonObject = new JSONObject(Utils.readFromConnection(conn));
conn.disconnect();
final JSONArray usersArray = jsonObject.getJSONArray("users");
final JSONArray hashtagsArray = jsonObject.getJSONArray("hashtags");
final int usersLen = usersArray.length();
final int hashtagsLen = hashtagsArray.length();
final ArrayList<SuggestionModel> suggestionModels = new ArrayList<>(usersLen + hashtagsLen);
for (int i = 0; i < hashtagsLen; i++) {
final JSONObject hashtagsArrayJSONObject = hashtagsArray.getJSONObject(i);
final JSONObject hashtag = hashtagsArrayJSONObject.getJSONObject("hashtag");
suggestionModels.add(new SuggestionModel(false,
hashtag.getString(Constants.EXTRAS_NAME),
null,
hashtag.optString("profile_pic_url", defaultHashTagPic),
SuggestionType.TYPE_HASHTAG,
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
}
for (int i = 0; i < usersLen; i++) {
final JSONObject usersArrayJSONObject = usersArray.getJSONObject(i);
final JSONObject user = usersArrayJSONObject.getJSONObject(Constants.EXTRAS_USER);
suggestionModels.add(new SuggestionModel(user.getBoolean("is_verified"),
user.getString(Constants.EXTRAS_USERNAME),
user.getString("full_name"),
user.getString("profile_pic_url"),
SuggestionType.TYPE_USER,
usersArrayJSONObject.optInt("position", suggestionModels.size() - 1)));
}
suggestionModels.trimToSize();
Collections.sort(suggestionModels);
result = suggestionModels.toArray(new SuggestionModel[0]);
}
} catch (final Exception e) {
if (BuildConfig.DEBUG && !(e instanceof InterruptedIOException)) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final SuggestionModel[] result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,54 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import org.json.JSONObject;
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;
public final class UsernameFetcher extends AsyncTask<Void, Void, String> {
private final FetchListener<String> fetchListener;
private final String uid;
public UsernameFetcher(final String uid, final FetchListener<String> fetchListener) {
this.uid = uid;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected String doInBackground(final Void... voids) {
String result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://i.instagram.com/api/v1/users/" + uid + "/info/").openConnection();
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.setUseCaches(true);
final JSONObject user;
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK &&
(user = new JSONObject(Utils.readFromConnection(conn)).optJSONObject(Constants.EXTRAS_USER)) != null)
result = user.getString(Constants.EXTRAS_USERNAME);
conn.disconnect();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final String result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View file

@ -0,0 +1,100 @@
package awais.instagrabber.asyncs.direct_messages;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
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.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile;
public final class InboxFetcher extends AsyncTask<Void, Void, InboxModel> {
private final String endCursor;
private final FetchListener<InboxModel> fetchListener;
public InboxFetcher(final String endCursor, final FetchListener<InboxModel> fetchListener) {
this.endCursor = Utils.isEmpty(endCursor) ? "" : "?cursor=" + endCursor;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected InboxModel doInBackground(final Void... voids) {
InboxModel result = null;
final String url = "https://i.instagram.com/api/v1/direct_v2/inbox/" + endCursor;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
conn.setUseCaches(false);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONObject data = new JSONObject(Utils.readFromConnection(conn));
// try (FileWriter fileWriter = new FileWriter(new File("/sdcard/test.json"))) {
// fileWriter.write(data.toString(2));
// }
final long seqId = data.optLong("seq_id");
final int pendingRequestsCount = data.optInt("pending_requests_total");
final boolean hasPendingTopRequests = data.optBoolean("has_pending_top_requests");
data = data.getJSONObject("inbox");
final boolean blendedInboxEnabled = data.optBoolean("blended_inbox_enabled");
final boolean hasOlder = data.optBoolean("has_older");
final int unseenCount = data.optInt("unseen_count");
final long unseenCountTimestamp = data.optLong("unseen_count_ts");
final String oldestCursor = data.optString("oldest_cursor");
InboxThreadModel[] inboxThreadModels = null;
final JSONArray threadsArray = data.optJSONArray("threads");
if (threadsArray != null) {
final int threadsLen = threadsArray.length();
inboxThreadModels = new InboxThreadModel[threadsLen];
for (int i = 0; i < threadsLen; ++i)
inboxThreadModels[i] = Utils.createInboxThreadModel(threadsArray.getJSONObject(i), false);
}
result = new InboxModel(hasOlder, hasPendingTopRequests,
blendedInboxEnabled, unseenCount, pendingRequestsCount,
seqId, unseenCountTimestamp, oldestCursor, inboxThreadModels);
}
conn.disconnect();
} catch (final Exception e) {
result = null;
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DMS, "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 InboxModel inboxModel) {
if (fetchListener != null) fetchListener.onResult(inboxModel);
}
}

View file

@ -0,0 +1,76 @@
package awais.instagrabber.asyncs.direct_messages;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
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.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile;
public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadModel> {
private final String id;
private final String endCursor;
private final FetchListener<InboxThreadModel> fetchListener;
private final String direction;
public UserInboxFetcher(final String id, final UserInboxDirection direction, final String endCursor,
final FetchListener<InboxThreadModel> fetchListener) {
this.id = id;
this.direction = "&direction=" + (direction == UserInboxDirection.NEWER ? "newer" : "older");
this.endCursor = !Utils.isEmpty(endCursor) ? "&cursor=" + endCursor : "";
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected InboxThreadModel doInBackground(final Void... voids) {
InboxThreadModel result = null;
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?visual_message_return_type=unseen"
+ direction + endCursor;
// todo probably
// & seq_id = seqId
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.setUseCaches(false);
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("thread");
result = Utils.createInboxThreadModel(data, true);
}
conn.disconnect();
} catch (final Exception e) {
result = null;
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "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 InboxThreadModel inboxThreadModel) {
if (fetchListener != null) fetchListener.onResult(inboxThreadModel);
}
}

View file

@ -0,0 +1,105 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import androidx.appcompat.widget.AppCompatImageView;
public final class CircularImageView extends AppCompatImageView {
private final int borderSize = 8;
private int color = Color.TRANSPARENT;
private final Paint paint = new Paint();
private final Paint paintBorder = new Paint();
private BitmapShader shader;
private Bitmap bitmap;
public CircularImageView(final Context context) {
super(context);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup();
}
private void setup() {
paint.setAntiAlias(true);
paintBorder.setColor(color);
paintBorder.setAntiAlias(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setOutlineProvider(new ViewOutlineProvider() {
private int viewHeight;
private int viewWidth;
@Override
public void getOutline(final View view, final Outline outline) {
if (viewHeight == 0) viewHeight = getHeight();
if (viewWidth == 0) viewWidth = getWidth();
outline.setRoundRect(borderSize, borderSize, viewWidth - borderSize, viewHeight - borderSize, viewHeight >> 1);
}
});
}
}
@Override
public void onDraw(final Canvas canvas) {
final BitmapDrawable bitmapDrawable = (BitmapDrawable) getDrawable();
if (bitmapDrawable != null) {
final Bitmap prevBitmap = bitmap;
bitmap = bitmapDrawable.getBitmap();
final boolean changed = prevBitmap != bitmap;
if (bitmap != null) {
final int width = getWidth();
final int height = getHeight();
if (shader == null || changed) {
shader = new BitmapShader(Bitmap.createScaledBitmap(bitmap, width, height, true), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint.setShader(shader);
}
if (changed) color = 0;
paintBorder.setColor(color);
final int circleCenter = (width - borderSize) / 2;
final int position = circleCenter + (borderSize / 2);
canvas.drawCircle(position, position, position - 4.0f, paintBorder);
canvas.drawCircle(position, position, circleCenter - 4.0f, paint);
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setLayerType(LAYER_TYPE_HARDWARE, null);
}
@Override
protected void onDetachedFromWindow() {
setLayerType(LAYER_TYPE_NONE, null);
super.onDetachedFromWindow();
}
public void setStoriesBorder() {
this.color = Color.GREEN;
invalidate();
}
}

View file

@ -0,0 +1,17 @@
package awais.instagrabber.customviews;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.view.View;
import androidx.annotation.NonNull;
public final class CommentMentionClickSpan extends ClickableSpan {
@Override
public void onClick(@NonNull final View widget) { }
@Override
public void updateDrawState(@NonNull final TextPaint ds) {
ds.setColor(ds.linkColor);
}
}

View file

@ -0,0 +1,25 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.widget.AppCompatImageView;
public final class FixedImageView extends AppCompatImageView {
public FixedImageView(final Context context) {
super(context);
}
public FixedImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public FixedImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(final int wMeasure, final int hMeasure) {
super.onMeasure(wMeasure, wMeasure);
}
}

View file

@ -0,0 +1,986 @@
package awais.instagrabber.customviews;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.customview.view.AbsSavedState;
import androidx.customview.widget.ViewDragHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
// exactly same as the LayoutDrawer with some edits
@SuppressLint("RtlHardcoded")
public class MouseDrawer extends ViewGroup {
@IntDef({ViewDragHelper.STATE_IDLE, ViewDragHelper.STATE_DRAGGING, ViewDragHelper.STATE_SETTLING})
@Retention(RetentionPolicy.SOURCE)
private @interface State {}
@IntDef(value = {Gravity.NO_GRAVITY, Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}, flag = true)
@Retention(RetentionPolicy.SOURCE)
public @interface EdgeGravity {}
////////////////////////////////////////////////////////////////////////////////////
private static final boolean CHILDREN_DISALLOW_INTERCEPT = true;
////////////////////////////////////////////////////////////////////////////////////
private final ArrayList<View> mNonDrawerViews = new ArrayList<>();
private final ViewDragHelper mLeftDragger, mRightDragger;
private boolean mInLayout, mFirstLayout = true;
private float mDrawerElevation, mInitialMotionX, mInitialMotionY;
private int mDrawerState;
private List<DrawerListener> mListeners;
private Matrix mChildInvertedMatrix;
private Rect mChildHitRect;
public interface DrawerListener {
void onDrawerSlide(final View drawerView, @EdgeGravity final int gravity, final float slideOffset);
default void onDrawerOpened(final View drawerView, @EdgeGravity final int gravity) {}
default void onDrawerClosed(final View drawerView, @EdgeGravity final int gravity) {}
default void onDrawerStateChanged() {}
}
public MouseDrawer(@NonNull final Context context) {
this(context, null);
}
public MouseDrawer(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public MouseDrawer(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
final float density = getResources().getDisplayMetrics().density;
this.mDrawerElevation = 10 * density;
final float touchSlopSensitivity = 0.5f; // was 1.0f
final float minFlingVelocity = 400 /* dips per second */ * density;
final ViewDragCallback mLeftCallback = new ViewDragCallback(Gravity.LEFT);
this.mLeftDragger = ViewDragHelper.create(this, touchSlopSensitivity, mLeftCallback);
this.mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
this.mLeftDragger.setMinVelocity(minFlingVelocity);
final ViewDragCallback mRightCallback = new ViewDragCallback(Gravity.RIGHT);
this.mRightDragger = ViewDragHelper.create(this, touchSlopSensitivity, mRightCallback);
this.mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
this.mRightDragger.setMinVelocity(minFlingVelocity);
try {
final Field edgeSizeField = ViewDragHelper.class.getDeclaredField("mEdgeSize");
if (!edgeSizeField.isAccessible()) edgeSizeField.setAccessible(true);
final int widthPixels = getResources().getDisplayMetrics().widthPixels; // whole screen
edgeSizeField.set(this.mLeftDragger, widthPixels / 2);
edgeSizeField.set(this.mRightDragger, widthPixels / 2);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
mLeftCallback.setDragger(mLeftDragger);
mRightCallback.setDragger(mRightDragger);
setFocusableInTouchMode(true);
//setMotionEventSplittingEnabled(false);
}
public void setDrawerElevation(final float elevation) {
mDrawerElevation = elevation;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (isDrawerView(child)) ViewCompat.setElevation(child, mDrawerElevation);
}
}
public float getDrawerElevation() {
return Build.VERSION.SDK_INT >= 21 ? mDrawerElevation : 0f;
}
public void addDrawerListener(@NonNull final DrawerListener listener) {
if (mListeners == null) mListeners = new ArrayList<>();
mListeners.add(listener);
}
private boolean isInBoundsOfChild(final float x, final float y, final View child) {
if (mChildHitRect == null) mChildHitRect = new Rect();
child.getHitRect(mChildHitRect);
return mChildHitRect.contains((int) x, (int) y);
}
private boolean dispatchTransformedGenericPointerEvent(final MotionEvent event, @NonNull final View child) {
final boolean handled;
final Matrix childMatrix = child.getMatrix();
if (!childMatrix.isIdentity()) {
final MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
handled = child.dispatchGenericMotionEvent(transformedEvent);
transformedEvent.recycle();
} else {
final float offsetX = getScrollX() - child.getLeft();
final float offsetY = getScrollY() - child.getTop();
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchGenericMotionEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
@NonNull
private MotionEvent getTransformedMotionEvent(final MotionEvent event, @NonNull final View child) {
final float offsetX = getScrollX() - child.getLeft();
final float offsetY = getScrollY() - child.getTop();
final MotionEvent transformedEvent = MotionEvent.obtain(event);
transformedEvent.offsetLocation(offsetX, offsetY);
final Matrix childMatrix = child.getMatrix();
if (!childMatrix.isIdentity()) {
if (mChildInvertedMatrix == null) mChildInvertedMatrix = new Matrix();
childMatrix.invert(mChildInvertedMatrix);
transformedEvent.transform(mChildInvertedMatrix);
}
return transformedEvent;
}
void updateDrawerState(@State final int activeState, final View activeDrawer) {
final int leftState = mLeftDragger.getViewDragState();
final int rightState = mRightDragger.getViewDragState();
final int state;
if (leftState == ViewDragHelper.STATE_DRAGGING || rightState == ViewDragHelper.STATE_DRAGGING)
state = ViewDragHelper.STATE_DRAGGING;
else if (leftState == ViewDragHelper.STATE_SETTLING || rightState == ViewDragHelper.STATE_SETTLING)
state = ViewDragHelper.STATE_SETTLING;
else state = ViewDragHelper.STATE_IDLE;
if (activeDrawer != null && activeState == ViewDragHelper.STATE_IDLE) {
final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams();
if (lp.onScreen == 0) dispatchOnDrawerClosed(activeDrawer);
else if (lp.onScreen == 1) dispatchOnDrawerOpened(activeDrawer);
}
if (state != mDrawerState) {
mDrawerState = state;
if (mListeners != null) {
final int listenerCount = mListeners.size();
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerStateChanged();
}
}
}
void dispatchOnDrawerClosed(@NonNull final View drawerView) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
lp.openState = 0;
if (mListeners != null) {
final int listenerCount = mListeners.size();
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerClosed(drawerView, lp.gravity);
}
}
}
void dispatchOnDrawerOpened(@NonNull final View drawerView) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) {
lp.openState = LayoutParams.FLAG_IS_OPENED;
if (mListeners != null) {
final int listenerCount = mListeners.size();
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerOpened(drawerView, lp.gravity);
}
}
}
void setDrawerViewOffset(@NonNull final View drawerView, final float slideOffset) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if (slideOffset != lp.onScreen) {
lp.onScreen = slideOffset;
if (mListeners != null) {
final int listenerCount = mListeners.size();
for (int i = listenerCount - 1; i >= 0; i--)
mListeners.get(i).onDrawerSlide(drawerView, lp.gravity, slideOffset);
}
}
}
float getDrawerViewOffset(@NonNull final View drawerView) {
return ((LayoutParams) drawerView.getLayoutParams()).onScreen;
}
int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this));
}
boolean checkDrawerViewAbsoluteGravity(final View drawerView, final int checkFor) {
final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
return (absGravity & checkFor) == checkFor;
}
void moveDrawerToOffset(final View drawerView, final float slideOffset) {
final float oldOffset = getDrawerViewOffset(drawerView);
final int width = drawerView.getWidth();
final int oldPos = (int) (width * oldOffset);
final int newPos = (int) (width * slideOffset);
final int dx = newPos - oldPos;
drawerView.offsetLeftAndRight(checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx);
setDrawerViewOffset(drawerView, slideOffset);
}
public View findOpenDrawer() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams childLp = (LayoutParams) child.getLayoutParams();
if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) return child;
}
return null;
}
public View findDrawerWithGravity(final int gravity) {
final int absHorizGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) return child;
}
return null;
}
@NonNull
static String gravityToString(@EdgeGravity final int gravity) {
if ((gravity & Gravity.LEFT) == Gravity.LEFT) return "LEFT";
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) return "RIGHT";
return Integer.toHexString(gravity);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mFirstLayout = true;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
@SuppressLint("WrongConstant")
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
boolean hasDrawerOnLeftEdge = false;
boolean hasDrawerOnRightEdge = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
// Content views get measured at exactly the layout's size.
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
child.measure(contentWidthSpec, contentHeightSpec);
} else if (isDrawerView(child)) {
if (Build.VERSION.SDK_INT >= 21 && ViewCompat.getElevation(child) != mDrawerElevation)
ViewCompat.setElevation(child, mDrawerElevation);
final int childGravity = getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
final boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT);
if (isLeftEdgeDrawer && hasDrawerOnLeftEdge || !isLeftEdgeDrawer && hasDrawerOnRightEdge)
throw new IllegalStateException("Child drawer has absolute gravity " + gravityToString(childGravity)
+ " but this MouseDrawer already has a drawer view along that edge");
if (isLeftEdgeDrawer) hasDrawerOnLeftEdge = true;
else hasDrawerOnRightEdge = true;
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width);
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height);
child.measure(drawerWidthSpec, drawerHeightSpec);
} else
throw new IllegalStateException("Child " + child + " at index " + i
+ " does not have a valid layout_gravity - must be Gravity.LEFT, Gravity.RIGHT or Gravity.NO_GRAVITY");
}
}
}
@Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
mInLayout = true;
final int width = right - left;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isContentView(child)) {
child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(),
lp.topMargin + child.getMeasuredHeight());
} else { // Drawer, if it wasn't onMeasure would have thrown an exception.
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final int childLeft;
final float newOffset;
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {
childLeft = -childWidth + (int) (childWidth * lp.onScreen);
newOffset = (float) (childWidth + childLeft) / childWidth;
} else { // Right; onMeasure checked for us.
childLeft = width - (int) (childWidth * lp.onScreen);
newOffset = (float) (width - childLeft) / childWidth;
}
final boolean changeOffset = newOffset != lp.onScreen;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (vgrav) {
default:
case Gravity.TOP:
child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight);
break;
case Gravity.BOTTOM: {
final int height = bottom - top;
child.layout(childLeft, height - lp.bottomMargin - child.getMeasuredHeight(),
childLeft + childWidth, height - lp.bottomMargin);
break;
}
case Gravity.CENTER_VERTICAL: {
final int height = bottom - top;
int childTop = (height - childHeight) / 2;
if (childTop < lp.topMargin) childTop = lp.topMargin;
else if (childTop + childHeight > height - lp.bottomMargin)
childTop = height - lp.bottomMargin - childHeight;
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
break;
}
}
if (changeOffset) setDrawerViewOffset(child, newOffset);
final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE;
if (child.getVisibility() != newVisibility) child.setVisibility(newVisibility);
}
}
}
mInLayout = false;
mFirstLayout = false;
}
@Override
public void requestLayout() {
if (!mInLayout) super.requestLayout();
}
@Override
public void computeScroll() {
final boolean leftDraggerSettling = mLeftDragger.continueSettling(true);
final boolean rightDraggerSettling = mRightDragger.continueSettling(true);
if (leftDraggerSettling || rightDraggerSettling) postInvalidateOnAnimation();
}
private static boolean hasOpaqueBackground(@NonNull final View v) {
final Drawable bg = v.getBackground();
if (bg != null) return bg.getOpacity() == PixelFormat.OPAQUE;
return false;
}
@Override
protected boolean drawChild(@NonNull final Canvas canvas, final View child, final long drawingTime) {
final int height = getHeight();
final boolean drawingContent = isContentView(child);
int clipLeft = 0, clipRight = getWidth();
final int restoreCount = canvas.save();
if (drawingContent) {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
if (v != child && v.getVisibility() == VISIBLE && hasOpaqueBackground(v) && isDrawerView(v) && v.getHeight() >= height) {
if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
final int vright = v.getRight();
if (vright > clipLeft) clipLeft = vright;
} else {
final int vleft = v.getLeft();
if (vleft < clipRight) clipRight = vleft;
}
}
}
canvas.clipRect(clipLeft, 0, clipRight, getHeight());
}
final boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(restoreCount);
return result;
}
boolean isContentView(@NonNull final View child) {
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
}
boolean isDrawerView(@NonNull final View child) {
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(child));
return (absGravity & Gravity.LEFT) != 0 || (absGravity & Gravity.RIGHT) != 0;
}
@Override
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) {
final int action = ev.getActionMasked();
// "|" used deliberately here; both methods should be invoked.
final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | mRightDragger.shouldInterceptTouchEvent(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
mInitialMotionX = ev.getX();
mInitialMotionY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
closeDrawers(true);
}
return interceptForDrag || hasPeekingDrawer();
}
@Override
public boolean dispatchGenericMotionEvent(@NonNull final MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0 || event.getAction() == MotionEvent.ACTION_HOVER_EXIT)
return super.dispatchGenericMotionEvent(event);
final int childrenCount = getChildCount();
if (childrenCount != 0) {
final float x = event.getX();
final float y = event.getY();
// Walk through children from top to bottom.
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (isInBoundsOfChild(x, y, child) && !isContentView(child) && dispatchTransformedGenericPointerEvent(event, child))
return true;
}
}
return false;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent ev) {
mLeftDragger.processTouchEvent(ev);
mRightDragger.processTouchEvent(ev);
final int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mInitialMotionX = ev.getX();
mInitialMotionY = ev.getY();
break;
case MotionEvent.ACTION_UP:
final float x = ev.getX();
final float y = ev.getY();
boolean peekingOnly = true;
final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y);
if (touchedView != null && isContentView(touchedView)) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mLeftDragger.getTouchSlop();
if (dx * dx + dy * dy < slop * slop) {
// Taps close a dimmed open drawer but only if it isn't locked open.
final View openDrawer = findOpenDrawer();
if (openDrawer != null) peekingOnly = false;
}
}
closeDrawers(peekingOnly);
break;
case MotionEvent.ACTION_CANCEL:
closeDrawers(true);
break;
}
return true;
}
@Override
public void requestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
if (CHILDREN_DISALLOW_INTERCEPT || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT)))
super.requestDisallowInterceptTouchEvent(disallowIntercept);
if (disallowIntercept) closeDrawers(true);
}
public void closeDrawers() {
closeDrawers(false);
}
void closeDrawers(final boolean peekingOnly) {
boolean needsInvalidate = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isDrawerView(child) && (!peekingOnly || lp.isPeeking)) {
final int childWidth = child.getWidth();
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT))
needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, -childWidth, child.getTop());
else
needsInvalidate |= mRightDragger.smoothSlideViewTo(child, getWidth(), child.getTop());
lp.isPeeking = false;
}
}
if (needsInvalidate) invalidate();
}
public void openDrawer(@NonNull final View drawerView, final boolean animate) {
if (isDrawerView(drawerView)) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if (mFirstLayout) {
lp.onScreen = 1.f;
lp.openState = LayoutParams.FLAG_IS_OPENED;
} else if (animate) {
lp.openState |= LayoutParams.FLAG_IS_OPENING;
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT))
mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop());
else
mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), drawerView.getTop());
} else {
moveDrawerToOffset(drawerView, 1.f);
updateDrawerState(ViewDragHelper.STATE_IDLE, drawerView);
drawerView.setVisibility(VISIBLE);
}
invalidate();
return;
}
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
public void openDrawer(@NonNull final View drawerView) {
openDrawer(drawerView, true);
}
// public void openDrawer(@EdgeGravity final int gravity, final boolean animate) {
// final View drawerView = findDrawerWithGravity(gravity);
// if (drawerView != null) openDrawer(drawerView, animate);
// else throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity));
// }
// public void openDrawer(@EdgeGravity final int gravity) {
// openDrawer(gravity, true);
// }
public void closeDrawer(@NonNull final View drawerView) {
closeDrawer(drawerView, true);
}
public void closeDrawer(@NonNull final View drawerView, final boolean animate) {
if (isDrawerView(drawerView)) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if (mFirstLayout) {
lp.onScreen = 0.f;
lp.openState = 0;
} else if (animate) {
lp.openState |= LayoutParams.FLAG_IS_CLOSING;
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT))
mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop());
else
mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
} else {
moveDrawerToOffset(drawerView, 0.f);
updateDrawerState(ViewDragHelper.STATE_IDLE, drawerView);
drawerView.setVisibility(INVISIBLE);
}
invalidate();
} else throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
// public void closeDrawer(@EdgeGravity final int gravity) {
// closeDrawer(gravity, true);
// }
// public void closeDrawer(@EdgeGravity final int gravity, final boolean animate) {
// final View drawerView = findDrawerWithGravity(gravity);
// if (drawerView != null) closeDrawer(drawerView, animate);
// else throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity));
// }
public boolean isDrawerOpen(@NonNull final View drawer) {
if (isDrawerView(drawer)) return (((LayoutParams) drawer.getLayoutParams()).openState & LayoutParams.FLAG_IS_OPENED) == 1;
else throw new IllegalArgumentException("View " + drawer + " is not a drawer");
}
// public boolean isDrawerOpen(@EdgeGravity final int drawerGravity) {
// final View drawerView = findDrawerWithGravity(drawerGravity);
// return drawerView != null && isDrawerOpen(drawerView);
// }
public boolean isDrawerVisible(@NonNull final View drawer) {
if (isDrawerView(drawer)) return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0;
throw new IllegalArgumentException("View " + drawer + " is not a drawer");
}
// public boolean isDrawerVisible(@EdgeGravity final int drawerGravity) {
// final View drawerView = findDrawerWithGravity(drawerGravity);
// return drawerView != null && isDrawerVisible(drawerView);
// }
private boolean hasPeekingDrawer() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
if (lp.isPeeking) return true;
}
return false;
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams params) {
return params instanceof LayoutParams ? new LayoutParams((LayoutParams) params) :
params instanceof ViewGroup.MarginLayoutParams ? new LayoutParams((MarginLayoutParams) params) : new LayoutParams(params);
}
@Override
protected boolean checkLayoutParams(final ViewGroup.LayoutParams params) {
return params instanceof LayoutParams && super.checkLayoutParams(params);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
public void addFocusables(final ArrayList<View> views, final int direction, final int focusableMode) {
if (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS) {
final int childCount = getChildCount();
boolean isDrawerOpen = false;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (!isDrawerView(child)) mNonDrawerViews.add(child);
else if (isDrawerOpen(child)) {
isDrawerOpen = true;
child.addFocusables(views, direction, focusableMode);
}
}
if (!isDrawerOpen) {
final int nonDrawerViewsCount = mNonDrawerViews.size();
for (int i = 0; i < nonDrawerViewsCount; ++i) {
final View child = mNonDrawerViews.get(i);
if (child.getVisibility() == View.VISIBLE) child.addFocusables(views, direction, focusableMode);
}
}
mNonDrawerViews.clear();
}
}
private boolean hasVisibleDrawer() {
return findVisibleDrawer() != null;
}
@Nullable
final View findVisibleDrawer() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (isDrawerView(child) && isDrawerVisible(child)) return child;
}
return null;
}
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) {
event.startTracking();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(final int keyCode, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
final View visibleDrawer = findVisibleDrawer();
if (visibleDrawer != null && isDrawerView(visibleDrawer)) closeDrawers();
return visibleDrawer != null;
}
return super.onKeyUp(keyCode, event);
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
if (state instanceof SavedState) {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.openDrawerGravity != Gravity.NO_GRAVITY) {
final View toOpen = findDrawerWithGravity(ss.openDrawerGravity);
if (toOpen != null) openDrawer(toOpen);
}
} else super.onRestoreInstanceState(state);
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
assert superState != null;
final SavedState ss = new SavedState(superState);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Is the current child fully opened (that is, not closing)?
final boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED);
// Is the current child opening?
final boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING);
if (isOpenedAndNotClosing || isClosedAndOpening) {
// If one of the conditions above holds, save the child's gravity so that we open that child during state restore.
ss.openDrawerGravity = lp.gravity;
break;
}
}
return ss;
}
@Override
public void addView(final View child, final int index, final ViewGroup.LayoutParams params) {
super.addView(child, index, params);
final View openDrawer = findOpenDrawer();
if (openDrawer == null) isDrawerView(child);
}
protected static class SavedState extends AbsSavedState {
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
@NonNull
@Override
public SavedState createFromParcel(final Parcel in, final ClassLoader loader) {
return new SavedState(in, loader);
}
@NonNull
@Override
public SavedState createFromParcel(final Parcel in) {
return new SavedState(in, null);
}
@NonNull
@Override
public SavedState[] newArray(final int size) {
return new SavedState[size];
}
};
int openDrawerGravity = Gravity.NO_GRAVITY;
public SavedState(@NonNull final Parcelable superState) {
super(superState);
}
public SavedState(@NonNull final Parcel in, @Nullable final ClassLoader loader) {
super(in, loader);
openDrawerGravity = in.readInt();
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(openDrawerGravity);
}
}
private class ViewDragCallback extends ViewDragHelper.Callback {
private final int mAbsGravity;
private ViewDragHelper mDragger;
ViewDragCallback(final int gravity) {
mAbsGravity = gravity;
}
public void setDragger(final ViewDragHelper dragger) {
mDragger = dragger;
}
@Override
public boolean tryCaptureView(@NonNull final View child, final int pointerId) {
return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity);
}
@Override
public void onViewDragStateChanged(final int state) {
updateDrawerState(state, mDragger.getCapturedView());
}
@Override
public void onViewPositionChanged(@NonNull final View changedView, final int left, final int top, final int dx, final int dy) {
final float offset;
final int childWidth = changedView.getWidth();
if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) offset = (float) (childWidth + left) / childWidth;
else offset = (float) (getWidth() - left) / childWidth;
setDrawerViewOffset(changedView, offset);
changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE);
invalidate();
}
@Override
public void onViewCaptured(@NonNull final View capturedChild, final int activePointerId) {
final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams();
lp.isPeeking = false;
closeOtherDrawer();
}
private void closeOtherDrawer() {
final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT;
final View toClose = findDrawerWithGravity(otherGrav);
if (toClose != null) closeDrawer(toClose);
}
@Override
public void onViewReleased(@NonNull final View releasedChild, final float xvel, final float yvel) {
final float offset = getDrawerViewOffset(releasedChild);
final int childWidth = releasedChild.getWidth();
final int left;
if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT))
left = xvel > 0 || (xvel == 0 && offset > 0.5f) ? 0 : -childWidth;
else {
final int width = getWidth();
left = xvel < 0 || (xvel == 0 && offset > 0.5f) ? width - childWidth : width;
}
mDragger.settleCapturedViewAt(left, releasedChild.getTop());
invalidate();
}
@Override
public void onEdgeDragStarted(final int edgeFlags, final int pointerId) {
final View toCapture;
if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT)
toCapture = findDrawerWithGravity(Gravity.LEFT);
else toCapture = findDrawerWithGravity(Gravity.RIGHT);
if (toCapture != null && isDrawerView(toCapture)) mDragger.captureChildView(toCapture, pointerId);
}
@Override
public int getViewHorizontalDragRange(@NonNull final View child) {
return isDrawerView(child) ? child.getWidth() : 0;
}
@Override
public int clampViewPositionHorizontal(@NonNull final View child, final int left, final int dx) {
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) return Math.max(-child.getWidth(), Math.min(left, 0));
final int width = getWidth();
return Math.max(width - child.getWidth(), Math.min(left, width));
}
@Override
public int clampViewPositionVertical(@NonNull final View child, final int top, final int dy) {
return child.getTop();
}
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
private static final int FLAG_IS_CLOSING = 0x4;
public static final int FLAG_IS_OPENED = 0x1;
public static final int FLAG_IS_OPENING = 0x2;
public int openState;
@EdgeGravity
public int gravity = Gravity.NO_GRAVITY;
public boolean isPeeking;
public float onScreen;
public LayoutParams(@NonNull final Context c, @Nullable final AttributeSet attrs) {
super(c, attrs);
final TypedArray a = c.obtainStyledAttributes(attrs, new int[]{android.R.attr.layout_gravity});
try {
this.gravity = a.getInt(0, Gravity.NO_GRAVITY);
} finally {
a.recycle();
}
}
public LayoutParams(final int width, final int height) {
super(width, height);
}
public LayoutParams(@NonNull final LayoutParams source) {
super(source);
this.gravity = source.gravity;
}
public LayoutParams(@NonNull final ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(@NonNull final ViewGroup.MarginLayoutParams source) {
super(source);
}
}
}

View file

@ -0,0 +1,179 @@
package awais.instagrabber.customviews;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.RectF;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Utils;
public final class RamboTextView extends AppCompatTextView {
private static final int highlightBackgroundSpanKey = R.id.tvComment;
private static final RectF touchedLineBounds = new RectF();
private ClickableSpan clickableSpanUnderTouchOnActionDown;
private MentionClickListener mentionClickListener;
private boolean isUrlHighlighted, isExpandable, isExpanded;
public RamboTextView(final Context context) {
super(context);
}
public RamboTextView(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
public RamboTextView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setMentionClickListener(final MentionClickListener mentionClickListener) {
this.mentionClickListener = mentionClickListener;
}
public void setCaptionIsExpandable(final boolean isExpandable) {
this.isExpandable = isExpandable;
}
public void setCaptionIsExpanded(final boolean isExpanded) {
this.isExpanded = isExpanded;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(final MotionEvent event) {
final CharSequence text = getText();
if (text instanceof SpannableString || text instanceof SpannableStringBuilder) {
final Spannable spanText = (Spannable) text;
final ClickableSpan clickableSpanUnderTouch = findClickableSpanUnderTouch(this, spanText, event);
final int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch;
final boolean touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null;
final boolean isURLSpan = clickableSpanUnderTouch instanceof URLSpan;
// feed view caption hacks
if (isExpandable && !touchStartedOverAClickableSpan)
return !isExpanded | super.onTouchEvent(event); // short operator, because we want two shits to work
final Object tag = getTag();
final FeedModel feedModel = tag instanceof FeedModel ? (FeedModel) tag : null;
switch (action) {
case MotionEvent.ACTION_DOWN:
if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText);
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan;
case MotionEvent.ACTION_UP:
if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
dispatchUrlClick(spanText, clickableSpanUnderTouch);
if (feedModel != null) feedModel.setMentionClicked(true);
}
cleanupOnTouchUp(spanText);
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan;
case MotionEvent.ACTION_MOVE:
if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText);
else removeUrlHighlightColor(spanText);
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan;
case MotionEvent.ACTION_CANCEL:
if (feedModel != null) feedModel.setMentionClicked(false);
cleanupOnTouchUp(spanText);
return super.onTouchEvent(event);
}
}
return super.onTouchEvent(event);
}
protected void dispatchUrlClick(final Spanned s, final ClickableSpan clickableSpan) {
if (mentionClickListener != null) {
final int spanStart = s.getSpanStart(clickableSpan);
final boolean ishHashtag = s.charAt(spanStart) == '#';
final int start = ishHashtag || s.charAt(spanStart) != '@' ? spanStart : spanStart + 1;
CharSequence subSequence = s.subSequence(start, s.getSpanEnd(clickableSpan));
// for feed ellipsize
final int indexOfEllipsize = Utils.indexOfChar(subSequence, '…', 0);
if (indexOfEllipsize != -1)
subSequence = subSequence.subSequence(0, indexOfEllipsize - 1);
mentionClickListener.onClick(this, subSequence.toString(), ishHashtag);
}
}
protected void highlightUrl(final ClickableSpan clickableSpan, final Spannable text) {
if (!isUrlHighlighted) {
isUrlHighlighted = true;
final int spanStart = text.getSpanStart(clickableSpan);
final int spanEnd = text.getSpanEnd(clickableSpan);
final BackgroundColorSpan highlightSpan = new BackgroundColorSpan(getHighlightColor());
text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTag(highlightBackgroundSpanKey, highlightSpan);
Selection.setSelection(text, spanStart, spanEnd);
}
}
protected void removeUrlHighlightColor(final Spannable text) {
if (isUrlHighlighted) {
isUrlHighlighted = false;
final BackgroundColorSpan highlightSpan = (BackgroundColorSpan) getTag(highlightBackgroundSpanKey);
text.removeSpan(highlightSpan);
Selection.removeSelection(text);
}
}
private void cleanupOnTouchUp(final Spannable text) {
clickableSpanUnderTouchOnActionDown = null;
removeUrlHighlightColor(text);
}
@Nullable
private static ClickableSpan findClickableSpanUnderTouch(@NonNull final TextView textView, final Spannable text, @NonNull final MotionEvent event) {
final int touchX = (int) (event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX());
final int touchY = (int) (event.getY() - textView.getTotalPaddingTop() + textView.getScrollY());
final Layout layout = textView.getLayout();
final int touchedLine = layout.getLineForVertical(touchY);
final int touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX);
touchedLineBounds.left = layout.getLineLeft(touchedLine);
touchedLineBounds.top = layout.getLineTop(touchedLine);
touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left;
touchedLineBounds.bottom = layout.getLineBottom(touchedLine);
if (touchedLineBounds.contains(touchX, touchY)) {
final Object[] spans = text.getSpans(touchOffset, touchOffset, ClickableSpan.class);
for (final Object span : spans)
if (span instanceof ClickableSpan) return (ClickableSpan) span;
}
return null;
}
}

View file

@ -0,0 +1,182 @@
package awais.instagrabber.customviews;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import awais.instagrabber.R;
public final class RemixDrawerLayout extends MouseDrawer implements MouseDrawer.DrawerListener {
private final FrameLayout frameLayout;
private View drawerView;
private RecyclerView scroll, feedPosts;
private float startX;
public RemixDrawerLayout(@NonNull final Context context) {
this(context, null);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
super.setDrawerElevation(getDrawerElevation());
addDrawerListener(this);
frameLayout = new FrameLayout(context);
frameLayout.setPadding(0, 0, 0, 0);
super.addView(frameLayout);
}
@Override
public void addView(@NonNull final View child, final ViewGroup.LayoutParams params) {
child.setLayoutParams(params);
addView(child);
}
@Override
public void addView(@NonNull final View child) {
if (child.getTag() != null) super.addView(child);
else frameLayout.addView(child);
}
@Override
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
// another one of my own weird hack thingies to make this app work
if (feedPosts == null) feedPosts = findViewById(R.id.feedPosts);
if (feedPosts != null) {
for (int i = 0; i < feedPosts.getChildCount(); ++i) {
final View viewHolder = feedPosts.getChildAt(i);
final View mediaList = viewHolder.findViewById(R.id.media_list);
if (mediaList instanceof ViewPager) {
final ViewPager viewPager = (ViewPager) mediaList;
final Rect rect = new Rect();
viewPager.getGlobalVisibleRect(rect);
final boolean touchIsInMediaList = rect.contains((int) x, (int) y);
if (touchIsInMediaList) {
final PagerAdapter adapter = viewPager.getAdapter();
final int count = adapter != null ? adapter.getCount() : 0;
if (count < 1 || viewPager.getCurrentItem() != count - 1) return false;
break;
}
}
}
}
// thanks to Fede @ https://stackoverflow.com/questions/6920137/android-viewpager-and-horizontalscrollview/7258579#7258579
if (scroll == null) scroll = findViewById(R.id.highlightsList);
if (scroll != null) {
final boolean touchIsInRecycler = x >= scroll.getLeft() && x < scroll.getRight()
&& y >= scroll.getTop() && scroll.getBottom() > y;
if (touchIsInRecycler) {
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_CANCEL) return super.onInterceptTouchEvent(ev);
if (action == MotionEvent.ACTION_DOWN) startX = x;
else if (action == MotionEvent.ACTION_MOVE) {
final int scrollRange = scroll.computeHorizontalScrollRange();
final int scrollOffset = scroll.computeHorizontalScrollOffset();
final boolean scrollable = scrollRange > scroll.getWidth();
final boolean draggingFromRight = startX > x;
if (scrollOffset < 1) {
if (!scrollable) return super.onInterceptTouchEvent(ev);
else if (!draggingFromRight) return super.onInterceptTouchEvent(ev);
} else if (scrollable && draggingFromRight && scrollRange - scrollOffset == scroll.computeHorizontalScrollExtent()) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public void onDrawerSlide(@NonNull final View view, @EdgeGravity final int gravity, final float slideOffset) {
drawerView = view;
final int absHorizGravity = getDrawerViewAbsoluteGravity(GravityCompat.START);
final int childAbsGravity = getDrawerViewAbsoluteGravity(drawerView);
final Window window = getActivity(getContext()).getWindow();
final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| window.getDecorView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
final int drawerViewWidth = drawerView.getWidth();
// for (int i = 0; i < frameLayout.getChildCount(); i++) {
// final View child = frameLayout.getChildAt(i);
//
// final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
// float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
//
// child.setX(width * slideOffset);
// }
final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
frameLayout.setX(width * (isRtl ? -slideOffset : slideOffset));
}
@Override
public void openDrawer(@NonNull final View drawerView, final boolean animate) {
super.openDrawer(drawerView, animate);
post(() -> onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f));
}
@Override
protected void onConfigurationChanged(final Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (drawerView != null) onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f);
}
private static Activity getActivity(final Context context) {
if (context != null) {
if (context instanceof Activity) return (Activity) context;
if (context instanceof ContextWrapper)
return getActivity(((ContextWrapper) context).getBaseContext());
}
return null;
}
final int getDrawerViewAbsoluteGravity(final int gravity) {
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
}
final int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return getDrawerViewAbsoluteGravity(gravity);
}
}

View file

@ -0,0 +1,37 @@
package awais.instagrabber.customviews.helpers;
import android.content.Context;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.utils.Utils;
public class GridAutofitLayoutManager extends GridLayoutManager {
private int mColumnWidth;
private boolean mColumnWidthChanged = true;
public GridAutofitLayoutManager(Context context, int columnWidth) {
super(context, 1);
if (columnWidth <= 0) columnWidth = (int) (48 * Utils.displayMetrics.density);
if (columnWidth > 0 && columnWidth != mColumnWidth) {
mColumnWidth = columnWidth;
mColumnWidthChanged = true;
}
}
@Override
public void onLayoutChildren(final RecyclerView.Recycler recycler, final RecyclerView.State state) {
final int width = getWidth();
final int height = getHeight();
if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0) {
final int totalSpace = getOrientation() == VERTICAL ? width - getPaddingRight() - getPaddingLeft()
: height - getPaddingTop() - getPaddingBottom();
setSpanCount(Math.max(1, totalSpace / mColumnWidth));
mColumnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}

View file

@ -0,0 +1,31 @@
package awais.instagrabber.customviews.helpers;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private final int spacing;
public GridSpacingItemDecoration(int spacing) {
this.spacing = spacing;
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final RecyclerView.LayoutManager manager = parent.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final int spanCount = ((GridLayoutManager) manager).getSpanCount();
final int position = parent.getChildAdapterPosition(view);
final int column = position % spanCount;
outRect.left = column * spacing / spanCount;
outRect.right = spacing - (column + 1) * spacing / spanCount;
if (position < spanCount) outRect.top = spacing;
outRect.bottom = spacing;
}
}
}

View file

@ -0,0 +1,67 @@
package awais.instagrabber.customviews.helpers;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.interfaces.LazyLoadListener;
// thanks to nesquena's EndlessRecyclerViewScrollListener
// https://gist.github.com/nesquena/d09dc68ff07e845cc622
public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
private int currentPage = 0; // The current offset index of data you have loaded
private int previousTotalItemCount = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private final int visibleThreshold; // The minimum amount of items to have below your current scroll position before loading more.
private final LazyLoadListener lazyLoadListener;
private final RecyclerView.LayoutManager layoutManager;
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener) {
this.layoutManager = layoutManager;
this.lazyLoadListener = lazyLoadListener;
if (layoutManager instanceof GridLayoutManager) {
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount());
} else if (layoutManager instanceof LinearLayoutManager) {
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8;
} else {
this.visibleThreshold = 5;
}
}
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
final int totalItemCount = layoutManager.getItemCount();
if (totalItemCount < previousTotalItemCount) {
currentPage = 0;
previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) loading = true;
}
if (loading && totalItemCount > previousTotalItemCount) {
loading = false;
previousTotalItemCount = totalItemCount;
}
final int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
} else {
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
}
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
if (lazyLoadListener != null) lazyLoadListener.onLoadMore(++currentPage, totalItemCount);
loading = true;
}
}
public void resetState() {
this.currentPage = 0;
this.previousTotalItemCount = 0;
this.loading = true;
}
}

View file

@ -0,0 +1,34 @@
package awais.instagrabber.customviews.helpers;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import awais.instagrabber.interfaces.SwipeEvent;
public final class SwipeGestureListener extends GestureDetector.SimpleOnGestureListener {
public static final int SWIPE_THRESHOLD = 200;
public static final int SWIPE_VELOCITY_THRESHOLD = 200;
private final SwipeEvent swipeEvent;
public SwipeGestureListener(final SwipeEvent swipeEvent) {
this.swipeEvent = swipeEvent;
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
try {
final float diffY = e2.getY() - e1.getY();
final float diffX = e2.getX() - e1.getX();
final float diffXAbs = Math.abs(diffX);
if (diffXAbs > Math.abs(diffY) && diffXAbs > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) swipeEvent.onSwipe(true);
else swipeEvent.onSwipe(false);
return true;
}
} catch (final Exception e) {
Log.e("AWAISKING_APP", "", e);
}
return false;
}
}

View file

@ -0,0 +1,282 @@
package awais.instagrabber.customviews.helpers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
// wasted around 3 hours to get this working, made from scrach, forgot to take a shower so i'm gonna go take a shower (time: May 11, 2020 @ 8:09:30 PM)
public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
private static final Object LOCK = new Object();
private LinearLayoutManager layoutManager;
private View firstItemView, lastItemView;
private int videoPosShown = -1, lastVideoPos = -1, lastChangedVideoPos, lastStoppedVideoPos, lastPlayedVideoPos;
private boolean videoAttached = false;
private final List<FeedModel> feedModels;
////////////////////////////////////////////////////
private SimpleExoPlayer player;
private ImageView btnMute;
private final Context context;
private final View.OnClickListener commentClickListener = new View.OnClickListener() {
@Override
public void onClick(@NonNull final View v) {
final Object tag = v.getTag();
if (tag instanceof FeedModel && context instanceof Activity) {
if (player != null) player.setPlayWhenReady(false);
((Activity) context).startActivityForResult(new Intent(context, CommentsViewer.class)
.putExtra(Constants.EXTRAS_SHORTCODE, ((FeedModel) tag).getShortCode()), 6969);
}
}
};
private final View.OnClickListener muteClickListener = v -> {
if (player == null) return;
final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol);
if (btnMute != null) btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute);
Utils.sessionVolumeFull = intVol == 1f;
};
private final VideoChangeCallback videoChangeCallback;
// private final ScrollerVideoCallback videoCallback;
// private View lastVideoHolder;
// private int videoState = -1;
public VideoAwareRecyclerScroller(final Context context, final List<FeedModel> feedModels,
final VideoChangeCallback videoChangeCallback) {
this.context = context;
this.feedModels = feedModels;
this.videoChangeCallback = videoChangeCallback;
}
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
if (layoutManager == null) {
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) this.layoutManager = (LinearLayoutManager) layoutManager;
}
if (feedModels.size() > 0 && layoutManager != null) {
int firstVisibleItemPos = layoutManager.findFirstCompletelyVisibleItemPosition();
int lastVisibleItemPos = layoutManager.findLastCompletelyVisibleItemPosition();
if (firstVisibleItemPos == -1 && lastVisibleItemPos == -1) {
firstVisibleItemPos = layoutManager.findFirstVisibleItemPosition();
lastVisibleItemPos = layoutManager.findLastVisibleItemPosition();
}
boolean processFirstItem = false, processLastItem = false;
View currView;
if (firstVisibleItemPos != -1) {
currView = layoutManager.findViewByPosition(firstVisibleItemPos);
if (currView != null && currView.getId() == R.id.videoHolder) {
firstItemView = currView;
processFirstItem = true;
}
}
if (lastVisibleItemPos != -1) {
currView = layoutManager.findViewByPosition(lastVisibleItemPos);
if (currView != null && currView.getId() == R.id.videoHolder) {
lastItemView = currView;
processLastItem = true;
}
}
final Rect visibleItemRect = new Rect();
int firstVisibleItemHeight = 0, lastVisibleItemHeight = 0;
final boolean isFirstItemVideoHolder = firstItemView != null && firstItemView.getId() == R.id.videoHolder;
if (isFirstItemVideoHolder) {
firstItemView.getGlobalVisibleRect(visibleItemRect);
firstVisibleItemHeight = visibleItemRect.height();
}
final boolean isLastItemVideoHolder = lastItemView != null && lastItemView.getId() == R.id.videoHolder;
if (isLastItemVideoHolder) {
lastItemView.getGlobalVisibleRect(visibleItemRect);
lastVisibleItemHeight = visibleItemRect.height();
}
if (processFirstItem && firstVisibleItemHeight > lastVisibleItemHeight) videoPosShown = firstVisibleItemPos;
else if (processLastItem && lastVisibleItemHeight != 0) videoPosShown = lastVisibleItemPos;
if (firstItemView != lastItemView) {
final int mox = lastVisibleItemHeight - firstVisibleItemHeight;
if (processLastItem && lastVisibleItemHeight > firstVisibleItemHeight) videoPosShown = lastVisibleItemPos;
if ((processFirstItem || processLastItem) && mox >= 0) videoPosShown = lastVisibleItemPos;
}
if (lastChangedVideoPos != -1 && lastVideoPos != -1) {
currView = layoutManager.findViewByPosition(lastChangedVideoPos);
if (currView != null && currView.getId() == R.id.videoHolder &&
lastStoppedVideoPos != lastChangedVideoPos && lastPlayedVideoPos != lastChangedVideoPos) {
lastStoppedVideoPos = lastChangedVideoPos;
stopVideo(lastChangedVideoPos, recyclerView, currView);
}
currView = layoutManager.findViewByPosition(lastVideoPos);
if (currView != null && currView.getId() == R.id.videoHolder) {
final Rect rect = new Rect();
currView.getGlobalVisibleRect(rect);
final int holderTop = currView.getTop();
final int holderHeight = currView.getBottom() - holderTop;
final int halfHeight = holderHeight / 2;
//halfHeight -= halfHeight / 5;
if (rect.height() < halfHeight) {
if (lastStoppedVideoPos != lastVideoPos) {
lastStoppedVideoPos = lastVideoPos;
stopVideo(lastVideoPos, recyclerView, currView);
}
} else if (lastPlayedVideoPos != lastVideoPos) {
lastPlayedVideoPos = lastVideoPos;
playVideo(lastVideoPos, recyclerView, currView);
}
}
if (lastChangedVideoPos != lastVideoPos) lastChangedVideoPos = lastVideoPos;
}
if (lastVideoPos != -1 && lastVideoPos != videoPosShown) {
if (videoAttached) {
//if ((currView = layoutManager.findViewByPosition(lastVideoPos)) != null && currView.getId() == R.id.videoHolder)
releaseVideo(lastVideoPos, recyclerView, null);
videoAttached = false;
}
}
if (videoPosShown != -1) {
lastVideoPos = videoPosShown;
if (!videoAttached) {
if ((currView = layoutManager.findViewByPosition(videoPosShown)) != null && currView.getId() == R.id.videoHolder)
attachVideo(videoPosShown, recyclerView, currView);
videoAttached = true;
}
}
}
}
private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
synchronized (LOCK) {
if (recyclerView != null) {
final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
if (adapter instanceof FeedAdapter) {
final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
}
}
if (player != null) {
player.stop(true);
player.release();
player = null;
}
player = new SimpleExoPlayer.Builder(context).build();
if (itemView != null) {
final Object tag = itemView.getTag();
final View btnComments = itemView.findViewById(R.id.btnComments);
if (btnComments != null && tag instanceof FeedModel) {
final FeedModel feedModel = (FeedModel) tag;
if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
else {
btnComments.setTag(feedModel);
btnComments.setEnabled(true);
btnComments.setOnClickListener(commentClickListener);
}
}
final PlayerView playerView = itemView.findViewById(R.id.playerView);
if (playerView == null) return;
playerView.setPlayer(player);
if (player != null) {
btnMute = itemView.findViewById(R.id.btnMute);
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
if (btnMute != null) {
btnMute.setVisibility(View.VISIBLE);
btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
btnMute.setOnClickListener(muteClickListener);
}
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram"))
.createMediaSource(Uri.parse(feedModels.get(itemPos).getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setVolume(vol);
playerView.setOnClickListener(muteClickListener);
}
}
if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
}
}
private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// Log.d("AWAISKING_APP", "release: " + itemPos);
// if (player != null) {
// player.stop(true);
// player.release();
// }
// player = null;
}
private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) {
// final int playbackState = player.getPlaybackState();
// if (!player.isPlaying()
// || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// ) {
// player.setPlayWhenReady(true);
// }
// }
// if (player != null) {
// player.setPlayWhenReady(true);
// player.getPlaybackState();
// }
}
private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
if (player != null) {
player.setPlayWhenReady(false);
player.getPlaybackState();
}
}
public interface VideoChangeCallback {
void playerChanged(final int itemPos, final SimpleExoPlayer player);
}
}

View file

@ -0,0 +1,252 @@
package awais.instagrabber.customviews.masoudss_waveform;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.nfc.FormatException;
import android.os.Build;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
final class SoundParser {
private ProgressListener progressListener;
int[] frameGains;
//////////////////
private static String[] supportedExtensions = {"mp3", "wav", "3gpp", "3gp", "amr", "aac", "m4a", "ogg"};
private static ArrayList<String> additionalExtensions = new ArrayList<>();
static void addCustomExtension(final String extension) {
additionalExtensions.add(extension);
}
static void removeCustomExtension(final String extension) {
additionalExtensions.remove(extension);
}
static void addCustomExtensions(final List<String> extensions) {
additionalExtensions.addAll(extensions);
}
static void removeCustomExtensions(final List<String> extensions) {
additionalExtensions.removeAll(extensions);
}
private static boolean isFilenameSupported(final String filename) {
for (final String supportedExtension : supportedExtensions)
if (filename.endsWith('.' + supportedExtension)) return true;
for (final String additionalExtension : additionalExtensions)
if (filename.endsWith('.' + additionalExtension)) return true;
return false;
}
@NonNull
public static SoundParser create(final String fileName, final boolean ignoreExtension) throws IOException, FormatException {
if (!ignoreExtension && !isFilenameSupported(fileName))
throw new FormatException("Not supported file extension.");
final File f = new File(fileName);
if (!f.exists()) throw new FileNotFoundException(fileName);
final SoundParser soundFile = new SoundParser();
soundFile.readFile(f);
return soundFile;
}
public void setProgressListener(final ProgressListener progressListener) {
this.progressListener = progressListener;
}
@SuppressWarnings("deprecation")
private void readFile(@NonNull final File inputFile) throws IOException, FormatException {
final MediaExtractor extractor = new MediaExtractor();
MediaFormat format = null;
final int fileSizeBytes = (int) inputFile.length();
extractor.setDataSource(inputFile.getPath());
final int numTracks = extractor.getTrackCount();
int i = 0;
while (i < numTracks) {
format = extractor.getTrackFormat(i);
if (Objects.requireNonNull(format.getString(MediaFormat.KEY_MIME)).startsWith("audio/")) {
extractor.selectTrack(i);
break;
}
i++;
}
if (i == numTracks) throw new FormatException("No audio track found in " + inputFile);
assert format != null;
final int channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
final int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
final int expectedNumSamples = (int) (format.getLong(MediaFormat.KEY_DURATION) / 1000000f * sampleRate + 0.5f);
final MediaCodec codec = MediaCodec.createDecoderByType(Objects.requireNonNull(format.getString(MediaFormat.KEY_MIME)));
codec.configure(format, null, null, 0);
codec.start();
final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
final ByteBuffer[] inputBuffers = codec.getInputBuffers();
boolean firstSampleData = true, doneReading = false;
long presentationTime;
int sampleSize, decodedSamplesSize = 0, totSizeRead = 0;
byte[] decodedSamples = null;
ByteBuffer mDecodedBytes = ByteBuffer.allocate(1 << 20);
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
while (true) {
final int inputBufferIndex = codec.dequeueInputBuffer(100);
if (!doneReading && inputBufferIndex >= 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
sampleSize = extractor.readSampleData(Objects.requireNonNull(codec.getInputBuffer(inputBufferIndex)), 0);
else
sampleSize = extractor.readSampleData(inputBuffers[inputBufferIndex], 0);
if (firstSampleData && sampleSize == 2 && "audio/mp4a-latm".equals(format.getString(MediaFormat.KEY_MIME))) {
extractor.advance();
totSizeRead += sampleSize;
} else if (sampleSize < 0) {
codec.queueInputBuffer(inputBufferIndex, 0, 0, -1, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
doneReading = true;
} else {
presentationTime = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTime, 0);
extractor.advance();
totSizeRead += sampleSize;
if (progressListener != null && !progressListener.reportProgress((double) totSizeRead / fileSizeBytes)) {
// We are asked to stop reading the file. Returning immediately.
// The SoundFile object is invalid and should NOT be used afterward!
extractor.release();
codec.stop();
codec.release();
return;
}
}
firstSampleData = false;
}
// Get decoded stream from the decoder output buffers.
final int outputBufferIndex = codec.dequeueOutputBuffer(info, 100);
if (outputBufferIndex >= 0 && info.size > 0) {
if (decodedSamplesSize < info.size) {
decodedSamplesSize = info.size;
decodedSamples = new byte[decodedSamplesSize];
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
assert outputBuffer != null;
outputBuffer.get(decodedSamples, 0, info.size);
outputBuffer.clear();
} else {
outputBuffers[outputBufferIndex].get(decodedSamples, 0, info.size);
outputBuffers[outputBufferIndex].clear();
}
// Check if buffer is big enough. Resize it if it's too small.
if (mDecodedBytes.remaining() < info.size) {
// Getting a rough estimate of the total size, allocate 20% more, and
// make sure to allocate at least 5MB more than the initial size.
final int position = mDecodedBytes.position();
int newSize = (int) (position * (1.0 * fileSizeBytes / totSizeRead) * 1.2);
final int infoSize = info.size + 5 * (1 << 20);
if (newSize - position < infoSize)
newSize = position + infoSize;
ByteBuffer newDecodedBytes = null;
// Try to allocate memory. If we are OOM, try to run the garbage collector.
int retry = 10;
while (retry > 0) {
try {
newDecodedBytes = ByteBuffer.allocate(newSize);
break;
} catch (final OutOfMemoryError e) {
retry--;
}
}
if (retry == 0) break;
mDecodedBytes.rewind();
assert newDecodedBytes != null;
newDecodedBytes.put(mDecodedBytes);
mDecodedBytes = newDecodedBytes;
mDecodedBytes.position(position);
}
mDecodedBytes.put(decodedSamples, 0, info.size);
codec.releaseOutputBuffer(outputBufferIndex, false);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
outputBuffers = codec.getOutputBuffers();
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 || mDecodedBytes.position() / (2 * channels) >= expectedNumSamples)
break;
}
final int numSamples = mDecodedBytes.position() / (channels * 2); // One sample = 2 bytes.
mDecodedBytes.rewind();
mDecodedBytes.order(ByteOrder.LITTLE_ENDIAN);
final ShortBuffer mDecodedSamples = mDecodedBytes.asShortBuffer();
// final int avgBitrateKbps = (int) (fileSizeBytes * 8F * ((float) sampleRate / numSamples) / 1000F);
extractor.release();
codec.stop();
codec.release();
final int samplesPerFrame = 1024;
int numFrames = numSamples / samplesPerFrame;
if (numSamples % samplesPerFrame != 0) numFrames++;
frameGains = new int[numFrames];
// final int[] mFrameLens = new int[numFrames];
// final int[] mFrameOffsets = new int[numFrames];
// final int frameLens = (int) (1000F * avgBitrateKbps / 8F * ((float) samplesPerFrame / sampleRate));
int j, gain, value;
i = 0;
while (i < numFrames) {
gain = -1;
j = 0;
while (j < samplesPerFrame) {
value = 0;
for (int k = 0; k < channels; ++k)
if (mDecodedSamples.remaining() > 0)
value += Math.abs(mDecodedSamples.get());
value /= channels;
if (gain < value) gain = value;
j++;
}
frameGains[i] = (int) Math.sqrt(gain);
// mFrameLens[i] = frameLens;
// mFrameOffsets[i] = (int) ((float) i * (1000F * avgBitrateKbps / 8F) * ((float) samplesPerFrame / sampleRate));
i++;
}
mDecodedSamples.rewind();
}
private interface ProgressListener {
boolean reportProgress(final double fractionComplete);
}
}

View file

@ -0,0 +1,5 @@
package awais.instagrabber.customviews.masoudss_waveform;
public interface WaveFormProgressChangeListener {
void onProgressChanged(final WaveformSeekBar waveformSeekBar, final int progress, final boolean fromUser);
}

View file

@ -0,0 +1,7 @@
package awais.instagrabber.customviews.masoudss_waveform;
public enum WaveGravity {
TOP,
CENTER,
BOTTOM,
}

View file

@ -0,0 +1,225 @@
package awais.instagrabber.customviews.masoudss_waveform;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import awais.instagrabber.R;
import awais.instagrabber.utils.Utils;
public final class WaveformSeekBar extends View {
private final int mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private final Paint mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mWaveRect = new RectF();
private final Canvas mProgressCanvas = new Canvas();
private final WaveGravity waveGravity = WaveGravity.BOTTOM;
private final int waveBackgroundColor;
private final int waveProgressColor;
private final float waveWidth = Utils.convertDpToPx(3);
private final float waveMinHeight = Utils.convertDpToPx(4);
private final float waveCornerRadius = Utils.convertDpToPx(2);
private final float waveGap = Utils.convertDpToPx(1);
private int mCanvasWidth = 0;
private int mCanvasHeight = 0;
private float mTouchDownX = 0F;
private int[] sample;
private int progress = 0;
private WaveFormProgressChangeListener progressChangeListener;
public WaveformSeekBar(final Context context) {
this(context, null);
}
public WaveformSeekBar(final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveformSeekBar(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.waveBackgroundColor = ContextCompat.getColor(context, R.color.text_color_light);
this.waveProgressColor = ContextCompat.getColor(context, R.color.text_color_dark);
}
private int getSampleMax() {
int max = -1;
if (sample != null) for (final int i : sample) if (i >= max) max = i;
return max;
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (sample != null && sample.length != 0) {
final int availableWidth = getAvailableWidth();
final int availableHeight = getAvailableHeight();
final float step = availableWidth / (waveGap + waveWidth) / sample.length;
float i = 0F;
float lastWaveRight = (float) getPaddingLeft();
final int sampleMax = getSampleMax();
while (i < sample.length) {
float waveHeight = availableHeight * ((float) sample[(int) i] / sampleMax);
if (waveHeight < waveMinHeight)
waveHeight = waveMinHeight;
final float top;
if (waveGravity == WaveGravity.TOP) {
top = (float) getPaddingTop();
} else if (waveGravity == WaveGravity.CENTER) {
top = (float) getPaddingTop() + availableHeight / 2F - waveHeight / 2F;
} else if (waveGravity == WaveGravity.BOTTOM) {
top = mCanvasHeight - (float) getPaddingBottom() - waveHeight;
} else {
top = 0;
}
mWaveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight);
if (mWaveRect.contains(availableWidth * progress / 100F, mWaveRect.centerY())) {
int bitHeight = (int) mWaveRect.height();
if (bitHeight <= 0) bitHeight = (int) waveWidth;
final Bitmap bitmap = Bitmap.createBitmap(availableWidth, bitHeight, Bitmap.Config.ARGB_8888);
mProgressCanvas.setBitmap(bitmap);
float fillWidth = availableWidth * progress / 100F;
mWavePaint.setColor(waveProgressColor);
mProgressCanvas.drawRect(0F, 0F, fillWidth, mWaveRect.bottom, mWavePaint);
mWavePaint.setColor(waveBackgroundColor);
mProgressCanvas.drawRect(fillWidth, 0F, (float) availableWidth, mWaveRect.bottom, mWavePaint);
mWavePaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
} else {
mWavePaint.setColor(mWaveRect.right <= availableWidth * progress / 100F ? waveProgressColor : waveBackgroundColor);
mWavePaint.setShader(null);
}
canvas.drawRoundRect(mWaveRect, waveCornerRadius, waveCornerRadius, mWavePaint);
lastWaveRight = mWaveRect.right + waveGap;
if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft())
break;
i += 1 / step;
}
}
}
@Override
public boolean onTouchEvent(final MotionEvent event) {
if (!isEnabled()) return false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (isParentScrolling()) mTouchDownX = event.getX();
else updateProgress(event);
break;
case MotionEvent.ACTION_MOVE:
updateProgress(event);
break;
case MotionEvent.ACTION_UP:
if (Math.abs(event.getX() - mTouchDownX) > mScaledTouchSlop)
updateProgress(event);
performClick();
break;
}
return true;
}
@Override
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCanvasWidth = w;
mCanvasHeight = h;
}
@Override
public boolean performClick() {
super.performClick();
return true;
}
private boolean isParentScrolling() {
View parent = (View) getParent();
final View root = getRootView();
while (true) {
if (parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1) ||
parent.canScrollVertically(1) || parent.canScrollVertically(-1))
return true;
if (parent == root) return false;
parent = (View) parent.getParent();
}
}
private void updateProgress(@NonNull final MotionEvent event) {
progress = (int) (100 * event.getX() / getAvailableWidth());
invalidate();
if (progressChangeListener != null)
progressChangeListener.onProgressChanged(this, Math.min(Math.max(0, progress), 100), true);
}
private int getAvailableWidth() {
return mCanvasWidth - getPaddingLeft() - getPaddingRight();
}
private int getAvailableHeight() {
return mCanvasHeight - getPaddingTop() - getPaddingBottom();
}
// public void setSampleFrom(final String path, final boolean ignoreExtension) { // was false
// try {
// final SoundParser soundFile = SoundParser.create(path, ignoreExtension);
// sample = soundFile.frameGains;
// } catch (final Exception e) {
// sample = null;
// }
// }
//
// public void setSampleFrom(@NonNull final File file, final boolean ignoreExtension) { // was false
// setSampleFrom(file.getAbsolutePath(), ignoreExtension);
// }
public void setProgress(final int progress) {
this.progress = progress;
invalidate();
}
public void setProgressChangeListener(final WaveFormProgressChangeListener progressChangeListener) {
this.progressChangeListener = progressChangeListener;
}
public void setSample(final int[] sample) {
if (sample != this.sample) {
this.sample = sample;
invalidate();
}
}
}

View file

@ -0,0 +1,98 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.RelativeSizeSpan;
import android.text.style.URLSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.LinearLayoutCompat;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R;
import awais.instagrabber.utils.Utils;
public final class AboutDialog extends BottomSheetDialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
final View contentView = View.inflate(getContext(), R.layout.dialog_main_about, null);
final LinearLayoutCompat infoContainer = contentView.findViewById(R.id.infoContainer);
final View btnTelegram = infoContainer.getChildAt(1);
final View btnProject = infoContainer.getChildAt(2);
final View.OnClickListener onClickListener = v -> {
final Intent intent = new Intent(Intent.ACTION_VIEW);
if (v == btnTelegram) {
intent.setData(Uri.parse("https://t.me/grabber_app"));
if (!Utils.isEmpty(Utils.telegramPackage))
intent.setPackage(Utils.telegramPackage);
} else
intent.setData(Uri.parse("https://gitlab.com/AwaisKing/instagrabber/"));
startActivity(intent);
};
btnProject.setOnClickListener(onClickListener);
btnTelegram.setOnClickListener(onClickListener);
final String description = getString(R.string.description);
if (!Utils.isEmpty(description)) {
final SpannableStringBuilder descriptionText = new SpannableStringBuilder(description, 0, description.length());
int lastIndex = descriptionText.length() / 2;
for (int i = 0; i < descriptionText.length(); ++i) {
char c = descriptionText.charAt(i);
if (c == '[') {
final int smallTextStart = i;
descriptionText.delete(i, i + 1);
do {
c = descriptionText.charAt(i);
if (c == ']') {
descriptionText.delete(i, i + 1);
descriptionText.setSpan(new RelativeSizeSpan(0.5f), smallTextStart, i, 0);
}
++i;
} while (c != ']' || i == descriptionText.length() - 1);
} else if (c == '{') {
final int smallerTextStart = i;
descriptionText.delete(i, i + 1);
i = smallerTextStart;
do {
c = descriptionText.charAt(i);
if (c == '}') {
descriptionText.delete(i, i + 1);
descriptionText.setSpan(new RelativeSizeSpan(0.35f), smallerTextStart, i, 0);
}
++i;
lastIndex = i;
} while (c != '}' || i == descriptionText.length() - 1);
}
}
lastIndex = Utils.indexOfChar(descriptionText, '@', lastIndex);
descriptionText.setSpan(new URLSpan("https://t.me/awais404"), lastIndex, lastIndex + 9, 0);
lastIndex = Utils.indexOfChar(descriptionText, ':', lastIndex + 9) + 2;
descriptionText.setSpan(new URLSpan("mailto:chapter50000@hotmail.com"), lastIndex, lastIndex + 24, 0);
final TextView textView = (TextView) infoContainer.getChildAt(0);
textView.setMovementMethod(new LinkMovementMethod());
textView.setText(descriptionText, TextView.BufferType.SPANNABLE);
}
dialog.setContentView(contentView);
return dialog;
}
}

View file

@ -0,0 +1,62 @@
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) { }
}

View file

@ -0,0 +1,169 @@
package awais.instagrabber.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.activities.Main;
import awais.instagrabber.adapters.SimpleAdapter;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener,
View.OnClickListener, View.OnLongClickListener {
private boolean cookieChanged, isQuery;
private Activity activity;
private String userQuery;
private View btnFavorite, btnImportExport;
private SimpleAdapter<DataBox.FavoriteModel> favoritesAdapter;
public QuickAccessDialog setQuery(final String userQuery) {
this.userQuery = userQuery;
return this;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setOnShowListener(this);
final Context context = getContext();
activity = context instanceof Activity ? (Activity) context : getActivity();
final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null);
btnFavorite = contentView.findViewById(R.id.btnFavorite);
btnImportExport = contentView.findViewById(R.id.importExport);
isQuery = !Utils.isEmpty(userQuery);
btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE);
Utils.setTooltipText(btnImportExport, R.string.import_export);
favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this);
btnFavorite.setOnClickListener(this);
btnImportExport.setOnClickListener(this);
final RecyclerView rvFavorites = contentView.findViewById(R.id.rvFavorites);
final RecyclerView rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess);
final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL);
rvFavorites.addItemDecoration(itemDecoration);
rvFavorites.setAdapter(favoritesAdapter);
final String cookieStr = settingsHelper.getString(Constants.COOKIE);
if (!Utils.isEmpty(cookieStr)
|| Utils.dataBox.getCookieCount() > 0 // fallback for export / import
) {
rvQuickAccess.addItemDecoration(itemDecoration);
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (!Utils.isEmpty(cookieStr) && allCookies != null) {
for (final DataBox.CookieModel cookie : allCookies) {
if (cookieStr.equals(cookie.getCookie())) {
cookie.setSelected(true);
break;
}
}
}
rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this));
} else {
((View) rvQuickAccess.getParent()).setVisibility(View.GONE);
}
dialog.setContentView(contentView);
return dialog;
}
@Override
public void onClick(@NonNull final View v) {
final Object tag = v.getTag();
if (v == btnFavorite) {
if (isQuery) {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis()));
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites());
}
} else if (v == btnImportExport) {
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(Utils.PERMS, 6007);
else Utils.showImportExportDialog(v.getContext());
} else if (tag instanceof DataBox.FavoriteModel) {
if (Main.scanHack != null) {
Main.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery());
dismiss();
}
} else if (tag instanceof DataBox.CookieModel) {
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag;
if (!cookieModel.isSelected()) {
settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie());
Utils.setupCookies(cookieModel.getCookie());
cookieChanged = true;
}
dismiss();
}
}
@Override
public boolean onLongClick(@NonNull final View v) {
final Object tag = v.getTag();
if (tag instanceof DataBox.FavoriteModel) {
final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag;
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> Utils.dataBox.delFavorite(favoriteModel))
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete,
favoriteModel.getQuery())).show();
} else if (tag instanceof DataBox.CookieModel) {
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag;
if (cookieModel.isSelected())
Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show();
else
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> Utils.dataBox.delUserCookie(cookieModel))
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete,
cookieModel.getUsername())).show();
}
return true;
}
@Override
public void onDismiss(@NonNull final DialogInterface dialog) {
super.onDismiss(dialog);
if (cookieChanged && activity != null) activity.recreate();
}
@Override
public void onShow(final DialogInterface dialog) {
if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG))
new AlertDialog.Builder(activity)
.setMessage(R.string.quick_access_info_dialog)
.setPositiveButton(R.string.ok, null)
.setNeutralButton(R.string.dont_show_again, (d, which) ->
settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show();
}
}

View file

@ -0,0 +1,213 @@
package awais.instagrabber.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R;
import awais.instagrabber.activities.Login;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.Utils;
import awaisomereport.CrashReporter;
import static awais.instagrabber.utils.Constants.APP_LANGUAGE;
import static awais.instagrabber.utils.Constants.APP_THEME;
import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS;
import static awais.instagrabber.utils.Constants.AUTOPLAY_VIDEOS;
import static awais.instagrabber.utils.Constants.BOTTOM_TOOLBAR;
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.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.SHOW_FEED;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SettingsDialog extends BottomSheetDialogFragment implements View.OnClickListener, AdapterView.OnItemSelectedListener,
CompoundButton.OnCheckedChangeListener {
private Activity activity;
private FragmentManager fragmentManager;
private View btnSaveTo, btnImportExport, btnLogin, btnTimeSettings, btnReport;
private Spinner spAppTheme, spLanguage;
private boolean somethingChanged = false;
private int currentTheme, currentLanguage, selectedLanguage;
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
if (requestCode != 6200) return;
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) showDirectoryChooser();
else Toast.makeText(activity, R.string.direct_download_perms_ask, Toast.LENGTH_SHORT).show();
}
private void showDirectoryChooser() {
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager == null) fragmentManager = getChildFragmentManager();
new DirectoryChooser().setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(path -> {
settingsHelper.putString(FOLDER_PATH, path);
somethingChanged = true;
}).show(fragmentManager, null);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
final Context context = getContext();
activity = context instanceof Activity ? (Activity) context : getActivity();
fragmentManager = getFragmentManager();
if (fragmentManager == null) fragmentManager = getChildFragmentManager();
final View contentView = View.inflate(activity, R.layout.dialog_main_settings, null);
btnLogin = contentView.findViewById(R.id.btnLogin);
btnSaveTo = contentView.findViewById(R.id.btnSaveTo);
btnImportExport = contentView.findViewById(R.id.importExport);
btnTimeSettings = contentView.findViewById(R.id.btnTimeSettings);
btnReport = contentView.findViewById(R.id.btnReport);
Utils.setTooltipText(btnImportExport, R.string.import_export);
btnLogin.setOnClickListener(this);
btnReport.setOnClickListener(this);
btnSaveTo.setOnClickListener(this);
btnImportExport.setOnClickListener(this);
btnTimeSettings.setOnClickListener(this);
spAppTheme = contentView.findViewById(R.id.spAppTheme);
currentTheme = settingsHelper.getInteger(APP_THEME);
spAppTheme.setSelection(currentTheme);
spAppTheme.setOnItemSelectedListener(this);
spLanguage = contentView.findViewById(R.id.spLanguage);
currentLanguage = settingsHelper.getInteger(APP_LANGUAGE);
spLanguage.setSelection(currentLanguage);
spLanguage.setOnItemSelectedListener(this);
final AppCompatCheckBox cbSaveTo = contentView.findViewById(R.id.cbSaveTo);
final AppCompatCheckBox cbShowFeed = contentView.findViewById(R.id.cbShowFeed);
final AppCompatCheckBox cbMuteVideos = contentView.findViewById(R.id.cbMuteVideos);
final AppCompatCheckBox cbBottomToolbar = contentView.findViewById(R.id.cbBottomToolbar);
final AppCompatCheckBox cbAutoloadPosts = contentView.findViewById(R.id.cbAutoloadPosts);
final AppCompatCheckBox cbAutoplayVideos = contentView.findViewById(R.id.cbAutoplayVideos);
final AppCompatCheckBox cbDownloadUsername = contentView.findViewById(R.id.cbDownloadUsername);
cbSaveTo.setChecked(settingsHelper.getBoolean(FOLDER_SAVE_TO));
cbMuteVideos.setChecked(settingsHelper.getBoolean(MUTED_VIDEOS));
cbBottomToolbar.setChecked(settingsHelper.getBoolean(BOTTOM_TOOLBAR));
cbAutoplayVideos.setChecked(settingsHelper.getBoolean(AUTOPLAY_VIDEOS));
cbShowFeed.setChecked(settingsHelper.getBoolean(SHOW_FEED));
cbAutoloadPosts.setChecked(settingsHelper.getBoolean(AUTOLOAD_POSTS));
cbDownloadUsername.setChecked(settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER));
setupListener(cbSaveTo);
setupListener(cbShowFeed);
setupListener(cbMuteVideos);
setupListener(cbBottomToolbar);
setupListener(cbAutoloadPosts);
setupListener(cbAutoplayVideos);
setupListener(cbDownloadUsername);
btnSaveTo.setEnabled(cbSaveTo.isChecked());
dialog.setContentView(contentView);
return dialog;
}
private void setupListener(@NonNull final AppCompatCheckBox checkBox) {
checkBox.setOnCheckedChangeListener(this);
((View) checkBox.getParent()).setOnClickListener(this);
}
@Override
public void onItemSelected(final AdapterView<?> spinner, final View view, final int position, final long id) {
if (spinner == spAppTheme) {
if (position != currentTheme) {
settingsHelper.putInteger(APP_THEME, position);
somethingChanged = true;
}
} else if (spinner == spLanguage) {
selectedLanguage = position;
if (position != currentLanguage) {
settingsHelper.putInteger(APP_LANGUAGE, position);
somethingChanged = true;
}
}
}
@Override
public void onClick(final View v) {
if (v == btnLogin) {
startActivity(new Intent(v.getContext(), Login.class));
somethingChanged = true;
} else if (v == btnImportExport) {
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(Utils.PERMS, 6007);
else Utils.showImportExportDialog(activity);
} else if (v == btnTimeSettings) {
new TimeSettingsDialog().show(fragmentManager, null);
} else if (v == btnReport) {
CrashReporter.get(activity.getApplication()).zipLogs().startCrashEmailIntent(activity, true);
} else if (v == btnSaveTo) {
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(Utils.PERMS, 6200);
else showDirectoryChooser();
} else if (v instanceof ViewGroup)
((ViewGroup) v).getChildAt(0).performClick();
}
@Override
public void onCheckedChanged(@NonNull final CompoundButton checkBox, final boolean checked) {
final int id = checkBox.getId();
if (id == R.id.cbDownloadUsername) settingsHelper.putBoolean(DOWNLOAD_USER_FOLDER, checked);
else if (id == R.id.cbBottomToolbar) settingsHelper.putBoolean(BOTTOM_TOOLBAR, checked);
else if (id == R.id.cbAutoplayVideos) settingsHelper.putBoolean(AUTOPLAY_VIDEOS, checked);
else if (id == R.id.cbMuteVideos) settingsHelper.putBoolean(MUTED_VIDEOS, checked);
else if (id == R.id.cbAutoloadPosts) settingsHelper.putBoolean(AUTOLOAD_POSTS, checked);
else if (id == R.id.cbShowFeed) settingsHelper.putBoolean(SHOW_FEED, checked);
else if (id == R.id.cbSaveTo) {
settingsHelper.putBoolean(FOLDER_SAVE_TO, checked);
btnSaveTo.setEnabled(checked);
}
somethingChanged = true;
}
@Override
public void onDismiss(@NonNull final DialogInterface dialog) {
if (selectedLanguage != currentLanguage)
LocaleUtils.setLocale(activity != null ? activity.getBaseContext() : getLayoutInflater().getContext().getApplicationContext());
super.onDismiss(dialog);
if (somethingChanged && activity != null) activity.recreate();
}
@Override
public void onNothingSelected(final AdapterView<?> parent) { }
}

View file

@ -0,0 +1,173 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import awais.instagrabber.databinding.DialogTimeSettingsBinding;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class TimeSettingsDialog extends DialogFragment implements AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener,
View.OnClickListener, TextWatcher {
private DialogTimeSettingsBinding timeSettingsBinding;
private final Date magicDate;
private SimpleDateFormat currentFormat;
private String selectedFormat;
public TimeSettingsDialog() {
super();
final Calendar instance = GregorianCalendar.getInstance();
instance.set(2020, 5, 22, 8, 17, 13);
magicDate = instance.getTime();
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
final Dialog dialog = super.onCreateDialog(savedInstanceState);
timeSettingsBinding = DialogTimeSettingsBinding.inflate(LayoutInflater.from(getContext()));
timeSettingsBinding.cbCustomFormat.setOnCheckedChangeListener(this);
timeSettingsBinding.cbCustomFormat.setChecked(settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED));
timeSettingsBinding.etCustomFormat.setText(settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT));
final String[] dateTimeFormat = settingsHelper.getString(Constants.DATE_TIME_SELECTION).split(";"); // output = time;separator;date
timeSettingsBinding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0]));
timeSettingsBinding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1]));
timeSettingsBinding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2]));
timeSettingsBinding.cbSwapTimeDate.setOnCheckedChangeListener(this);
refreshTimeFormat();
timeSettingsBinding.spTimeFormat.setOnItemSelectedListener(this);
timeSettingsBinding.spDateFormat.setOnItemSelectedListener(this);
timeSettingsBinding.spSeparator.setOnItemSelectedListener(this);
timeSettingsBinding.etCustomFormat.addTextChangedListener(this);
timeSettingsBinding.btnConfirm.setOnClickListener(this);
timeSettingsBinding.btnInfo.setOnClickListener(this);
dialog.setContentView(timeSettingsBinding.getRoot());
return dialog;
}
private void refreshTimeFormat() {
if (timeSettingsBinding.cbCustomFormat.isChecked()) {
timeSettingsBinding.btnConfirm.setEnabled(false);
checkCustomTimeFormat();
} else {
final String sepStr = String.valueOf(timeSettingsBinding.spSeparator.getSelectedItem());
final String timeStr = String.valueOf(timeSettingsBinding.spTimeFormat.getSelectedItem());
final String dateStr = String.valueOf(timeSettingsBinding.spDateFormat.getSelectedItem());
final boolean isSwapTime = !timeSettingsBinding.cbSwapTimeDate.isChecked();
selectedFormat = (isSwapTime ? timeStr : dateStr)
+ (Utils.isEmpty(sepStr) || timeSettingsBinding.spSeparator.getSelectedItemPosition() == 0 ? " " : " '" + sepStr + "' ")
+ (isSwapTime ? dateStr : timeStr);
timeSettingsBinding.btnConfirm.setEnabled(true);
timeSettingsBinding.timePreview.setText((currentFormat = new SimpleDateFormat(selectedFormat, LocaleUtils.getCurrentLocale())).format(magicDate));
}
}
private void checkCustomTimeFormat() {
try {
//noinspection ConstantConditions
final String string = timeSettingsBinding.etCustomFormat.getText().toString();
if (Utils.isEmpty(string)) throw new NullPointerException();
final String format = (currentFormat = new SimpleDateFormat(string, LocaleUtils.getCurrentLocale())).format(magicDate);
timeSettingsBinding.timePreview.setText(format);
timeSettingsBinding.btnConfirm.setEnabled(true);
} catch (final Exception e) {
timeSettingsBinding.btnConfirm.setEnabled(false);
timeSettingsBinding.timePreview.setText(null);
}
}
@Override
public void onItemSelected(final AdapterView<?> p, final View v, final int pos, final long id) {
refreshTimeFormat();
}
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
if (buttonView == timeSettingsBinding.cbCustomFormat) {
timeSettingsBinding.etCustomFormat.setEnabled(isChecked);
timeSettingsBinding.btnInfo.setEnabled(isChecked);
timeSettingsBinding.spTimeFormat.setEnabled(!isChecked);
timeSettingsBinding.spDateFormat.setEnabled(!isChecked);
timeSettingsBinding.spSeparator.setEnabled(!isChecked);
timeSettingsBinding.cbSwapTimeDate.setEnabled(!isChecked);
}
refreshTimeFormat();
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
checkCustomTimeFormat();
}
@Override
public void onClick(final View v) {
if (v == timeSettingsBinding.btnConfirm) {
final String formatSelection;
final boolean isCustomFormat = timeSettingsBinding.cbCustomFormat.isChecked();
if (isCustomFormat) {
//noinspection ConstantConditions
formatSelection = timeSettingsBinding.etCustomFormat.getText().toString();
settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection);
} else {
formatSelection = timeSettingsBinding.spTimeFormat.getSelectedItemPosition() + ";"
+ timeSettingsBinding.spSeparator.getSelectedItemPosition() + ';'
+ timeSettingsBinding.spDateFormat.getSelectedItemPosition(); // time;separator;date
settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat);
settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelection);
}
settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat);
Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone();
dismiss();
} else if (v == timeSettingsBinding.btnInfo) {
timeSettingsBinding.customPanel.setVisibility(timeSettingsBinding.customPanel
.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
}
}
@Override
public void onNothingSelected(final AdapterView<?> parent) { }
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { }
@Override
public void afterTextChanged(final Editable s) { }
}

View file

@ -0,0 +1,153 @@
package awais.instagrabber.directdownload;
import android.Manifest;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import java.util.Arrays;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.CHANNEL_ID;
import static awais.instagrabber.utils.Utils.CHANNEL_NAME;
import static awais.instagrabber.utils.Utils.isChannelCreated;
import static awais.instagrabber.utils.Utils.notificationManager;
public final class DirectDownload extends Activity {
private boolean isFound = false;
private Intent intent;
private Context context;
@Override
public void onWindowAttributesChanged(final WindowManager.LayoutParams params) {
super.onWindowAttributesChanged(params);
if (!isFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
isFound = true;
checkIntent();
}
}
}
@Override
public Resources getResources() {
if (!isFound) {
intent = getIntent();
context = getApplicationContext();
if (intent != null && context != null) {
isFound = true;
checkIntent();
}
}
return super.getResources();
}
private synchronized void checkIntent() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
doDownload();
else {
final Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, R.string.direct_download_perms_ask, Toast.LENGTH_LONG).show();
handler.removeCallbacks(this);
}
});
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020);
}
finish();
}
private synchronized void doDownload() {
final String action = intent.getAction();
if (!Utils.isEmpty(action) && !Intent.ACTION_MAIN.equals(action)) {
boolean error = true;
String data = null;
final Bundle extras = intent.getExtras();
if (extras != null) {
final Object extraData = extras.get(Intent.EXTRA_TEXT);
if (extraData != null) {
error = false;
data = extraData.toString();
}
}
if (error) {
final Uri intentData = intent.getData();
if (intentData != null) data = intentData.toString();
}
if (data != null && !Utils.isEmpty(data)) {
final IntentModel model = Utils.stripString(data);
if (model != null && model.getType() == IntentModelType.POST) {
final String text = model.getText();
new PostFetcher(text, new FetchListener<ViewerPostModel[]>() {
@Override
public void doBefore() {
if (notificationManager == null)
notificationManager = NotificationManagerCompat.from(context.getApplicationContext());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isChannelCreated) {
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH));
isChannelCreated = true;
}
final Notification fetchingPostNotif = new NotificationCompat.Builder(context, CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_STATUS).setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(false).setPriority(NotificationCompat.PRIORITY_MIN)
.setContentText(context.getString(R.string.direct_download_loading)).build();
notificationManager.notify(1900000000, fetchingPostNotif);
}
@Override
public void onResult(final ViewerPostModel[] result) {
if (notificationManager != null) notificationManager.cancel(1900000000);
if (result != null) {
if (result.length == 1) {
Utils.batchDownload(context, result[0].getUsername(), DownloadMethod.DOWNLOAD_DIRECT,
Arrays.asList(result));
} else if (result.length > 1) {
context.startActivity(new Intent(context, MultiDirectDialog.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.putExtra(Constants.EXTRAS_POST, result));
}
}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
}
}

View file

@ -0,0 +1,117 @@
package awais.instagrabber.directdownload;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.Collections;
import awais.instagrabber.R;
import awais.instagrabber.activities.BaseLanguageActivity;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.models.enums.DownloadMethod;
public final class MultiDirectDialog extends BaseLanguageActivity {
public final ArrayList<BasePostModel> selectedItems = new ArrayList<>();
private PostsAdapter postsAdapter;
private MenuItem btnDownload;
private String username = null;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_direct);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final ViewerPostModel[] postModels;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST)
|| (postModels = (ViewerPostModel[]) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) {
Utils.errorFinish(this);
return;
}
username = postModels[0].getUsername();
toolbar.setTitle(username);
toolbar.setSubtitle(postModels[0].getShortCode());
final RecyclerView recyclerView = findViewById(R.id.mainPosts);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new GridAutofitLayoutManager(this, Utils.convertDpToPx(130)));
recyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
final ArrayList<PostModel> models = new ArrayList<>(postModels.length - 1);
for (final ViewerPostModel postModel : postModels)
models.add(new PostModel(postModel.getItemType(), postModel.getPostId(), postModel.getDisplayUrl(),
postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp()));
postsAdapter = new PostsAdapter(models, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
final PostModel postModel = (PostModel) tag;
if (postsAdapter.isSelecting) toggleSelection(postModel);
else {
Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(postModel));
finish();
}
}
}, v -> {
final Object tag = v.getTag();
if (tag instanceof PostModel) {
postsAdapter.isSelecting = true;
toggleSelection((PostModel) tag);
}
return true;
});
recyclerView.setAdapter(postsAdapter);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, selectedItems);
finish();
return true;
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
btnDownload = menu.findItem(R.id.action_download);
menu.findItem(R.id.action_search).setVisible(false);
return true;
}
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 (btnDownload != null) btnDownload.setVisible(postsAdapter.isSelecting);
}
}

View file

@ -0,0 +1,6 @@
package awais.instagrabber.interfaces;
public interface FetchListener<T> {
void onResult(T result);
default void doBefore() { }
}

View file

@ -0,0 +1,11 @@
package awais.instagrabber.interfaces;
import java.io.Serializable;
import java.util.List;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.enums.ItemGetType;
public interface ItemGetter extends Serializable {
List<? extends BasePostModel> get(final ItemGetType itemGetType);
}

View file

@ -0,0 +1,5 @@
package awais.instagrabber.interfaces;
public interface LazyLoadListener {
void onLoadMore(final int page, final int totalItemsCount);
}

View file

@ -0,0 +1,7 @@
package awais.instagrabber.interfaces;
import awais.instagrabber.customviews.RamboTextView;
public interface MentionClickListener {
void onClick(final RamboTextView view, final String text, final boolean isHashtag);
}

View file

@ -0,0 +1,5 @@
package awais.instagrabber.interfaces;
public interface OnGroupClickListener {
void toggleGroup(final int flatPos);
}

View file

@ -0,0 +1,5 @@
package awais.instagrabber.interfaces;
public interface SwipeEvent {
void onSwipe(final boolean isRight);
}

View file

@ -0,0 +1,82 @@
package awais.instagrabber.models;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Date;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils;
public abstract class BasePostModel implements Serializable {
protected String postId;
protected String displayUrl;
protected String shortCode;
protected CharSequence postCaption;
protected MediaItemType itemType;
protected boolean isSelected;
protected boolean isDownloaded;
protected long timestamp;
protected int position;
public MediaItemType getItemType() {
return itemType;
}
public final String getPostId() {
return postId;
}
public final String getDisplayUrl() {
return displayUrl;
}
public final CharSequence getPostCaption() {
return postCaption;
}
public final String getShortCode() {
return shortCode;
}
public final long getTimestamp() {
return timestamp;
}
public int getPosition() {
return this.position;
}
public boolean isSelected() {
return isSelected;
}
public boolean isDownloaded() {
return isDownloaded;
}
public void setItemType(final MediaItemType itemType) {
this.itemType = itemType;
}
public void setPostId(final String postId) {
this.postId = postId;
}
public void setPosition(final int position) {
this.position = position;
}
public void setSelected(final boolean selected) {
this.isSelected = selected;
}
public void setDownloaded(final boolean downloaded) {
isDownloaded = downloaded;
}
@NonNull
public final String getPostDate() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
}
}

View file

@ -0,0 +1,85 @@
package awais.instagrabber.models;
import androidx.annotation.NonNull;
import java.util.Date;
import awais.instagrabber.utils.Utils;
public final class CommentModel {
private final ProfileModel profileModel;
private final String id;
private final CharSequence text;
private final long likes, timestamp;
private CommentModel[] childCommentModels;
private boolean hasNextPage;
private String endCursor;
public CommentModel(final String id, final String text, final long timestamp, final long likes, final ProfileModel profileModel) {
this.id = id;
this.text = Utils.hasMentions(text) ? Utils.getMentionText(text) : text;
this.likes = likes;
this.timestamp = timestamp;
this.profileModel = profileModel;
}
public String getId() {
return id;
}
public CharSequence getText() {
return text;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
}
public long getLikes() {
return likes;
}
public ProfileModel getProfileModel() {
return profileModel;
}
public CommentModel[] getChildCommentModels() {
return childCommentModels;
}
public void setChildCommentModels(final CommentModel[] childCommentModels) {
this.childCommentModels = childCommentModels;
}
public void setPageCursor(final boolean hasNextPage, final String endCursor) {
this.hasNextPage = hasNextPage;
this.endCursor = endCursor;
}
public boolean hasNextPage() {
return hasNextPage;
}
public String getEndCursor() {
return endCursor;
}
// @NonNull
// @Override
// public String toString() {
// try {
// final JSONObject object = new JSONObject();
// object.put(Constants.EXTRAS_ID, id);
// object.put("text", text);
// object.put(Constants.EXTRAS_NAME, profileModel != null ? profileModel.getUsername() : "");
// if (childCommentModels != null) object.put("childComments", childCommentModels);
// return object.toString();
// } catch (Exception e) {
// return "{\"id\":\"" + id + "\", \"text\":\"" + text
// //(text != null ? text.replaceAll("\"", "\\\\\"") : "")
// + "\", \"name\":\"" + (profileModel != null ? profileModel.getUsername() : "") +
// (childCommentModels != null ? "\", \"childComments\":" + childCommentModels.length : "\"") + '}';
// }
// }
}

View file

@ -0,0 +1,29 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.MediaItemType;
public final class DiscoverItemModel extends BasePostModel {
private boolean moreAvailable;
private String nextMaxId;
public DiscoverItemModel(final MediaItemType mediaType, final String postId, final String shortCode, final String thumbnail) {
this.postId = postId;
this.itemType = mediaType;
this.shortCode = shortCode;
this.displayUrl = thumbnail;
}
public void setMore(final boolean moreAvailable, final String nextMaxId) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
}
public boolean hasMore() {
return moreAvailable;
}
public String getNextMaxId() {
return nextMaxId;
}
}

View file

@ -0,0 +1,56 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.MediaItemType;
public final class FeedModel extends PostModel {
private final ProfileModel profileModel;
private final long commentsCount, viewCount;
private boolean captionExpanded = false, mentionClicked = false;
private ViewerPostModel[] sliderItems;
public FeedModel(final ProfileModel profileModel, final MediaItemType itemType, final long viewCount, final String postId,
final String displayUrl, final String thumbnailUrl, final String shortCode, final String postCaption,
final long commentsCount, final long timestamp) {
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp);
this.profileModel = profileModel;
this.commentsCount = commentsCount;
this.viewCount = viewCount;
}
public ProfileModel getProfileModel() {
return profileModel;
}
public ViewerPostModel[] getSliderItems() {
return sliderItems;
}
public long getViewCount() {
return viewCount;
}
public long getCommentsCount() {
return commentsCount;
}
public boolean isCaptionExpanded() {
return captionExpanded;
}
public boolean isMentionClicked() {
return !mentionClicked;
}
public void setMentionClicked(final boolean mentionClicked) {
this.mentionClicked = mentionClicked;
}
public void setSliderItems(final ViewerPostModel[] sliderItems) {
this.sliderItems = sliderItems;
setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
}
public void toggleCaption() {
captionExpanded = !captionExpanded;
}
}

View file

@ -0,0 +1,30 @@
package awais.instagrabber.models;
import java.io.Serializable;
public final class FeedStoryModel implements Serializable {
private final String storyMediaId;
private final ProfileModel profileModel;
private StoryModel[] storyModels;
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel) {
this.storyMediaId = storyMediaId;
this.profileModel = profileModel;
}
public String getStoryMediaId() {
return storyMediaId;
}
public ProfileModel getProfileModel() {
return profileModel;
}
public void setStoryModels(final StoryModel[] storyModels) {
this.storyModels = storyModels;
}
public StoryModel[] getStoryModels() {
return storyModels;
}
}

View file

@ -0,0 +1,64 @@
package awais.instagrabber.models;
import androidx.annotation.Nullable;
import java.io.Serializable;
public final class FollowModel implements Serializable {
private final String id, username, fullName, profilePicUrl;
private String endCursor;
private boolean hasNextPage, isShown = true;
public FollowModel(final String id, final String username, final String fullName, final String profilePicUrl) {
this.id = id;
this.username = username;
this.fullName = fullName;
this.profilePicUrl = profilePicUrl;
}
public String getId() {
return id;
}
public String getUsername() {
return username;
}
public String getFullName() {
return fullName;
}
public String getProfilePicUrl() {
return profilePicUrl;
}
public boolean isShown() {
return isShown;
}
public void setShown(final boolean shown) {
isShown = shown;
}
public void setPageCursor(final boolean hasNextPage, final String endCursor) {
this.endCursor = endCursor;
this.hasNextPage = hasNextPage;
}
public boolean hasNextPage() {
return endCursor != null && hasNextPage;
}
public String getEndCursor() {
return endCursor;
}
@Override
public boolean equals(@Nullable final Object obj) {
if (obj instanceof FollowModel) {
final FollowModel model = (FollowModel) obj;
if (model.getId().equals(id) && model.getUsername().equals(username)) return true;
}
return super.equals(obj);
}
}

View file

@ -0,0 +1,27 @@
package awais.instagrabber.models;
public final class HighlightModel {
private final String title, thumbnailUrl;
private StoryModel[] storyModels;
public HighlightModel(final String title, final String thumbnailUrl) {
this.title = title;
this.thumbnailUrl = thumbnailUrl;
}
public String getTitle() {
return title;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public StoryModel[] getStoryModels() {
return storyModels;
}
public void setStoryModels(final StoryModel[] storyModels) {
this.storyModels = storyModels;
}
}

View file

@ -0,0 +1,21 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.IntentModelType;
public final class IntentModel {
private final IntentModelType type;
private final String text;
public IntentModel(final IntentModelType type, final String text) {
this.type = type;
this.text = text;
}
public IntentModelType getType() {
return type;
}
public String getText() {
return text;
}
}

View file

@ -0,0 +1,50 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.MediaItemType;
public class PostModel extends BasePostModel {
protected final String thumbnailUrl;
protected String endCursor;
protected boolean hasNextPage;
public PostModel(final String shortCode) {
this.shortCode = shortCode;
this.thumbnailUrl = null;
}
public PostModel(final MediaItemType itemType, final String postId, final String displayUrl, final String thumbnailUrl,
final String shortCode, final CharSequence postCaption, long timestamp) {
this.itemType = itemType;
this.postId = postId;
this.displayUrl = displayUrl;
this.thumbnailUrl = thumbnailUrl;
this.shortCode = shortCode;
this.postCaption = postCaption;
this.timestamp = timestamp;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public String getEndCursor() {
return endCursor;
}
public boolean hasNextPage() {
return endCursor != null && hasNextPage;
}
public void setPostCaption(final CharSequence postCaption) {
this.postCaption = postCaption;
}
public void setTimestamp(final long timestamp) {
this.timestamp = timestamp;
}
public void setPageCursor(final boolean hasNextPage, final String endCursor) {
this.endCursor = endCursor;
this.hasNextPage = hasNextPage;
}
}

View file

@ -0,0 +1,75 @@
package awais.instagrabber.models;
import java.io.Serializable;
public final class ProfileModel implements Serializable {
private final boolean isPrivate, isVerified;
private final long postCount, followersCount, followingCount;
private final String id, username, name, biography, url, sdProfilePic, hdProfilePic;
public ProfileModel(final boolean isPrivate, final boolean isVerified, final String id, final String username,
final String name, final String biography, final String url, final String sdProfilePic, final String hdProfilePic,
final long postCount, final long followersCount, final long followingCount) {
this.isPrivate = isPrivate;
this.isVerified = isVerified;
this.id = id;
this.url = url;
this.name = name;
this.username = username;
this.biography = biography;
this.sdProfilePic = sdProfilePic;
this.hdProfilePic = hdProfilePic;
this.postCount = postCount;
this.followersCount = followersCount;
this.followingCount = followingCount;
}
public boolean isPrivate() {
return isPrivate;
}
public boolean isVerified() {
return isVerified;
}
public String getId() {
return id;
}
public String getUsername() {
return username;
}
public String getName() {
return name;
}
public String getBiography() {
return biography;
}
public String getUrl() {
return url;
}
public String getSdProfilePic() {
return sdProfilePic;
}
public String getHdProfilePic() {
return hdProfilePic;
}
public long getPostCount() {
return postCount;
}
public long getFollowersCount() {
return followersCount;
}
public long getFollowingCount() {
return followingCount;
}
}

View file

@ -0,0 +1,69 @@
package awais.instagrabber.models;
import java.io.Serializable;
import awais.instagrabber.models.enums.MediaItemType;
public final class StoryModel implements Serializable {
private final String storyMediaId, storyUrl;
private final MediaItemType itemType;
private final long timestamp;
private String videoUrl, tappableShortCode;
private int position;
private boolean isCurrentSlide = false;
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType, final long timestamp) {
this.storyMediaId = storyMediaId;
this.storyUrl = storyUrl;
this.itemType = itemType;
this.timestamp = timestamp;
}
public String getStoryUrl() {
return storyUrl;
}
public String getStoryMediaId() {
return storyMediaId;
}
public MediaItemType getItemType() {
return itemType;
}
public long getTimestamp() {
return timestamp;
}
public String getVideoUrl() {
return videoUrl;
}
public String getTappableShortCode() {
return tappableShortCode;
}
public int getPosition() {
return position;
}
public void setVideoUrl(final String videoUrl) {
this.videoUrl = videoUrl;
}
public void setTappableShortCode(final String tappableShortCode) {
this.tappableShortCode = tappableShortCode;
}
public void setPosition(final int position) {
this.position = position;
}
public void setCurrentSlide(final boolean currentSlide) {
this.isCurrentSlide = currentSlide;
}
public boolean isCurrentSlide() {
return isCurrentSlide;
}
}

View file

@ -0,0 +1,51 @@
package awais.instagrabber.models;
import androidx.annotation.NonNull;
import awais.instagrabber.models.enums.SuggestionType;
public final class SuggestionModel implements Comparable<SuggestionModel> {
private final int position;
private final boolean isVerified;
private final String username, name, profilePic;
private final SuggestionType suggestionType;
public SuggestionModel(final boolean isVerified, final String username, final String name, final String profilePic,
final SuggestionType suggestionType, final int position) {
this.isVerified = isVerified;
this.username = username;
this.name = name;
this.profilePic = profilePic;
this.suggestionType = suggestionType;
this.position = position;
}
public boolean isVerified() {
return isVerified;
}
public String getUsername() {
return username;
}
public String getName() {
return name;
}
public String getProfilePic() {
return profilePic;
}
public SuggestionType getSuggestionType() {
return suggestionType;
}
public int getPosition() {
return position;
}
@Override
public int compareTo(@NonNull final SuggestionModel model) {
return Integer.compare(getPosition(), model.getPosition());
}
}

View file

@ -0,0 +1,63 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.MediaItemType;
public final class ViewerPostModel extends BasePostModel {
protected final String username;
protected final long videoViews;
protected String sliderDisplayUrl, commentsEndCursor;
protected long commentsCount;
private boolean isCurrentSlide = false;
public ViewerPostModel(final MediaItemType itemType, final String postId, final String displayUrl, final String shortCode,
final String postCaption, final String username, final long videoViews, final long timestamp) {
this.itemType = itemType;
this.postId = postId;
this.displayUrl = displayUrl;
this.postCaption = postCaption;
this.username = username;
this.shortCode = shortCode;
this.videoViews = videoViews;
this.timestamp = timestamp;
}
public long getCommentsCount() {
return commentsCount;
}
public String getSliderDisplayUrl() {
return sliderDisplayUrl;
}
public String getUsername() {
return username;
}
public String getCommentsEndCursor() {
return commentsEndCursor;
}
public final long getVideoViews() {
return videoViews;
}
public void setSliderDisplayUrl(final String sliderDisplayUrl) {
this.sliderDisplayUrl = sliderDisplayUrl;
}
public void setCommentsCount(final long commentsCount) {
this.commentsCount = commentsCount;
}
public void setCommentsEndCursor(final String commentsEndCursor) {
this.commentsEndCursor = commentsEndCursor;
}
public void setCurrentSlide(final boolean currentSlide) {
this.isCurrentSlide = currentSlide;
}
public boolean isCurrentSlide() {
return isCurrentSlide;
}
}

View file

@ -0,0 +1,490 @@
package awais.instagrabber.models.direct_messages;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Date;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Utils;
public final class DirectItemModel implements Serializable, Comparable<DirectItemModel> {
private final long userId, timestamp;
private final DirectItemType itemType;
private final String itemId;
private final CharSequence text;
private final DirectItemLinkModel linkModel;
private final DirectItemMediaModel mediaModel;
private final ProfileModel profileModel;
private final DirectItemReelShareModel reelShare;
private final DirectItemActionLogModel actionLogModel;
private final DirectItemVoiceMediaModel voiceMediaModel;
private final DirectItemRavenMediaModel ravenMediaModel;
private final DirectItemAnimatedMediaModel animatedMediaModel;
private final DirectItemVideoCallEventModel videoCallEventModel;
public DirectItemModel(final long userId, final long timestamp, final String itemId, final DirectItemType itemType,
final CharSequence text, final DirectItemLinkModel linkModel, final ProfileModel profileModel,
final DirectItemReelShareModel reelShare, final DirectItemMediaModel mediaModel,
final DirectItemActionLogModel actionLogModel, final DirectItemVoiceMediaModel voiceMediaModel,
final DirectItemRavenMediaModel ravenMediaModel, final DirectItemVideoCallEventModel videoCallEventModel,
final DirectItemAnimatedMediaModel animatedMediaModel) {
this.userId = userId;
this.timestamp = timestamp;
this.itemType = itemType;
this.itemId = itemId;
this.text = text;
this.linkModel = linkModel;
this.profileModel = profileModel;
this.reelShare = reelShare;
this.mediaModel = mediaModel;
this.actionLogModel = actionLogModel;
this.voiceMediaModel = voiceMediaModel;
this.ravenMediaModel = ravenMediaModel;
this.videoCallEventModel = videoCallEventModel;
this.animatedMediaModel = animatedMediaModel;
}
public DirectItemType getItemType() {
return itemType;
}
public CharSequence getText() {
return text;
}
public String getItemId() {
return itemId;
}
public long getUserId() {
return userId;
}
public long getTimestamp() {
return timestamp;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp / 1000L));
}
public ProfileModel getProfileModel() {
return profileModel;
}
public DirectItemLinkModel getLinkModel() {
return linkModel;
}
public DirectItemMediaModel getMediaModel() {
return mediaModel;
}
public DirectItemReelShareModel getReelShare() {
return reelShare;
}
public DirectItemActionLogModel getActionLogModel() {
return actionLogModel;
}
public DirectItemVoiceMediaModel getVoiceMediaModel() {
return voiceMediaModel;
}
public DirectItemRavenMediaModel getRavenMediaModel() {
return ravenMediaModel;
}
public DirectItemAnimatedMediaModel getAnimatedMediaModel() {
return animatedMediaModel;
}
public DirectItemVideoCallEventModel getVideoCallEventModel() {
return videoCallEventModel;
}
@Override
public int compareTo(@NonNull final DirectItemModel o) {
return Long.compare(timestamp, o.timestamp);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public final static class DirectItemAnimatedMediaModel implements Serializable {
private final boolean isRandom, isSticker;
private final String id;
private final String gifUrl, webpUrl, mp4Url;
private final int height, width;
public DirectItemAnimatedMediaModel(final boolean isRandom, final boolean isSticker, final String id, final String gifUrl,
final String webpUrl, final String mp4Url, final int height, final int width) {
this.isRandom = isRandom;
this.isSticker = isSticker;
this.id = id;
this.gifUrl = gifUrl;
this.webpUrl = webpUrl;
this.mp4Url = mp4Url;
this.height = height;
this.width = width;
}
public boolean isRandom() {
return isRandom;
}
public boolean isSticker() {
return isSticker;
}
public String getId() {
return id;
}
public String getGifUrl() {
return gifUrl;
}
public String getWebpUrl() {
return webpUrl;
}
public String getMp4Url() {
return mp4Url;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
}
public final static class DirectItemVoiceMediaModel implements Serializable {
private final String id, audioUrl;
private final long durationMs;
private final int[] waveformData;
private int progress;
private boolean isPlaying = false;
public DirectItemVoiceMediaModel(final String id, final String audioUrl, final long durationMs, final int[] waveformData) {
this.id = id;
this.audioUrl = audioUrl;
this.durationMs = durationMs;
this.waveformData = waveformData;
}
public String getId() {
return id;
}
public String getAudioUrl() {
return audioUrl;
}
public long getDurationMs() {
return durationMs;
}
public int[] getWaveformData() {
return waveformData;
}
public void setProgress(final int progress) {
this.progress = progress;
}
public int getProgress() {
return progress;
}
public boolean isPlaying() {
return isPlaying;
}
public void setPlaying(final boolean playing) {
isPlaying = playing;
}
}
public final static class DirectItemLinkModel implements Serializable {
private final String text;
private final String clientContext;
private final String mutationToken;
private final DirectItemLinkContext linkContext;
public DirectItemLinkModel(final String text, final String clientContext, final String mutationToken,
final DirectItemLinkContext linkContext) {
this.text = text;
this.clientContext = clientContext;
this.mutationToken = mutationToken;
this.linkContext = linkContext;
}
public String getText() {
return text;
}
public String getClientContext() {
return clientContext;
}
public String getMutationToken() {
return mutationToken;
}
public DirectItemLinkContext getLinkContext() {
return linkContext;
}
}
public final static class DirectItemLinkContext implements Serializable {
private final String linkUrl;
private final String linkTitle;
private final String linkSummary;
private final String linkImageUrl;
public DirectItemLinkContext(final String linkUrl, final String linkTitle, final String linkSummary, final String linkImageUrl) {
this.linkUrl = linkUrl;
this.linkTitle = linkTitle;
this.linkSummary = linkSummary;
this.linkImageUrl = linkImageUrl;
}
public String getLinkUrl() {
return linkUrl;
}
public String getLinkTitle() {
return linkTitle;
}
public String getLinkSummary() {
return linkSummary;
}
public String getLinkImageUrl() {
return linkImageUrl;
}
}
public final static class DirectItemActionLogModel implements Serializable {
private final String description;
public DirectItemActionLogModel(final String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public final static class DirectItemReelShareModel implements Serializable {
private final boolean isReelPersisted;
private final long reelOwnerId;
private final String text;
private final String type;
private final String reelType;
private final String reelName;
private final String reelId;
private final DirectItemMediaModel media;
public DirectItemReelShareModel(final boolean isReelPersisted, final long reelOwnerId, final String text, final String type,
final String reelType, final String reelName, final String reelId, final DirectItemMediaModel media) {
this.isReelPersisted = isReelPersisted;
this.reelOwnerId = reelOwnerId;
this.text = text;
this.type = type;
this.reelType = reelType;
this.reelName = reelName;
this.reelId = reelId;
this.media = media;
}
public boolean isReelPersisted() {
return isReelPersisted;
}
public long getReelOwnerId() {
return reelOwnerId;
}
public String getText() {
return text;
}
public String getType() {
return type;
}
public String getReelType() {
return reelType;
}
public String getReelName() {
return reelName;
}
public String getReelId() {
return reelId;
}
public DirectItemMediaModel getMedia() {
return media;
}
}
public final static class DirectItemMediaModel implements Serializable {
private final MediaItemType mediaType;
private final long expiringAt, pk;
private final String id, thumbUrl;
private final ProfileModel user;
public DirectItemMediaModel(final MediaItemType mediaType, final long expiringAt, final long pk, final String id,
final String thumbUrl, final ProfileModel user) {
this.mediaType = mediaType;
this.expiringAt = expiringAt;
this.pk = pk;
this.id = id;
this.thumbUrl = thumbUrl;
this.user = user;
}
public MediaItemType getMediaType() {
return mediaType;
}
public long getExpiringAt() {
return expiringAt;
}
public long getPk() {
return pk;
}
public String getId() {
return id;
}
public ProfileModel getUser() {
return user;
}
public String getThumbUrl() {
return thumbUrl;
}
}
public final static class DirectItemRavenMediaModel implements Serializable {
private final long expireAtSecs;
private final int playbackDurationSecs;
private final int seenCount;
private final String[] seenUserIds;
private final RavenMediaViewType viewType;
private final DirectItemMediaModel media;
private final RavenExpiringMediaActionSummaryModel expiringMediaActionSummary;
public DirectItemRavenMediaModel(final long expireAtSecs, final int playbackDurationSecs, final int seenCount,
final String[] seenUserIds, final RavenMediaViewType viewType, final DirectItemMediaModel media,
final RavenExpiringMediaActionSummaryModel expiringMediaActionSummary) {
this.expireAtSecs = expireAtSecs;
this.playbackDurationSecs = playbackDurationSecs;
this.seenCount = seenCount;
this.seenUserIds = seenUserIds;
this.viewType = viewType;
this.media = media;
this.expiringMediaActionSummary = expiringMediaActionSummary;
}
public long getExpireAtSecs() {
return expireAtSecs;
}
public int getPlaybackDurationSecs() {
return playbackDurationSecs;
}
public int getSeenCount() {
return seenCount;
}
public String[] getSeenUserIds() {
return seenUserIds;
}
public RavenMediaViewType getViewType() {
return viewType;
}
public DirectItemMediaModel getMedia() {
return media;
}
public RavenExpiringMediaActionSummaryModel getExpiringMediaActionSummary() {
return expiringMediaActionSummary;
}
}
public final static class DirectItemVideoCallEventModel implements Serializable {
private final long videoCallId;
private final boolean hasAudioOnlyCall;
private final String action;
private final String description;
public DirectItemVideoCallEventModel(final long videoCallId, final boolean hasAudioOnlyCall, final String action, final String description) {
this.videoCallId = videoCallId;
this.hasAudioOnlyCall = hasAudioOnlyCall;
this.action = action;
this.description = description;
}
public long getVideoCallId() {
return videoCallId;
}
public boolean isHasAudioOnlyCall() {
return hasAudioOnlyCall;
}
public String getAction() {
return action;
}
public String getDescription() {
return description;
}
}
public final static class RavenExpiringMediaActionSummaryModel implements Serializable {
private final long timestamp;
private final int count;
private final RavenExpiringMediaType type;
public RavenExpiringMediaActionSummaryModel(final long timestamp, final int count, final RavenExpiringMediaType type) {
this.timestamp = timestamp;
this.count = count;
this.type = type;
}
public long getTimestamp() {
return timestamp;
}
public int getCount() {
return count;
}
public RavenExpiringMediaType getType() {
return type;
}
}
}

View file

@ -0,0 +1,27 @@
package awais.instagrabber.models.direct_messages;
import awais.instagrabber.models.enums.MediaItemType;
public final class InboxMediaModel {
private final MediaItemType mediaType;
private final String mediaId;
private final String displayUrl;
public InboxMediaModel(final MediaItemType mediaType, final String mediaId, final String displayUrl) {
this.mediaType = mediaType;
this.mediaId = mediaId;
this.displayUrl = displayUrl;
}
public MediaItemType getMediaType() {
return mediaType;
}
public String getMediaId() {
return mediaId;
}
public String getDisplayUrl() {
return displayUrl;
}
}

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