mirror of
https://github.com/KokaKiwi/BarInsta
synced 2026-03-04 11:31:35 +00:00
init
This commit is contained in:
commit
13beabf741
275 changed files with 22638 additions and 0 deletions
218
app/src/main/AndroidManifest.xml
Executable file
218
app/src/main/AndroidManifest.xml
Executable 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>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Executable file
BIN
app/src/main/ic_launcher-playstore.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
66
app/src/main/java/awais/instagrabber/InstaApp.java
Executable file
66
app/src/main/java/awais/instagrabber/InstaApp.java
Executable 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();
|
||||
}
|
||||
}
|
||||
864
app/src/main/java/awais/instagrabber/MainHelper.java
Executable file
864
app/src/main/java/awais/instagrabber/MainHelper.java
Executable 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.java
Executable file
11
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.java
Executable 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);
|
||||
}
|
||||
}
|
||||
146
app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
Executable file
146
app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
Executable 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;
|
||||
}
|
||||
}
|
||||
113
app/src/main/java/awais/instagrabber/activities/DirectMessages.java
Executable file
113
app/src/main/java/awais/instagrabber/activities/DirectMessages.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
Executable file
101
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
Executable 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);
|
||||
}
|
||||
}
|
||||
348
app/src/main/java/awais/instagrabber/activities/FollowViewer.java
Executable file
348
app/src/main/java/awais/instagrabber/activities/FollowViewer.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
app/src/main/java/awais/instagrabber/activities/Login.java
Executable file
130
app/src/main/java/awais/instagrabber/activities/Login.java
Executable 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();
|
||||
}
|
||||
}
|
||||
480
app/src/main/java/awais/instagrabber/activities/Main.java
Executable file
480
app/src/main/java/awais/instagrabber/activities/Main.java
Executable 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;
|
||||
}
|
||||
}
|
||||
639
app/src/main/java/awais/instagrabber/activities/PostViewer.java
Executable file
639
app/src/main/java/awais/instagrabber/activities/PostViewer.java
Executable 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);
|
||||
}
|
||||
}
|
||||
215
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
Executable file
215
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
Executable 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;
|
||||
}
|
||||
}
|
||||
354
app/src/main/java/awais/instagrabber/activities/StoryViewer.java
Executable file
354
app/src/main/java/awais/instagrabber/activities/StoryViewer.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java
Executable file
136
app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
116
app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
Executable file
116
app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
Executable 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();
|
||||
}
|
||||
}
|
||||
87
app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
Executable file
87
app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
Executable 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();
|
||||
}
|
||||
}
|
||||
486
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
Executable file
486
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
Executable file
57
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
144
app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
Executable file
144
app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
Executable 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)];
|
||||
}
|
||||
}
|
||||
53
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
Executable file
53
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
354
app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
Executable file
354
app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
92
app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
Executable file
92
app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
Executable 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();
|
||||
}
|
||||
}
|
||||
68
app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
Executable file
68
app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
Executable 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;
|
||||
}
|
||||
}
|
||||
75
app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
Executable file
75
app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
Executable file
84
app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
Executable file
60
app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
Executable 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
23
app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
Executable file
23
app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
Executable 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
265
app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java
Executable file
265
app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java
Executable 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;
|
||||
}
|
||||
}
|
||||
194
app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
Executable file
194
app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
248
app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
Executable file
248
app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
Executable 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);
|
||||
}
|
||||
}
|
||||
194
app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
Executable file
194
app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
103
app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java
Executable file
103
app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
101
app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java
Executable file
101
app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
87
app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
Executable file
87
app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
146
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
Executable file
146
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
134
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
Executable file
134
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
83
app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
Executable file
83
app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
120
app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
Executable file
120
app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
102
app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
Executable file
102
app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
98
app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
Executable file
98
app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
54
app/src/main/java/awais/instagrabber/asyncs/UsernameFetcher.java
Executable file
54
app/src/main/java/awais/instagrabber/asyncs/UsernameFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
100
app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
Executable file
100
app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
Executable 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
105
app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
Executable file
105
app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
Executable 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
25
app/src/main/java/awais/instagrabber/customviews/FixedImageView.java
Executable file
25
app/src/main/java/awais/instagrabber/customviews/FixedImageView.java
Executable 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);
|
||||
}
|
||||
}
|
||||
986
app/src/main/java/awais/instagrabber/customviews/MouseDrawer.java
Executable file
986
app/src/main/java/awais/instagrabber/customviews/MouseDrawer.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
179
app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
Executable file
179
app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
Executable 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;
|
||||
}
|
||||
}
|
||||
182
app/src/main/java/awais/instagrabber/customviews/RemixDrawerLayout.java
Executable file
182
app/src/main/java/awais/instagrabber/customviews/RemixDrawerLayout.java
Executable 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package awais.instagrabber.customviews.masoudss_waveform;
|
||||
|
||||
public interface WaveFormProgressChangeListener {
|
||||
void onProgressChanged(final WaveformSeekBar waveformSeekBar, final int progress, final boolean fromUser);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package awais.instagrabber.customviews.masoudss_waveform;
|
||||
|
||||
public enum WaveGravity {
|
||||
TOP,
|
||||
CENTER,
|
||||
BOTTOM,
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
98
app/src/main/java/awais/instagrabber/dialogs/AboutDialog.java
Executable file
98
app/src/main/java/awais/instagrabber/dialogs/AboutDialog.java
Executable 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;
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/awais/instagrabber/dialogs/ProfileSettingsDialog.java
Executable file
62
app/src/main/java/awais/instagrabber/dialogs/ProfileSettingsDialog.java
Executable 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) { }
|
||||
}
|
||||
169
app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
Executable file
169
app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
Executable 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();
|
||||
}
|
||||
}
|
||||
213
app/src/main/java/awais/instagrabber/dialogs/SettingsDialog.java
Executable file
213
app/src/main/java/awais/instagrabber/dialogs/SettingsDialog.java
Executable 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) { }
|
||||
}
|
||||
173
app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java
Executable file
173
app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java
Executable 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) { }
|
||||
}
|
||||
153
app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
Executable file
153
app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
Executable file
117
app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
Executable 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);
|
||||
}
|
||||
}
|
||||
6
app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
Executable file
6
app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
package awais.instagrabber.interfaces;
|
||||
|
||||
public interface FetchListener<T> {
|
||||
void onResult(T result);
|
||||
default void doBefore() { }
|
||||
}
|
||||
11
app/src/main/java/awais/instagrabber/interfaces/ItemGetter.java
Executable file
11
app/src/main/java/awais/instagrabber/interfaces/ItemGetter.java
Executable 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);
|
||||
}
|
||||
5
app/src/main/java/awais/instagrabber/interfaces/LazyLoadListener.java
Executable file
5
app/src/main/java/awais/instagrabber/interfaces/LazyLoadListener.java
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
package awais.instagrabber.interfaces;
|
||||
|
||||
public interface LazyLoadListener {
|
||||
void onLoadMore(final int page, final int totalItemsCount);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package awais.instagrabber.interfaces;
|
||||
|
||||
public interface OnGroupClickListener {
|
||||
void toggleGroup(final int flatPos);
|
||||
}
|
||||
5
app/src/main/java/awais/instagrabber/interfaces/SwipeEvent.java
Executable file
5
app/src/main/java/awais/instagrabber/interfaces/SwipeEvent.java
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
package awais.instagrabber.interfaces;
|
||||
|
||||
public interface SwipeEvent {
|
||||
void onSwipe(final boolean isRight);
|
||||
}
|
||||
82
app/src/main/java/awais/instagrabber/models/BasePostModel.java
Executable file
82
app/src/main/java/awais/instagrabber/models/BasePostModel.java
Executable 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));
|
||||
}
|
||||
}
|
||||
85
app/src/main/java/awais/instagrabber/models/CommentModel.java
Executable file
85
app/src/main/java/awais/instagrabber/models/CommentModel.java
Executable 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 : "\"") + '}';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
29
app/src/main/java/awais/instagrabber/models/DiscoverItemModel.java
Executable file
29
app/src/main/java/awais/instagrabber/models/DiscoverItemModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
56
app/src/main/java/awais/instagrabber/models/FeedModel.java
Executable file
56
app/src/main/java/awais/instagrabber/models/FeedModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
30
app/src/main/java/awais/instagrabber/models/FeedStoryModel.java
Executable file
30
app/src/main/java/awais/instagrabber/models/FeedStoryModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
64
app/src/main/java/awais/instagrabber/models/FollowModel.java
Executable file
64
app/src/main/java/awais/instagrabber/models/FollowModel.java
Executable 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);
|
||||
}
|
||||
}
|
||||
27
app/src/main/java/awais/instagrabber/models/HighlightModel.java
Executable file
27
app/src/main/java/awais/instagrabber/models/HighlightModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/awais/instagrabber/models/IntentModel.java
Executable file
21
app/src/main/java/awais/instagrabber/models/IntentModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
50
app/src/main/java/awais/instagrabber/models/PostModel.java
Executable file
50
app/src/main/java/awais/instagrabber/models/PostModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
75
app/src/main/java/awais/instagrabber/models/ProfileModel.java
Executable file
75
app/src/main/java/awais/instagrabber/models/ProfileModel.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
||||
69
app/src/main/java/awais/instagrabber/models/StoryModel.java
Executable file
69
app/src/main/java/awais/instagrabber/models/StoryModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/awais/instagrabber/models/SuggestionModel.java
Executable file
51
app/src/main/java/awais/instagrabber/models/SuggestionModel.java
Executable 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());
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/awais/instagrabber/models/ViewerPostModel.java
Executable file
63
app/src/main/java/awais/instagrabber/models/ViewerPostModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
490
app/src/main/java/awais/instagrabber/models/direct_messages/DirectItemModel.java
Executable file
490
app/src/main/java/awais/instagrabber/models/direct_messages/DirectItemModel.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
63
app/src/main/java/awais/instagrabber/models/direct_messages/InboxModel.java
Executable file
63
app/src/main/java/awais/instagrabber/models/direct_messages/InboxModel.java
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
package awais.instagrabber.models.direct_messages;
|
||||
|
||||
public final class InboxModel {
|
||||
private final boolean hasOlder, hasPendingTopRequests, blendedInboxEnabled;
|
||||
private final int unseenCount, pendingRequestsCount;
|
||||
private final long seqId, unseenCountTimestamp;
|
||||
private final InboxThreadModel[] threads;
|
||||
private String oldestCursor;
|
||||
|
||||
public InboxModel(final boolean hasOlder, final boolean hasPendingTopRequests, final boolean blendedInboxEnabled,
|
||||
final int unseenCount, final int pendingRequestsCount, final long seqId, final long unseenCountTimestamp,
|
||||
final String oldestCursor, final InboxThreadModel[] threads) {
|
||||
this.hasOlder = hasOlder;
|
||||
this.hasPendingTopRequests = hasPendingTopRequests;
|
||||
this.blendedInboxEnabled = blendedInboxEnabled;
|
||||
this.unseenCount = unseenCount;
|
||||
this.pendingRequestsCount = pendingRequestsCount;
|
||||
this.unseenCountTimestamp = unseenCountTimestamp;
|
||||
this.oldestCursor = oldestCursor;
|
||||
this.threads = threads;
|
||||
this.seqId = seqId;
|
||||
}
|
||||
|
||||
public boolean isHasOlder() {
|
||||
return hasOlder;
|
||||
}
|
||||
|
||||
public boolean isHasPendingTopRequests() {
|
||||
return hasPendingTopRequests;
|
||||
}
|
||||
|
||||
public boolean isBlendedInboxEnabled() {
|
||||
return blendedInboxEnabled;
|
||||
}
|
||||
|
||||
public int getUnseenCount() {
|
||||
return unseenCount;
|
||||
}
|
||||
|
||||
public int getPendingRequestsCount() {
|
||||
return pendingRequestsCount;
|
||||
}
|
||||
|
||||
public long getUnseenCountTimestamp() {
|
||||
return unseenCountTimestamp;
|
||||
}
|
||||
|
||||
public long getSeqId() {
|
||||
return seqId;
|
||||
}
|
||||
|
||||
public String getOldestCursor() {
|
||||
return oldestCursor;
|
||||
}
|
||||
|
||||
public void setOldestCursor(final String oldestCursor) {
|
||||
this.oldestCursor = oldestCursor;
|
||||
}
|
||||
|
||||
public InboxThreadModel[] getThreads() {
|
||||
return threads;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package awais.instagrabber.models.direct_messages;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.models.enums.InboxReadState;
|
||||
|
||||
public final class InboxThreadModel implements Serializable {
|
||||
private final InboxReadState readState;
|
||||
private final String threadId, threadV2Id, threadType, threadTitle, newestCursor, oldestCursor, nextCursor, prevCursor;
|
||||
private final ProfileModel inviter;
|
||||
private final ProfileModel[] users, leftUsers;
|
||||
private final DirectItemModel[] items;
|
||||
private final boolean muted, isPin, isSpam, isGroup, named, pending, archived, canonical, hasOlder, hasNewer;
|
||||
private final long lastActivityAt;
|
||||
|
||||
public InboxThreadModel(final InboxReadState readState, final String threadId, final String threadV2Id, final String threadType, final String threadTitle,
|
||||
final String newestCursor, final String oldestCursor, final String nextCursor, final String prevCursor,
|
||||
final ProfileModel inviter, final ProfileModel[] users,
|
||||
final ProfileModel[] leftUsers, final DirectItemModel[] items, final boolean muted,
|
||||
final boolean isPin, final boolean named, final boolean canonical, final boolean pending,
|
||||
final boolean hasOlder, final boolean hasNewer, final boolean isSpam, final boolean isGroup,
|
||||
final boolean archived, final long lastActivityAt) {
|
||||
this.readState = readState;
|
||||
this.threadId = threadId;
|
||||
this.threadV2Id = threadV2Id;
|
||||
this.threadType = threadType;
|
||||
this.threadTitle = threadTitle;
|
||||
this.newestCursor = newestCursor;
|
||||
this.oldestCursor = oldestCursor;
|
||||
this.nextCursor = nextCursor;
|
||||
this.prevCursor = prevCursor;
|
||||
this.inviter = inviter;
|
||||
this.users = users;
|
||||
this.leftUsers = leftUsers;
|
||||
this.items = items; // todo
|
||||
this.muted = muted;
|
||||
this.isPin = isPin;
|
||||
this.named = named;
|
||||
this.canonical = canonical;
|
||||
this.pending = pending;
|
||||
this.hasOlder = hasOlder;
|
||||
this.hasNewer = hasNewer;
|
||||
this.isSpam = isSpam;
|
||||
this.isGroup = isGroup;
|
||||
this.archived = archived;
|
||||
this.lastActivityAt = lastActivityAt;
|
||||
}
|
||||
|
||||
public InboxReadState getReadState() {
|
||||
return readState;
|
||||
}
|
||||
|
||||
public String getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
public String getThreadV2Id() {
|
||||
return threadV2Id;
|
||||
}
|
||||
|
||||
public String getThreadType() {
|
||||
return threadType;
|
||||
}
|
||||
|
||||
public String getThreadTitle() {
|
||||
return threadTitle;
|
||||
}
|
||||
|
||||
public String getNewestCursor() {
|
||||
return newestCursor;
|
||||
}
|
||||
|
||||
public String getOldestCursor() {
|
||||
return oldestCursor;
|
||||
}
|
||||
|
||||
public String getNextCursor() {
|
||||
return nextCursor;
|
||||
}
|
||||
|
||||
public String getPrevCursor() {
|
||||
return prevCursor;
|
||||
}
|
||||
|
||||
public ProfileModel getInviter() {
|
||||
return inviter;
|
||||
}
|
||||
|
||||
public ProfileModel[] getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public ProfileModel[] getLeftUsers() {
|
||||
return leftUsers;
|
||||
}
|
||||
|
||||
public DirectItemModel[] getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return muted;
|
||||
}
|
||||
|
||||
public boolean isPin() {
|
||||
return isPin;
|
||||
}
|
||||
|
||||
public boolean isNamed() {
|
||||
return named;
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return pending;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return archived;
|
||||
}
|
||||
|
||||
public boolean isCanonical() {
|
||||
return canonical;
|
||||
}
|
||||
|
||||
public boolean isHasOlder() {
|
||||
return hasOlder;
|
||||
}
|
||||
|
||||
public boolean isHasNewer() {
|
||||
return hasNewer;
|
||||
}
|
||||
|
||||
public boolean isSpam() {
|
||||
return isSpam;
|
||||
}
|
||||
|
||||
public boolean isGroup() {
|
||||
return isGroup;
|
||||
}
|
||||
|
||||
public long getLastActivityAt() {
|
||||
return lastActivityAt;
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java
Executable file
19
app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
package awais.instagrabber.models.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public enum DirectItemType implements Serializable {
|
||||
TEXT,
|
||||
LIKE,
|
||||
LINK,
|
||||
MEDIA,
|
||||
RAVEN_MEDIA,
|
||||
PROFILE,
|
||||
VIDEO_CALL_EVENT,
|
||||
ANIMATED_MEDIA,
|
||||
VOICE_MEDIA,
|
||||
MEDIA_SHARE,
|
||||
REEL_SHARE,
|
||||
ACTION_LOG,
|
||||
PLACEHOLDER,
|
||||
}
|
||||
9
app/src/main/java/awais/instagrabber/models/enums/DownloadMethod.java
Executable file
9
app/src/main/java/awais/instagrabber/models/enums/DownloadMethod.java
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
package awais.instagrabber.models.enums;
|
||||
|
||||
public enum DownloadMethod {
|
||||
DOWNLOAD_MAIN,
|
||||
DOWNLOAD_DISCOVER,
|
||||
DOWNLOAD_FEED,
|
||||
DOWNLOAD_POST_VIEWER,
|
||||
DOWNLOAD_DIRECT;
|
||||
}
|
||||
8
app/src/main/java/awais/instagrabber/models/enums/InboxReadState.java
Executable file
8
app/src/main/java/awais/instagrabber/models/enums/InboxReadState.java
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
package awais.instagrabber.models.enums;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public enum InboxReadState implements Serializable {
|
||||
STATE_READ,
|
||||
STATE_UNREAD,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue