Browse Source

Update Feed view, check description

Changes:

1. Separate out view holders for feed post types.
2. Improve performance for binding.
4. Initiating migration from Glide to Fresco, as performance of Fresco is way better.
3. Prefetch feed view thumbnails to get aspect ratio, to improve layout performance.
4. If auto play is off, the videos in feed are not loaded until play is pressed.
5. Lots of optimizations here and there.
renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
da98cff507
  1. 6
      app/build.gradle
  2. 7
      app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
  3. 462
      app/src/main/java/awais/instagrabber/MainHelper.java
  4. 17
      app/src/main/java/awais/instagrabber/activities/MainActivity.java
  5. 122
      app/src/main/java/awais/instagrabber/activities/PostViewer.java
  6. 201
      app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
  7. 517
      app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
  8. 6
      app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
  9. 53
      app/src/main/java/awais/instagrabber/adapters/viewholder/FeedItemViewHolder.java
  10. 3
      app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java
  11. 153
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java
  12. 87
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java
  13. 339
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java
  14. 156
      app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
  15. 162
      app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
  16. 4
      app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
  17. 170
      app/src/main/java/awais/instagrabber/customviews/drawee/AbstractAnimatedZoomableController.java
  18. 105
      app/src/main/java/awais/instagrabber/customviews/drawee/AnimatedZoomableController.java
  19. 720
      app/src/main/java/awais/instagrabber/customviews/drawee/DefaultZoomableController.java
  20. 84
      app/src/main/java/awais/instagrabber/customviews/drawee/DoubleTapGestureListener.java
  21. 72
      app/src/main/java/awais/instagrabber/customviews/drawee/GestureListenerWrapper.java
  22. 156
      app/src/main/java/awais/instagrabber/customviews/drawee/MultiGestureListener.java
  23. 286
      app/src/main/java/awais/instagrabber/customviews/drawee/MultiPointerGestureDetector.java
  24. 51
      app/src/main/java/awais/instagrabber/customviews/drawee/MultiZoomableControllerListener.java
  25. 203
      app/src/main/java/awais/instagrabber/customviews/drawee/TransformGestureDetector.java
  26. 134
      app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableController.java
  27. 416
      app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java
  28. 43
      app/src/main/java/awais/instagrabber/customviews/helpers/ImageResizingControllerListener.java
  29. 45
      app/src/main/java/awais/instagrabber/customviews/helpers/PauseGlideOnFlingScrollListener.java
  30. 497
      app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java
  31. 22
      app/src/main/java/awais/instagrabber/models/FeedModel.java
  32. 31
      app/src/main/java/awais/instagrabber/models/enums/MediaItemType.java
  33. 92
      app/src/main/java/awais/instagrabber/utils/Utils.java
  34. 11
      app/src/main/res/drawable/rounder_corner_semi_black_bg.xml
  35. 2
      app/src/main/res/layout/activity_main.xml
  36. 17
      app/src/main/res/layout/item_feed.xml
  37. 11
      app/src/main/res/layout/item_feed_bottom.xml
  38. 25
      app/src/main/res/layout/item_feed_photo.xml
  39. 19
      app/src/main/res/layout/item_feed_slider.xml
  40. 15
      app/src/main/res/layout/item_feed_top.xml
  41. 53
      app/src/main/res/layout/item_feed_video.xml
  42. 2
      app/src/main/res/layout/layout_feed_view.xml
  43. 59
      app/src/main/res/layout/layout_profile_view.xml
  44. 1
      app/src/main/res/values/color.xml

6
app/build.gradle

@ -39,8 +39,8 @@ android {
dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true }
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true }
implementation('com.google.android.material:material:1.3.0-alpha02@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01') { transitive true }
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
@ -52,4 +52,6 @@ dependencies {
implementation('com.github.bumptech.glide:glide:4.11.0') { transitive true }
implementation('com.github.chrisbanes:PhotoView:v2.0.0@aar') { transitive true }
implementation('com.google.android.exoplayer:exoplayer:2.11.1@aar') { transitive true }
implementation 'com.facebook.fresco:fresco:2.3.0'
}

7
app/src/main/java/awais/instagrabber/InstaGrabberApplication.java

@ -7,6 +7,8 @@ import android.util.Log;
import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication;
import com.facebook.drawee.backends.pipeline.Fresco;
import java.net.CookieHandler;
import java.text.SimpleDateFormat;
import java.util.UUID;
@ -34,12 +36,13 @@ public final class InstaGrabberApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
Fresco.initialize(this);
if (BuildConfig.DEBUG) {
try {
Class.forName("dalvik.system.CloseGuard")
.getMethod("setEnabled", boolean.class)
.invoke(null, true);
.getMethod("setEnabled", boolean.class)
.invoke(null, true);
} catch (Exception e) {
Log.e(TAG, "Error", e);
}

462
app/src/main/java/awais/instagrabber/MainHelper.java

@ -1,10 +1,10 @@
package awais.instagrabber;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.AsyncTask;
@ -32,14 +32,25 @@ import androidx.core.widget.ImageViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import com.google.android.exoplayer2.SimpleExoPlayer;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.activities.FollowViewer;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.activities.PostViewer;
@ -49,6 +60,7 @@ import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.asyncs.FeedStoriesFetcher;
@ -63,6 +75,7 @@ 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.PauseGlideOnFlingScrollListener;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.interfaces.FetchListener;
@ -74,8 +87,12 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.IntentModel;
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.IntentModelType;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils;
@ -87,13 +104,18 @@ import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "MainHelper";
private static final double MAX_VIDEO_HEIGHT = 0.9 * Utils.displayMetrics.heightPixels;
private static final int RESIZED_VIDEO_HEIGHT = (int) (0.8 * Utils.displayMetrics.heightPixels);
private static AsyncTask<?, ?, ?> currentlyExecuting;
private AsyncTask<Void, Void, FeedStoryModel[]> prevStoriesFetcher;
private final boolean autoloadPosts;
private FeedStoryModel[] stories;
private boolean hasNextPage = false, feedHasNextPage = false, discoverHasMore = false;
private String endCursor = null, feedEndCursor = null, discoverEndMaxId = null, topic = null, rankToken = null;
private String[] topicIds = null;
private final boolean autoloadPosts;
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() {
@Override
public void onResult(final PostModel[] result) {
@ -112,7 +134,8 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.toolbar.toolbar.setTitle(mainActivity.userQuery);
else if (isLocation)
mainActivity.mainBinding.toolbar.toolbar.setTitle(mainActivity.locationModel.getName());
else mainActivity.mainBinding.toolbar.toolbar.setTitle("@"+ mainActivity.profileModel.getUsername());
else
mainActivity.mainBinding.toolbar.toolbar.setTitle("@" + mainActivity.profileModel.getUsername());
final PostModel model = result[result.length - 1];
if (model != null) {
@ -121,7 +144,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (autoloadPosts && hasNextPage)
currentlyExecuting = new PostsFetcher(
mainActivity.profileModel != null ? mainActivity.profileModel.getId()
: (mainActivity.hashtagModel != null ? mainActivity.userQuery : mainActivity.locationModel.getId()), endCursor, this)
: (mainActivity.hashtagModel != null ? mainActivity.userQuery : mainActivity.locationModel.getId()), endCursor, this)
.setUsername((isLocation || isHashtag) ? null : mainActivity.profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else {
@ -129,8 +152,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
model.setPageCursor(false, null);
}
}
else {
} else {
mainActivity.mainBinding.profileView.swipeRefreshLayout.setRefreshing(false);
mainActivity.mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
mainActivity.mainBinding.profileView.privatePage2.setText(R.string.empty_acc);
@ -146,22 +168,80 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onResult(final FeedModel[] result) {
if (result != null) {
final int oldSize = mainActivity.feedItems.size();
mainActivity.feedItems.addAll(Arrays.asList(result));
feedAdapter.notifyItemRangeInserted(oldSize, result.length);
if (result == null) {
return;
}
final int oldSize = mainActivity.feedItems.size();
final HashMap<String, FeedModel> thumbToFeedMap = new HashMap<>();
for (final FeedModel feedModel : result) {
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
mainActivity.mainBinding.feedView.feedPosts.post(() -> mainActivity.mainBinding.feedView.feedPosts.setNestedScrollingEnabled(true));
@Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
// dataSource
final Map<String, Object> extras = dataSource.getExtras();
if (extras == null) {
return;
}
// Log.d(TAG, "extras: " + extras);
final Uri thumbUri = (Uri) extras.get("uri_source");
if (thumbUri == null) {
return;
}
final Integer encodedWidth = (Integer) extras.get("encoded_width");
final Integer encodedHeight = (Integer) extras.get("encoded_height");
if (encodedWidth == null || encodedHeight == null) {
return;
}
final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
if (feedModel == null) {
return;
}
int requiredWidth = Utils.displayMetrics.widthPixels;
int resultingHeight = Utils.getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
if (feedModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
// If its a video and the height is too large, need to reduce the height,
// so that entire video fits on screen
resultingHeight = RESIZED_VIDEO_HEIGHT;
requiredWidth = Utils.getResultingWidth(RESIZED_VIDEO_HEIGHT, resultingHeight, requiredWidth);
}
feedModel.setImageWidth(requiredWidth);
feedModel.setImageHeight(resultingHeight);
success++;
updateAdapter();
}
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
@Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
failed++;
updateAdapter();
}
}
mainActivity.mainBinding.feedView.feedSwipeRefreshLayout.setRefreshing(false);
public void updateAdapter() {
if (failed + success != result.length) return;
mainActivity.feedItems.addAll(Arrays.asList(result));
feedAdapter.submitList(mainActivity.feedItems);
feedAdapter.notifyItemRangeInserted(oldSize, result.length);
mainActivity.mainBinding.feedView.feedPosts.post(() -> mainActivity.mainBinding.feedView.feedPosts.setNestedScrollingEnabled(true));
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
mainActivity.mainBinding.feedView.feedSwipeRefreshLayout.setRefreshing(false);
}
};
for (final FeedModel feedModel : result) {
final DataSource<Void> ds = Fresco.getImagePipeline().prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
}
};
private final FetchListener<DiscoverItemModel[]> discoverFetchListener = new FetchListener<DiscoverItemModel[]>() {
@ -174,8 +254,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
public void onResult(final DiscoverItemModel[] result) {
if (result == null || result.length == 0) {
Toast.makeText(mainActivity, R.string.discover_empty, Toast.LENGTH_SHORT).show();
}
else if (result != null) {
} else {
final int oldSize = mainActivity.discoverItems.size();
mainActivity.discoverItems.addAll(Arrays.asList(result));
discoverAdapter.notifyItemRangeInserted(oldSize, result.length);
@ -201,7 +280,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
topicIds = result.getIds();
rankToken = result.getToken();
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(
mainActivity, android.R.layout.simple_spinner_dropdown_item, result.getNames() );
mainActivity, android.R.layout.simple_spinner_dropdown_item, result.getNames());
mainActivity.mainBinding.discoverType.setAdapter(spinnerArrayAdapter);
}
}
@ -225,7 +304,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
@Override
public void onClick(final RamboTextView view, final String text, final boolean isHashtag) {
new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (MainActivity.scanHack != null) MainActivity.scanHack.onResult(text);
}).show();
}
@ -240,29 +319,31 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
if (result != null && result.length > 0)
mainActivity.startActivity(new Intent(mainActivity, StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, result)
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
.putExtra(Constants.FEED, stories)
.putExtra(Constants.FEED_ORDER, index)
.putExtra(Constants.EXTRAS_STORIES, result)
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername())
.putExtra(Constants.FEED, stories)
.putExtra(Constants.FEED_ORDER, index)
);
else Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else
Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
@NonNull
private final MainActivity mainActivity;
private MainActivity mainActivity;
private Resources resources;
private final View collapsingToolbar;
private final RecyclerLazyLoader lazyLoader;
private boolean isHashtag, isUser, isLocation;
private boolean isHashtag;
private boolean isLocation;
private PostsAdapter postsAdapter;
private FeedAdapter feedAdapter;
private RecyclerLazyLoader feedLazyLoader, discoverLazyLoader;
private DiscoverAdapter discoverAdapter;
public SimpleExoPlayer currentFeedPlayer; // hack for remix drawer layout
private String cookie = settingsHelper.getString(Constants.COOKIE);
public boolean isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
private boolean isLoggedIn;
private RequestManager glide;
public MainHelper(@NonNull final MainActivity mainActivity) {
stopCurrentExecutor();
@ -270,6 +351,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
this.mainActivity = mainActivity;
this.resources = mainActivity.getResources();
this.autoloadPosts = settingsHelper.getBoolean(AUTOLOAD_POSTS);
glide = Glide.with(mainActivity);
mainActivity.mainBinding.profileView.swipeRefreshLayout.setOnRefreshListener(this);
mainActivity.mainBinding.profileView.mainUrl.setMovementMethod(new LinkMovementMethod());
@ -280,7 +362,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final ImageView iconDiscover = (ImageView) iconSlider.getChildAt(2);
final boolean isBottomToolbar = settingsHelper.getBoolean(BOTTOM_TOOLBAR);
isLoggedIn = !Utils.isEmpty(cookie);
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
if (!isLoggedIn) {
mainActivity.mainBinding.drawerLayout.removeView(mainActivity.mainBinding.feedView.feedLayout);
mainActivity.mainBinding.drawerLayout.removeView(mainActivity.mainBinding.discoverLayout);
@ -474,16 +556,101 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.mainPosts.addOnScrollListener(lazyLoader);
}
private final View.OnClickListener clickListener = v -> {
if (mainActivity == null) {
return;
}
final Object tag = v.getTag();
final Context context = v.getContext();
if (tag instanceof FeedModel) {
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked())
feedModel.toggleCaption();
feedModel.setMentionClicked(false);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
feedModel.toggleCaption();
} else {
final int id = v.getId();
switch (id) {
case R.id.btnComments:
mainActivity.startActivityForResult(new Intent(mainActivity, CommentsViewer.class)
.putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode())
.putExtra(Constants.EXTRAS_POST, feedModel.getPostId())
.putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()), 6969);
break;
case R.id.viewStoryPost:
mainActivity.startActivity(new Intent(mainActivity, PostViewer.class)
.putExtra(Constants.EXTRAS_INDEX, feedModel.getPosition())
.putExtra(Constants.EXTRAS_POST, new PostModel(feedModel.getShortCode(), false))
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.FEED_ITEMS));
break;
case R.id.btnDownload:
ProfileModel profileModel = feedModel.getProfileModel();
final String username = profileModel != null ? profileModel.getUsername() : null;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
Utils.batchDownload(context, username, DownloadMethod.DOWNLOAD_FEED, Collections.singletonList(feedModel));
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected)
postModels.add(sliderItem);
else if (sliderItem.isSelected()) {
postModels.add(sliderItem);
break;
}
}
}
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
if (breakWhenFoundSelected && postModels.size() == 0)
postModels.add(sliderItems[0]);
if (postModels.size() > 0)
Utils.batchDownload(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, clickListener1)
.setNegativeButton(R.string.post_viewer_download_album, clickListener1).show();
}
break;
case R.id.ivProfilePic:
profileModel = feedModel.getProfileModel();
if (profileModel != null)
mentionClickListener.onClick(null, profileModel.getUsername(), false);
break;
}
}
}
};
private void setupFeed() {
mainActivity.mainBinding.feedView.feedStories.setLayoutManager(new LinearLayoutManager(mainActivity, LinearLayoutManager.HORIZONTAL, false));
mainActivity.mainBinding.feedView.feedStories.setAdapter(feedStoriesAdapter);
refreshFeedStories();
final LinearLayoutManager layoutManager = new LinearLayoutManager(mainActivity);
mainActivity.mainBinding.feedView.feedPosts.setHasFixedSize(true);
mainActivity.mainBinding.feedView.feedPosts.setLayoutManager(layoutManager);
mainActivity.mainBinding.feedView.feedPosts.setAdapter(feedAdapter = new FeedAdapter(mainActivity, mainActivity.feedItems, (view, text, isHashtag) ->
mainActivity.mainBinding.feedView.feedPosts.setAdapter(feedAdapter = new FeedAdapter(glide, clickListener, (view, text, isHashtag) ->
new AlertDialog.Builder(mainActivity).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search)
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> {
if (MainActivity.scanHack != null) {
mainActivity.mainBinding.drawerLayout.closeDrawers();
MainActivity.scanHack.onResult(text);
@ -507,8 +674,11 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
}));
mainActivity.mainBinding.feedView.feedPosts.addOnScrollListener(new VideoAwareRecyclerScroller(mainActivity, mainActivity.feedItems,
(itemPos, player) -> currentFeedPlayer = player));
final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
if (shouldAutoPlay) {
mainActivity.mainBinding.feedView.feedPosts.addOnScrollListener(new VideoAwareRecyclerScroller());
}
mainActivity.mainBinding.feedView.feedPosts.addOnScrollListener(new PauseGlideOnFlingScrollListener(glide));
new FeedFetcher(feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -623,7 +793,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
} else {
mainActivity.addToStack();
mainActivity.userQuery = modelType == IntentModelType.HASHTAG ? ('#' + modelText) :
(modelType == IntentModelType.LOCATION ? modelText : ('@'+modelText));
(modelType == IntentModelType.LOCATION ? modelText : ('@' + modelText));
onRefresh();
}
}
@ -646,9 +816,9 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.appBarLayout.setExpanded(true, true);
mainActivity.mainBinding.profileView.privatePage.setVisibility(View.GONE);
mainActivity.mainBinding.profileView.privatePage2.setTextSize(28);
mainActivity.mainBinding.profileView.mainProfileImage.setImageBitmap(null);
mainActivity.mainBinding.profileView.mainHashtagImage.setImageBitmap(null);
mainActivity.mainBinding.profileView.mainLocationImage.setImageBitmap(null);
// mainActivity.mainBinding.profileView.mainProfileImage.setImageBitmap(null);
// mainActivity.mainBinding.profileView.mainHashtagImage.setImageBitmap(null);
// mainActivity.mainBinding.profileView.mainLocationImage.setImageBitmap(null);
mainActivity.mainBinding.profileView.mainUrl.setText(null);
mainActivity.mainBinding.profileView.locationUrl.setText(null);
mainActivity.mainBinding.profileView.mainFullName.setText(null);
@ -702,7 +872,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
isHashtag = mainActivity.userQuery.charAt(0) == '#';
isUser = mainActivity.userQuery.charAt(0) == '@';
final boolean isUser = mainActivity.userQuery.charAt(0) == '@';
isLocation = mainActivity.userQuery.contains("/");
collapsingToolbar.setVisibility(isUser ? View.VISIBLE : View.GONE);
@ -731,15 +901,15 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, result -> {
mainActivity.storyModels = result;
if (result != null && result.length > 0) mainActivity.mainBinding.profileView.mainHashtagImage.setStoriesBorder();
if (result != null && result.length > 0)
mainActivity.mainBinding.profileView.mainHashtagImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (hashtagModel.getFollowing() == true) {
if (hashtagModel.getFollowing()) {
mainActivity.mainBinding.profileView.btnFollowTag.setText(R.string.unfollow);
mainActivity.mainBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_purple_background)));
}
else {
} else {
mainActivity.mainBinding.profileView.btnFollowTag.setText(R.string.follow);
mainActivity.mainBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_pink_background)));
@ -749,8 +919,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.btnFollowTag.setText(R.string.unfavorite_short);
mainActivity.mainBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_purple_background)));
}
else {
} else {
mainActivity.mainBinding.profileView.btnFollowTag.setText(R.string.favorite_short);
mainActivity.mainBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_pink_background)));
@ -758,7 +927,8 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
mainActivity.mainBinding.profileView.mainHashtagImage.setEnabled(false);
new MyTask().execute();
// new MyTask().execute();
mainActivity.mainBinding.profileView.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
mainActivity.mainBinding.profileView.mainHashtagImage.setEnabled(true);
final String postCount = String.valueOf(hashtagModel.getPostCount());
@ -790,20 +960,21 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final String profileId = profileModel.getId();
if (isLoggedIn || settingsHelper.getBoolean(Constants.STORIESIG)) {
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), false,
result -> {
mainActivity.storyModels = result;
if (result != null && result.length > 0) mainActivity.mainBinding.profileView.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), result -> {
if (result != null && result.length > 0) {
mainActivity.mainBinding.profileView.highlightsList.setVisibility(View.VISIBLE);
mainActivity.highlightsAdapter.setData(result);
}
else mainActivity.mainBinding.profileView.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), false,
result -> {
mainActivity.storyModels = result;
// if (result != null && result.length > 0)
mainActivity.mainBinding.profileView.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && settingsHelper.getBoolean(Constants.STORIESIG)), result -> {
if (result != null && result.length > 0) {
mainActivity.mainBinding.profileView.highlightsList.setVisibility(View.VISIBLE);
mainActivity.highlightsAdapter.setData(result);
} else
mainActivity.mainBinding.profileView.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
if (isLoggedIn) {
@ -817,13 +988,11 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.btnFollow.setText(R.string.unfollow);
mainActivity.mainBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_purple_background)));
}
else if (profileModel.getRequested() == true) {
} else if (profileModel.getRequested() == true) {
mainActivity.mainBinding.profileView.btnFollow.setText(R.string.cancel);
mainActivity.mainBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_purple_background)));
}
else {
} else {
mainActivity.mainBinding.profileView.btnFollow.setText(R.string.follow);
mainActivity.mainBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_pink_background)));
@ -833,8 +1002,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.btnRestrict.setText(R.string.unrestrict);
mainActivity.mainBinding.profileView.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_green_background)));
}
else {
} else {
mainActivity.mainBinding.profileView.btnRestrict.setText(R.string.restrict);
mainActivity.mainBinding.profileView.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_orange_background)));
@ -865,8 +1033,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity, R.color.btn_red_background)));
}
}
}
else {
} else {
mainActivity.mainBinding.profileView.btnTagged.setVisibility(View.VISIBLE);
mainActivity.mainBinding.profileView.btnSaved.setVisibility(View.VISIBLE);
mainActivity.mainBinding.profileView.btnLiked.setVisibility(View.VISIBLE);
@ -879,8 +1046,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.btnFollow.setText(R.string.unfavorite_short);
mainActivity.mainBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_purple_background)));
}
else {
} else {
mainActivity.mainBinding.profileView.btnFollow.setText(R.string.favorite_short);
mainActivity.mainBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
mainActivity, R.color.btn_pink_background)));
@ -894,9 +1060,10 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
}
mainActivity.mainBinding.profileView.mainProfileImage.setEnabled(false);
new MyTask().execute();
mainActivity.mainBinding.profileView.mainProfileImage.setEnabled(true);
// mainActivity.mainBinding.profileView.mainProfileImage.setEnabled(false);
// new MyTask().execute();
mainActivity.mainBinding.profileView.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
// mainActivity.mainBinding.profileView.mainProfileImage.setEnabled(true);
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
@ -966,12 +1133,11 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
mainActivity.mainBinding.profileView.privatePage2.setText(R.string.empty_acc);
mainActivity.mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
else {
} else {
mainActivity.mainBinding.profileView.swipeRefreshLayout.setRefreshing(true);
mainActivity.mainBinding.profileView.mainPosts.setVisibility(View.VISIBLE);
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener).setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
mainActivity.mainBinding.profileView.mainFollowers.setClickable(false);
@ -985,8 +1151,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
}
).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else if (isLocation) {
} else if (isLocation) {
mainActivity.profileModel = null;
mainActivity.hashtagModel = null;
mainActivity.mainBinding.toolbar.toolbar.setTitle(mainActivity.userQuery);
@ -1008,13 +1173,15 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (isLoggedIn) {
new iStoryStatusFetcher(profileId.split("/")[0], null, true, false, false, false, result -> {
mainActivity.storyModels = result;
if (result != null && result.length > 0) mainActivity.mainBinding.profileView.mainLocationImage.setStoriesBorder();
if (result != null && result.length > 0)
mainActivity.mainBinding.profileView.mainLocationImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
mainActivity.mainBinding.profileView.mainLocationImage.setEnabled(false);
new MyTask().execute();
mainActivity.mainBinding.profileView.mainLocationImage.setEnabled(true);
// mainActivity.mainBinding.profileView.mainLocationImage.setEnabled(false);
// new MyTask().execute();
mainActivity.mainBinding.profileView.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
// mainActivity.mainBinding.profileView.mainLocationImage.setEnabled(true);
final String postCount = String.valueOf(locationModel.getPostCount());
@ -1031,8 +1198,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (Utils.isEmpty(biography)) {
mainActivity.mainBinding.profileView.locationBiography.setVisibility(View.GONE);
}
else if (Utils.hasMentions(biography)) {
} else if (Utils.hasMentions(biography)) {
mainActivity.mainBinding.profileView.locationBiography.setVisibility(View.VISIBLE);
biography = Utils.getMentionText(biography);
mainActivity.mainBinding.profileView.locationBiography.setText(biography, TextView.BufferType.SPANNABLE);
@ -1050,8 +1216,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
intent.setData(Uri.parse(locationModel.getGeo()));
mainActivity.startActivity(intent);
});
}
else {
} else {
mainActivity.mainBinding.profileView.btnMap.setVisibility(View.GONE);
mainActivity.mainBinding.profileView.btnMap.setOnClickListener(null);
}
@ -1061,7 +1226,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.locationUrl.setVisibility(View.GONE);
} else if (!url.startsWith("http")) {
mainActivity.mainBinding.profileView.locationUrl.setVisibility(View.VISIBLE);
mainActivity.mainBinding.profileView.locationUrl.setText(Utils.getSpannableUrl("http://"+url));
mainActivity.mainBinding.profileView.locationUrl.setText(Utils.getSpannableUrl("http://" + url));
} else {
mainActivity.mainBinding.profileView.locationUrl.setVisibility(View.VISIBLE);
mainActivity.mainBinding.profileView.locationUrl.setText(Utils.getSpannableUrl(url));
@ -1075,8 +1240,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
mainActivity.mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
mainActivity.mainBinding.profileView.privatePage2.setText(R.string.empty_acc);
mainActivity.mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
else {
} else {
mainActivity.mainBinding.profileView.swipeRefreshLayout.setRefreshing(true);
mainActivity.mainBinding.profileView.mainPosts.setVisibility(View.VISIBLE);
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener)
@ -1102,14 +1266,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private void toggleSelection(final PostModel postModel) {
if (postModel != null && postsAdapter != null && mainActivity.selectedItems.size() >= 100) {
Toast.makeText(mainActivity, R.string.downloader_too_many, Toast.LENGTH_SHORT);
}
else if (postModel != null && postsAdapter != null) {
} else if (postModel != null && postsAdapter != null) {
if (postModel.isSelected()) mainActivity.selectedItems.remove(postModel);
else if (mainActivity.selectedItems.size() >= 100) {
Toast.makeText(mainActivity, R.string.downloader_too_many, Toast.LENGTH_SHORT);
return;
}
else mainActivity.selectedItems.add(postModel);
} else mainActivity.selectedItems.add(postModel);
postModel.setSelected(!postModel.isSelected());
notifyAdapter(postModel);
}
@ -1120,10 +1282,10 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged();
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel);
if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(postsAdapter.isSelecting);
if (mainActivity.downloadAction != null)
mainActivity.downloadAction.setVisible(postsAdapter.isSelecting);
}
///////////////////////////////////////////////////
private void toggleDiscoverSelection(final DiscoverItemModel itemModel) {
if (itemModel != null && discoverAdapter != null) {
if (itemModel.isSelected()) mainActivity.selectedDiscoverItems.remove(itemModel);
@ -1138,19 +1300,22 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (itemModel.getPosition() < 0) discoverAdapter.notifyDataSetChanged();
else discoverAdapter.notifyItemChanged(itemModel.getPosition(), itemModel);
if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(discoverAdapter.isSelecting);
if (mainActivity.downloadAction != null)
mainActivity.downloadAction.setVisible(discoverAdapter.isSelecting);
}
public boolean isSelectionCleared() {
if (postsAdapter != null && postsAdapter.isSelecting) {
for (final PostModel postModel : mainActivity.selectedItems) postModel.setSelected(false);
for (final PostModel postModel : mainActivity.selectedItems)
postModel.setSelected(false);
mainActivity.selectedItems.clear();
postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
if (mainActivity.downloadAction != null) mainActivity.downloadAction.setVisible(false);
return false;
} else if (discoverAdapter != null && discoverAdapter.isSelecting) {
for (final DiscoverItemModel itemModel : mainActivity.selectedDiscoverItems) itemModel.setSelected(false);
for (final DiscoverItemModel itemModel : mainActivity.selectedDiscoverItems)
itemModel.setSelected(false);
mainActivity.selectedDiscoverItems.clear();
discoverAdapter.isSelecting = false;
discoverAdapter.notifyDataSetChanged();
@ -1196,29 +1361,31 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
return returnvalue;
}
class MyTask extends AsyncTask<Void, Bitmap, Void> {
private Bitmap mIcon_val;
protected Void doInBackground(Void... voids) {
try {
mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(
(mainActivity.hashtagModel != null) ? mainActivity.hashtagModel.getSdProfilePic() : (
(mainActivity.locationModel != null) ? mainActivity.locationModel.getSdProfilePic() :
mainActivity.profileModel.getSdProfilePic())
).getContent());
} catch (Throwable ex) {
Log.e("austin_debug", "bitmap: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (mainActivity.hashtagModel != null) mainActivity.mainBinding.profileView.mainHashtagImage.setImageBitmap(mIcon_val);
else if (mainActivity.locationModel != null) mainActivity.mainBinding.profileView.mainLocationImage.setImageBitmap(mIcon_val);
else mainActivity.mainBinding.profileView.mainProfileImage.setImageBitmap(mIcon_val);
}
}
// class MyTask extends AsyncTask<Void, Bitmap, Void> {
// private Bitmap mIcon_val;
//
// protected Void doInBackground(Void... voids) {
// try {
// mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(
// (mainActivity.hashtagModel != null) ? mainActivity.hashtagModel.getSdProfilePic() : (
// (mainActivity.locationModel != null) ? mainActivity.locationModel.getSdProfilePic() :
// mainActivity.profileModel.getSdProfilePic())
// ).getContent());
// } catch (Throwable ex) {
// Log.e("austin_debug", "bitmap: " + ex);
// }
// return null;
// }
//
// @Override
// protected void onPostExecute(Void result) {
// if (mainActivity.hashtagModel != null)
// mainActivity.mainBinding.profileView.mainHashtagImage.setImageBitmap(mIcon_val);
// else if (mainActivity.locationModel != null)
// mainActivity.mainBinding.profileView.mainLocationImage.setImageBitmap(mIcon_val);
// else mainActivity.mainBinding.profileView.mainProfileImage.setImageBitmap(mIcon_val);
// }
// }
private final View.OnClickListener profileActionListener = new View.OnClickListener() {
@Override
@ -1243,18 +1410,18 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
new ProfileAction().execute("followtag");
} else if (v == mainActivity.mainBinding.profileView.btnTagged || (v == mainActivity.mainBinding.profileView.btnRestrict && !isLoggedIn)) {
mainActivity.startActivity(new Intent(mainActivity, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "%"+ mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+ mainActivity.profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "%" + mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + mainActivity.profileModel.getUsername())
);
} else if (v == mainActivity.mainBinding.profileView.btnSaved) {
mainActivity.startActivity(new Intent(mainActivity, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "$"+ mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+ mainActivity.profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "$" + mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + mainActivity.profileModel.getUsername())
);
} else if (v == mainActivity.mainBinding.profileView.btnLiked) {
mainActivity.startActivity(new Intent(mainActivity, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "^"+ mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+ mainActivity.profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "^" + mainActivity.profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + mainActivity.profileModel.getUsername())
);
}
}
@ -1266,17 +1433,17 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
protected Void doInBackground(String... rawAction) {
action = rawAction[0];
final String url = "https://www.instagram.com/web/"+
((action == "followtag" && mainActivity.hashtagModel != null) ? ("tags/"+
(mainActivity.hashtagModel.getFollowing() == true ? "unfollow/" : "follow/")+ mainActivity.hashtagModel.getName()+"/") : (
((action == "restrict" && mainActivity.profileModel != null) ? "restrict_action" : ("friendships/"+ mainActivity.profileModel.getId()))+"/"+
((action == "follow" && mainActivity.profileModel != null) ?
((mainActivity.profileModel.getFollowing() == true ||
(mainActivity.profileModel.getFollowing() == false && mainActivity.profileModel.getRequested() == true))
? "unfollow/" : "follow/") :
((action == "restrict" && mainActivity.profileModel != null) ?
(mainActivity.profileModel.getRestricted() == true ? "unrestrict/" : "restrict/") :
(mainActivity.profileModel.getBlocked() == true ? "unblock/" : "block/")))));
final String url = "https://www.instagram.com/web/" +
((action == "followtag" && mainActivity.hashtagModel != null) ? ("tags/" +
(mainActivity.hashtagModel.getFollowing() == true ? "unfollow/" : "follow/") + mainActivity.hashtagModel.getName() + "/") : (
((action == "restrict" && mainActivity.profileModel != null) ? "restrict_action" : ("friendships/" + mainActivity.profileModel.getId())) + "/" +
((action == "follow" && mainActivity.profileModel != null) ?
((mainActivity.profileModel.getFollowing() == true ||
(mainActivity.profileModel.getFollowing() == false && mainActivity.profileModel.getRequested() == true))
? "unfollow/" : "follow/") :
((action == "restrict" && mainActivity.profileModel != null) ?
(mainActivity.profileModel.getRestricted() == true ? "unrestrict/" : "restrict/") :
(mainActivity.profileModel.getBlocked() == true ? "unblock/" : "block/")))));
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
@ -1284,7 +1451,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
if (action == "restrict") {
final String urlParameters = "target_user_id="+ mainActivity.profileModel.getId();
final String urlParameters = "target_user_id=" + mainActivity.profileModel.getId();
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "" +
urlParameters.getBytes().length);
@ -1293,15 +1460,14 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
}
else urlConnection.connect();
} else urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}
else Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} else
Toast.makeText(mainActivity, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", action+": " + ex);
Log.e("austin_debug", action + ": " + ex);
}
return null;
}

17
app/src/main/java/awais/instagrabber/activities/MainActivity.java

@ -73,14 +73,13 @@ public final class MainActivity extends BaseLanguageActivity {
private static final int INITIAL_DELAY_MILLIS = 200;
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) {
@ -88,7 +87,7 @@ public final class MainActivity extends BaseLanguageActivity {
if (tag instanceof HighlightModel) {
final HighlightModel highlightModel = (HighlightModel) tag;
new iStoryStatusFetcher(highlightModel.getId(), null, false, false,
(!mainHelper.isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
if (result != null && result.length > 0)
startActivity(new Intent(MainActivity.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
@ -101,6 +100,7 @@ public final class MainActivity extends BaseLanguageActivity {
}
}
});
private SuggestionsAdapter suggestionAdapter;
private MenuItem searchAction;
public ActivityMainBinding mainBinding;
@ -119,6 +119,7 @@ public final class MainActivity extends BaseLanguageActivity {
private DataBox.CookieModel cookieModel;
private Runnable runnable;
private Handler handler;
private boolean isLoggedIn;
@Override
protected void onCreate(@Nullable final Bundle bundle) {
@ -142,7 +143,7 @@ public final class MainActivity extends BaseLanguageActivity {
setStack(bundle);
userQuery = bundle.getString("query");
}
mainHelper.isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE));
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null;
itemGetter = itemGetType -> {
if (itemGetType == ItemGetType.MAIN_ITEMS) return allItems;
@ -164,11 +165,11 @@ public final class MainActivity extends BaseLanguageActivity {
if (uid != null) {
final FetchListener<String> fetchListener = username -> {
if (!Utils.isEmpty(username)) {
if (!BuildConfig.DEBUG) {
// if (!BuildConfig.DEBUG) {
userQuery = username;
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing())
mainHelper.onRefresh();
}
// }
// adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid);
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername()))
@ -251,7 +252,7 @@ public final class MainActivity extends BaseLanguageActivity {
allItems.clear();
mainBinding.profileView.privatePage1.setImageResource(R.drawable.ic_info);
mainBinding.profileView.privatePage2.setTextSize(20);
mainBinding.profileView.privatePage2.setText(mainHelper.isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage2.setText(isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null)

122
app/src/main/java/awais/instagrabber/activities/PostViewer.java

@ -44,6 +44,8 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
@ -51,8 +53,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.json.JSONObject;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsMediaAdapter;
import awais.instagrabber.asyncs.PostFetcher;
@ -131,7 +131,7 @@ public final class PostViewer extends BaseLanguageActivity {
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();
.setNeutralButton(R.string.cancel, null).setTitle(viewerPostModel.getUsername()).show();
} else if (v == viewerBinding.ivToggleFullScreen) {
toggleFullscreen();
@ -243,7 +243,7 @@ public final class PostViewer extends BaseLanguageActivity {
}
}
setupPostInfoBar("@"+viewerPostModel.getUsername(), viewerPostModel.getItemType(), viewerPostModel.getLocation());
setupPostInfoBar("@" + viewerPostModel.getUsername(), viewerPostModel.getItemType(), viewerPostModel.getLocation());
postCaption = postModel.getPostCaption();
viewerCaptionParent.setVisibility(View.VISIBLE);
@ -289,8 +289,7 @@ public final class PostViewer extends BaseLanguageActivity {
));
containerLayoutParams.weight = (containerLayoutParams.weight == 3.3f) ? 3.3f : 2.2f;
viewerBinding.container.setLayoutParams(containerLayoutParams);
}
else {
} else {
viewerBinding.btnLike.setOnClickListener(onClickListener);
viewerBinding.btnBookmark.setOnClickListener(onClickListener);
}
@ -321,8 +320,7 @@ public final class PostViewer extends BaseLanguageActivity {
if (itemGetType == ItemGetType.SAVED_ITEMS && SavedViewer.itemGetter != null) {
itemGetterItems = SavedViewer.itemGetter.get(itemGetType);
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.SAVED_ITEMS && isFromShare);
}
else if (itemGetType != null && MainActivity.itemGetter != null) {
} else if (itemGetType != null && MainActivity.itemGetter != null) {
itemGetterItems = MainActivity.itemGetter.get(itemGetType);
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare);
} else {
@ -401,7 +399,7 @@ public final class PostViewer extends BaseLanguageActivity {
private void searchUsername(final String text) {
startActivity(
new Intent(getApplicationContext(), ProfileViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, text)
.putExtra(Constants.EXTRAS_USERNAME, text)
);
}
@ -509,9 +507,9 @@ public final class PostViewer extends BaseLanguageActivity {
};
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();
.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));
}
@ -586,7 +584,7 @@ public final class PostViewer extends BaseLanguageActivity {
viewerBinding.bottomPanel.viewerCaption.setText(postCaption);
}
setupPostInfoBar("@"+viewerPostModel.getUsername(), viewerPostModel.getItemType(),
setupPostInfoBar("@" + viewerPostModel.getUsername(), viewerPostModel.getItemType(),
viewerPostModel.getLocation());
if (postModel instanceof PostModel) {
@ -599,8 +597,7 @@ public final class PostViewer extends BaseLanguageActivity {
+ ((ok && viewerPostModel.getLike() != liked) ? (liked ? 1L : -1L) : 0L)));
viewerBinding.btnLike.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
getApplicationContext(), R.color.btn_pink_background)));
}
else {
} else {
viewerBinding.btnLike.setText(resources.getString(R.string.like, viewerPostModel.getLikes()
+ ((ok && viewerPostModel.getLike() != liked) ? (liked ? 1L : -1L) : 0L)));
viewerBinding.btnLike.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
@ -610,8 +607,7 @@ public final class PostViewer extends BaseLanguageActivity {
viewerBinding.btnBookmark.setText(R.string.unbookmark);
viewerBinding.btnBookmark.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
getApplicationContext(), R.color.btn_orange_background)));
}
else {
} else {
viewerBinding.btnBookmark.setText(R.string.bookmark);
viewerBinding.btnBookmark.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
getApplicationContext(), R.color.btn_lightorange_background)));
@ -639,55 +635,55 @@ public final class PostViewer extends BaseLanguageActivity {
private void setupPostInfoBar(final String from, final MediaItemType mediaItemType, final JSONObject location) {
if (prevUsername == null || !prevUsername.equals(from)) {
viewerBinding.topPanel.ivProfilePic.setImageBitmap(null);
viewerBinding.topPanel.ivProfilePic.setImageDrawable(null);
viewerBinding.topPanel.ivProfilePic.setImageResource(0);
// viewerBinding.topPanel.ivProfilePic.setImageBitmap(null);
// viewerBinding.topPanel.ivProfilePic.setImageDrawable(null);
// viewerBinding.topPanel.ivProfilePic.setImageResource(0);
viewerBinding.topPanel.ivProfilePic.setImageRequest(null);
if (!Utils.isEmpty(from) && from.charAt(0) == '@')
new ProfileFetcher(from.substring(1), result -> {
profileModel = result;
if (result != null) {
final String hdProfilePic = result.getHdProfilePic();
final String sdProfilePic = result.getSdProfilePic();
// final String hdProfilePic = result.getHdProfilePic();
// final String sdProfilePic = result.getSdProfilePic();
postUserId = result.getId();
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);
// 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);
viewerBinding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
final View viewStoryPost = findViewById(R.id.viewStoryPost);
if (viewStoryPost != null) {
viewStoryPost.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (result.isPrivate())
Toast.makeText(getApplicationContext(), R.string.share_private_post, Toast.LENGTH_LONG).show();
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, "https://instagram.com/p/"+postShortCode);
startActivity(Intent.createChooser(sharingIntent,
(result.isPrivate()) ? getString(R.string.share_private_post) : getString(R.string.share_public_post)));
}
viewStoryPost.setOnClickListener(v -> {
if (result.isPrivate())
Toast.makeText(getApplicationContext(), R.string.share_private_post, Toast.LENGTH_LONG).show();
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(Intent.EXTRA_TEXT, "https://instagram.com/p/" + postShortCode);
startActivity(Intent.createChooser(sharingIntent,
(result.isPrivate()) ? getString(R.string.share_private_post) : getString(R.string.share_public_post)));
});
}
}
@ -710,11 +706,10 @@ public final class PostViewer extends BaseLanguageActivity {
viewerBinding.topPanel.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
));
}
else {
} else {
viewerBinding.topPanel.location.setVisibility(View.VISIBLE);
viewerBinding.topPanel.location.setText(location.optString("name"));
viewerBinding.topPanel.location.setOnClickListener(v -> searchUsername(location.optString("id")+"/"+location.optString("slug")));
viewerBinding.topPanel.location.setOnClickListener(v -> searchUsername(location.optString("id") + "/" + location.optString("slug")));
viewerBinding.topPanel.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT
));
@ -736,7 +731,7 @@ public final class PostViewer extends BaseLanguageActivity {
protected Void doInBackground(String... rawAction) {
action = rawAction[0];
final String url = "https://www.instagram.com/web/"+action+"/"+postModel.getPostId()+"/"+ (action == "save" ?
final String url = "https://www.instagram.com/web/" + action + "/" + postModel.getPostId() + "/" + (action == "save" ?
(saved ? "unsave/" : "save/") :
(liked ? "unlike/" : "like/"));
try {
@ -752,7 +747,7 @@ public final class PostViewer extends BaseLanguageActivity {
}
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", action+": " + ex);
Log.e("austin_debug", action + ": " + ex);
}
return null;
}
@ -762,12 +757,11 @@ public final class PostViewer extends BaseLanguageActivity {
if (ok == true && action == "likes") {
liked = !liked;
refreshPost();
}
else if (ok == true && action == "save") {
} else if (ok == true && action == "save") {
saved = !saved;
refreshPost();
}
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} else
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
}
}

201
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java

@ -16,15 +16,10 @@ 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.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@ -32,8 +27,6 @@ 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;
@ -54,12 +47,10 @@ import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
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.databinding.ActivityProfileBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
@ -78,7 +69,6 @@ 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 ProfileViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
@ -111,7 +101,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.toolbar.toolbar.setTitle(userQuery);
else if (isLocation)
profileBinding.toolbar.toolbar.setTitle(locationModel.getName());
else profileBinding.toolbar.toolbar.setTitle("@"+profileModel.getUsername());
else profileBinding.toolbar.toolbar.setTitle("@" + profileModel.getUsername());
final PostModel model = result[result.length - 1];
if (model != null) {
@ -120,7 +110,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
if (autoloadPosts && hasNextPage)
currentlyExecuting = new PostsFetcher(
profileModel != null ? profileModel.getId()
: (hashtagModel != null ? ("#"+hashtagModel.getName()) : locationModel.getId()), endCursor, this)
: (hashtagModel != null ? ("#" + hashtagModel.getName()) : locationModel.getId()), endCursor, this)
.setUsername((isLocation || isHashtag) ? null : profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else {
@ -128,8 +118,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
}
model.setPageCursor(false, null);
}
}
else {
} else {
profileBinding.profileView.swipeRefreshLayout.setRefreshing(false);
profileBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
profileBinding.profileView.privatePage2.setText(R.string.empty_acc);
@ -157,7 +146,8 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result)
);
else Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else
Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@ -183,7 +173,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
return;
}
userQuery = (userQuery.contains("/") || userQuery.startsWith("#") || userQuery.startsWith("@")) ? userQuery : ("@"+userQuery);
userQuery = (userQuery.contains("/") || userQuery.startsWith("#") || userQuery.startsWith("@")) ? userQuery : ("@" + userQuery);
profileBinding = ActivityProfileBinding.inflate(getLayoutInflater());
setContentView(profileBinding.getRoot());
@ -198,10 +188,10 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
newintent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
}
else newintent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
} else
newintent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(newintent);
};
@ -249,7 +239,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.swipeRefreshLayout.setRefreshing(true);
stopCurrentExecutor();
currentlyExecuting = new PostsFetcher(profileModel != null ? profileModel.getId()
: (hashtagModel != null ? ("#"+hashtagModel.getName()) : locationModel.getId()), endCursor, postsFetchListener)
: (hashtagModel != null ? ("#" + hashtagModel.getName()) : locationModel.getId()), endCursor, postsFetchListener)
.setUsername((isHashtag || isLocation) ? null : profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
@ -268,7 +258,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
} else {
// because sometimes configuration changes made this crash on some phones
new AlertDialog.Builder(this).setAdapter(profileDialogAdapter, profileDialogListener)
.setNeutralButton(R.string.cancel, null).show();
.setNeutralButton(R.string.cancel, null).show();
}
}
};
@ -295,9 +285,9 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.appBarLayout.setExpanded(true, true);
profileBinding.profileView.privatePage.setVisibility(View.GONE);
profileBinding.profileView.privatePage2.setTextSize(28);
profileBinding.profileView.mainProfileImage.setImageBitmap(null);
profileBinding.profileView.mainHashtagImage.setImageBitmap(null);
profileBinding.profileView.mainLocationImage.setImageBitmap(null);
// profileBinding.profileView.mainProfileImage.setImageBitmap(null);
// profileBinding.profileView.mainHashtagImage.setImageBitmap(null);
// profileBinding.profileView.mainLocationImage.setImageBitmap(null);
profileBinding.profileView.mainUrl.setText(null);
profileBinding.profileView.locationUrl.setText(null);
profileBinding.profileView.mainFullName.setText(null);
@ -380,15 +370,15 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) profileBinding.profileView.mainHashtagImage.setStoriesBorder();
if (stories != null && stories.length > 0)
profileBinding.profileView.mainHashtagImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (hashtagModel.getFollowing() == true) {
profileBinding.profileView.btnFollowTag.setText(R.string.unfollow);
profileBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_purple_background)));
}
else {
} else {
profileBinding.profileView.btnFollowTag.setText(R.string.follow);
profileBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_pink_background)));
@ -398,16 +388,15 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.btnFollowTag.setText(R.string.unfavorite_short);
profileBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_purple_background)));
}
else {
} else {
profileBinding.profileView.btnFollowTag.setText(R.string.favorite_short);
profileBinding.profileView.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_pink_background)));
}
}
profileBinding.profileView.mainHashtagImage.setEnabled(false);
new MyTask().execute();
// profileBinding.profileView.mainHashtagImage.setEnabled(false);
profileBinding.profileView.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
profileBinding.profileView.mainHashtagImage.setEnabled(true);
final String postCount = String.valueOf(hashtagModel.getPostCount());
@ -439,20 +428,20 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
final String profileId = profileModel.getId();
if (isLoggedIn || Utils.settingsHelper.getBoolean(Constants.STORIESIG)) {
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), false,
stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) profileBinding.profileView.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), hls -> {
if (hls != null && hls.length > 0) {
profileBinding.profileView.highlightsList.setVisibility(View.VISIBLE);
highlightsAdapter.setData(hls);
}
else profileBinding.profileView.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), false,
stories -> {
storyModels = stories;
// if (stories != null && stories.length > 0)
profileBinding.profileView.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), hls -> {
if (hls != null && hls.length > 0) {
profileBinding.profileView.highlightsList.setVisibility(View.VISIBLE);
highlightsAdapter.setData(hls);
} else profileBinding.profileView.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
if (isLoggedIn) {
@ -466,13 +455,11 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.btnFollow.setText(R.string.unfollow);
profileBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_purple_background)));
}
else if (profileModel.getRequested() == true) {
} else if (profileModel.getRequested() == true) {
profileBinding.profileView.btnFollow.setText(R.string.cancel);
profileBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_purple_background)));
}
else {
} else {
profileBinding.profileView.btnFollow.setText(R.string.follow);
profileBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_pink_background)));
@ -482,8 +469,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.btnRestrict.setText(R.string.unrestrict);
profileBinding.profileView.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_green_background)));
}
else {
} else {
profileBinding.profileView.btnRestrict.setText(R.string.restrict);
profileBinding.profileView.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_orange_background)));
@ -514,8 +500,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
ProfileViewer.this, R.color.btn_red_background)));
}
}
}
else {
} else {
profileBinding.profileView.btnTagged.setVisibility(View.VISIBLE);
profileBinding.profileView.btnSaved.setVisibility(View.VISIBLE);
profileBinding.profileView.btnLiked.setVisibility(View.VISIBLE);
@ -528,8 +513,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.btnFollow.setText(R.string.unfavorite_short);
profileBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_purple_background)));
}
else {
} else {
profileBinding.profileView.btnFollow.setText(R.string.favorite_short);
profileBinding.profileView.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
ProfileViewer.this, R.color.btn_pink_background)));
@ -543,9 +527,9 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
}
}
profileBinding.profileView.mainProfileImage.setEnabled(false);
new MyTask().execute();
profileBinding.profileView.mainProfileImage.setEnabled(true);
// profileBinding.profileView.mainProfileImage.setEnabled(false);
profileBinding.profileView.mainProfileImage.setImageURI(profileModel.getSdProfilePic(), null);
// profileBinding.profileView.mainProfileImage.setEnabled(true);
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
@ -615,12 +599,11 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
profileBinding.profileView.privatePage2.setText(R.string.empty_acc);
profileBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
else {
} else {
profileBinding.profileView.swipeRefreshLayout.setRefreshing(true);
profileBinding.profileView.mainPosts.setVisibility(View.VISIBLE);
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener).setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
profileBinding.profileView.mainFollowers.setClickable(false);
@ -634,8 +617,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
}
}
).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else if (isLocation) {
} else if (isLocation) {
profileModel = null;
hashtagModel = null;
profileBinding.toolbar.toolbar.setTitle(userQuery);
@ -657,12 +639,13 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
if (isLoggedIn) {
new iStoryStatusFetcher(profileId.split("/")[0], null, true, false, false, false, stories -> {
storyModels = stories;
if (stories != null && stories.length > 0) profileBinding.profileView.mainLocationImage.setStoriesBorder();
if (stories != null && stories.length > 0)
profileBinding.profileView.mainLocationImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
profileBinding.profileView.mainLocationImage.setEnabled(false);
new MyTask().execute();
// profileBinding.profileView.mainLocationImage.setEnabled(false);
profileBinding.profileView.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
profileBinding.profileView.mainLocationImage.setEnabled(true);
final String postCount = String.valueOf(locationModel.getPostCount());
@ -680,8 +663,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
if (Utils.isEmpty(biography)) {
profileBinding.profileView.locationBiography.setVisibility(View.GONE);
}
else if (Utils.hasMentions(biography)) {
} else if (Utils.hasMentions(biography)) {
profileBinding.profileView.locationBiography.setVisibility(View.VISIBLE);
biography = Utils.getMentionText(biography);
profileBinding.profileView.locationBiography.setText(biography, TextView.BufferType.SPANNABLE);
@ -699,8 +681,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
intent.setData(Uri.parse(locationModel.getGeo()));
startActivity(intent);
});
}
else {
} else {
profileBinding.profileView.btnMap.setVisibility(View.GONE);
profileBinding.profileView.btnMap.setOnClickListener(null);
}
@ -710,7 +691,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.locationUrl.setVisibility(View.GONE);
} else if (!url.startsWith("http")) {
profileBinding.profileView.locationUrl.setVisibility(View.VISIBLE);
profileBinding.profileView.locationUrl.setText(Utils.getSpannableUrl("http://"+url));
profileBinding.profileView.locationUrl.setText(Utils.getSpannableUrl("http://" + url));
} else {
profileBinding.profileView.locationUrl.setVisibility(View.VISIBLE);
profileBinding.profileView.locationUrl.setText(Utils.getSpannableUrl(url));
@ -724,8 +705,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
profileBinding.profileView.privatePage1.setImageResource(R.drawable.ic_cancel);
profileBinding.profileView.privatePage2.setText(R.string.empty_acc);
profileBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
else {
} else {
profileBinding.profileView.swipeRefreshLayout.setRefreshing(true);
profileBinding.profileView.mainPosts.setVisibility(View.VISIBLE);
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener)
@ -771,8 +751,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis(),
locationModel != null ? locationModel.getName() : userQuery.replaceAll("^@", "")));
favouriteAction.setIcon(R.drawable.ic_like);
}
else {
} else {
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(userQuery,
Long.parseLong(Utils.dataBox.getFavorite(userQuery).split("/")[1]),
locationModel != null ? locationModel.getName() : userQuery.replaceAll("^@", "")));
@ -790,8 +769,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
else if (selectedItems.size() >= 100) {
Toast.makeText(ProfileViewer.this, R.string.downloader_too_many, Toast.LENGTH_SHORT);
return;
}
else selectedItems.add(postModel);
} else selectedItems.add(postModel);
postModel.setSelected(!postModel.isSelected());
notifyAdapter(postModel);
}
@ -825,11 +803,15 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
protected Void doInBackground(Void... voids) {
try {
mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(
(hashtagModel != null) ? hashtagModel.getSdProfilePic() : (
(locationModel != null) ? locationModel.getSdProfilePic() :
profileModel.getSdProfilePic())
).getContent());
String url;
if (hashtagModel != null) {
url = hashtagModel.getSdProfilePic();
} else if (locationModel != null) {
url = locationModel.getSdProfilePic();
} else {
url = profileModel.getSdProfilePic();
}
mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(url).getContent());
} catch (Throwable ex) {
Log.e("austin_debug", "bitmap: " + ex);
}
@ -838,8 +820,10 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
@Override
protected void onPostExecute(Void result) {
if (hashtagModel != null) profileBinding.profileView.mainHashtagImage.setImageBitmap(mIcon_val);
else if (locationModel != null) profileBinding.profileView.mainLocationImage.setImageBitmap(mIcon_val);
if (hashtagModel != null)
profileBinding.profileView.mainHashtagImage.setImageBitmap(mIcon_val);
else if (locationModel != null)
profileBinding.profileView.mainLocationImage.setImageBitmap(mIcon_val);
else profileBinding.profileView.mainProfileImage.setImageBitmap(mIcon_val);
}
}
@ -867,18 +851,18 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
new ProfileAction().execute("followtag");
} else if (v == profileBinding.profileView.btnTagged || (v == profileBinding.profileView.btnRestrict && !isLoggedIn)) {
startActivity(new Intent(ProfileViewer.this, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "%"+profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "%" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
);
} else if (v == profileBinding.profileView.btnSaved) {
startActivity(new Intent(ProfileViewer.this, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "$"+profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "$" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
);
} else if (v == profileBinding.profileView.btnLiked) {
startActivity(new Intent(ProfileViewer.this, SavedViewer.class)
.putExtra(Constants.EXTRAS_INDEX, "^"+profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@"+profileModel.getUsername())
.putExtra(Constants.EXTRAS_INDEX, "^" + profileModel.getId())
.putExtra(Constants.EXTRAS_USER, "@" + profileModel.getUsername())
);
}
}
@ -890,17 +874,17 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
protected Void doInBackground(String... rawAction) {
action = rawAction[0];
final String url = "https://www.instagram.com/web/"+
((action == "followtag" && hashtagModel != null) ? ("tags/"+
(hashtagModel.getFollowing() == true ? "unfollow/" : "follow/")+hashtagModel.getName()+"/") : (
((action == "restrict" && profileModel != null) ? "restrict_action" : ("friendships/"+profileModel.getId()))+"/"+
((action == "follow" && profileModel != null) ?
((profileModel.getFollowing() == true ||
(profileModel.getFollowing() == false && profileModel.getRequested() == true))
? "unfollow/" : "follow/") :
((action == "restrict" && profileModel != null) ?
(profileModel.getRestricted() == true ? "unrestrict/" : "restrict/") :
(profileModel.getBlocked() == true ? "unblock/" : "block/")))));
final String url = "https://www.instagram.com/web/" +
((action.equals("followtag") && hashtagModel != null) ? ("tags/" +
(hashtagModel.getFollowing() ? "unfollow/" : "follow/") + hashtagModel.getName() + "/") : (
((action.equals("restrict") && profileModel != null) ? "restrict_action" : ("friendships/" + profileModel.getId())) + "/" +
((action.equals("follow") && profileModel != null) ?
((profileModel.getFollowing() ||
(!profileModel.getFollowing() && profileModel.getRequested()))
? "unfollow/" : "follow/") :
((action.equals("restrict") && profileModel != null) ?
(profileModel.getRestricted() ? "unrestrict/" : "restrict/") :
(profileModel.getBlocked() ? "unblock/" : "block/")))));
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
@ -908,7 +892,7 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
if (action == "restrict") {
final String urlParameters = "target_user_id="+profileModel.getId();
final String urlParameters = "target_user_id=" + profileModel.getId();
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "" +
urlParameters.getBytes().length);
@ -917,22 +901,21 @@ public final class ProfileViewer extends BaseLanguageActivity implements SwipeRe
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
}
else urlConnection.connect();
} else urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}
else Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
} else
Toast.makeText(ProfileViewer.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", action+": " + ex);
Log.e("austin_debug", action + ": " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (ok == true) {
if (ok) {
onRefresh();
}
}

517
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java

@ -1,499 +1,118 @@
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.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
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.RelativeLayout;
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 androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
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 org.json.JSONObject;
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.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
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;
public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> {
private static final String TAG = "FeedAdapter";
// private final static String ellipsize = "… more";
private final RequestManager glide;
private final View.OnClickListener clickListener;
private final MentionClickListener mentionClickListener;
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())
.putExtra(Constants.EXTRAS_POST, feedModel.getPostId())
.putExtra(Constants.EXTRAS_USER, feedModel.getProfileModel().getId()), 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(), false))
.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;
}
}
}
}
};
public SimpleExoPlayer pagerPlayer;
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;
private static final DiffUtil.ItemCallback<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() {
@Override
public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
@Override
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
}
};
public FeedAdapter(final Activity activity, final ArrayList<FeedModel> FeedModels, final MentionClickListener mentionClickListener) {
this.activity = activity;
this.feedModels = FeedModels;
public FeedAdapter(final RequestManager glide,
final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) {
super(diffCallback);
this.glide = glide;
this.clickListener = clickListener;
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);
final int titleLen = profileModel.getUsername().length() + 1;
final SpannableString spannableString = new SpannableString("@"+profileModel.getUsername());
spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
viewHolder.username.setText(spannableString);
viewHolder.username.setMovementMethod(new LinkMovementMethod());
viewHolder.username.setMentionClickListener((view, text, isHashtag) ->
mentionClickListener.onClick(null, profileModel.getUsername(), false));
final Context context = parent.getContext();
final LayoutInflater layoutInflater = LayoutInflater.from(context);
final MediaItemType type = MediaItemType.valueOf(viewType);
switch (type) {
case MEDIA_TYPE_VIDEO: {
final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false);
return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
}
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));
viewHolder.btnComments.setTag(feedModel);
viewHolder.btnComments.setOnClickListener(clickListener);
viewHolder.btnComments.setEnabled(true);
final JSONObject location = feedModel.getLocation();
if (location == null) {
viewHolder.location.setVisibility(View.GONE);
viewHolder.username.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
));
}
else {
viewHolder.location.setVisibility(View.VISIBLE);
viewHolder.location.setText(location.optString("name"));
viewHolder.username.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT
));
viewHolder.location.setOnClickListener(v ->
new AlertDialog.Builder(v.getContext()).setTitle(location.optString("name"))
.setMessage(R.string.comment_view_mention_location_search)
.setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok,
(dialog, which) -> mentionClickListener.onClick(null, location.optString("id")+"/"+location.optString("slug"), false)).show()
);
}
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;
final Player player = viewHolder.playerView.getPlayer();
if (player != null) {
final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
player.setPlayWhenReady(shouldAutoplay);
}
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.mute : R.drawable.vol);
Utils.sessionVolumeFull = intVol == 1f;
}
};
viewHolder.btnMute.setOnClickListener(muteClickListener);
viewHolder.mediaList.setAdapter(new ChildMediaItemsAdapter(sliderItems, viewHolder.btnMute, playerChangeListener));
}
} else {
viewToChangeHeight = viewHolder.imageView;
String url = displayUrl;
if (Utils.isEmpty(url)) url = thumbnailUrl;
glideRequestManager.load(url).into(viewHolder.imageView);
}
case MEDIA_TYPE_SLIDER: {
final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false);
return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener);
}
if (viewToChangeHeight != null) {
final ViewGroup.LayoutParams layoutParams = viewToChangeHeight.getLayoutParams();
layoutParams.height = Utils.displayMetrics.widthPixels + 1;
viewToChangeHeight.setLayoutParams(layoutParams);
default:
case MEDIA_TYPE_IMAGE: {
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
return new FeedPhotoViewHolder(binding, glide, mentionClickListener, clickListener, longClickListener);
}
}
}
@Override
public int getItemCount() {
return feedModels == null ? 0 : feedModels.size();
public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) {
final FeedModel feedModel = getItem(position);
if (feedModel == null) {
return;
}
feedModel.setPosition(position);
viewHolder.bind(feedModel);
}
@Override
public int getItemViewType(final int position) {
if (feedModels != null) return feedModels.get(position).getItemType().ordinal();
return MediaItemType.MEDIA_TYPE_IMAGE.ordinal();
return getItem(position).getItemType().getId();
}
/**
* 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;
if (Utils.hasMentions(caption))
textView.setText(Utils.getMentionText(caption), TextView.BufferType.SPANNABLE);
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);
@Override
public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewAttachedToWindow(holder);
// Log.d(TAG, "attached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.startPlayingVideo();
}
private static final class ChildMediaItemsAdapter extends PagerAdapter {
private final PlayerChangeListener playerChangeListener;
private final ViewerPostModel[] sliderItems;
private final View btnMute;
private SimpleExoPlayer player;
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems, final View btnMute,
final PlayerChangeListener playerChangeListener) {
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) {
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);
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;
}
@Override
public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewDetachedFromWindow(holder);
// Log.d(TAG, "detached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.stopPlayingVideo();
}
}

6
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java

@ -7,8 +7,6 @@ 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;
@ -37,11 +35,9 @@ public final class FeedStoriesAdapter extends RecyclerView.Adapter<HighlightView
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);
holder.icon.setImageURI(profileModel.getSdProfilePic());
holder.icon.setAlpha(feedStoryModel.getFullyRead() ? 0.5F : 1.0F);
holder.title.setAlpha(feedStoryModel.getFullyRead() ? 0.5F : 1.0F);
}

53
app/src/main/java/awais/instagrabber/adapters/viewholder/FeedItemViewHolder.java

@ -1,53 +0,0 @@
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 commentsCount, videoViews, mediaCounter, tvPostDate, location;
public final RamboTextView username, 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);
location = itemView.findViewById(R.id.location);
// 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);
}
}

3
app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java

@ -8,9 +8,10 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.CircularImageView;
public final class HighlightViewHolder extends RecyclerView.ViewHolder {
public final ImageView icon;
public final CircularImageView icon;
public final TextView title;
public HighlightViewHolder(@NonNull final View itemView) {

153
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java

@ -0,0 +1,153 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestManager;
import org.json.JSONObject;
import awais.instagrabber.R;
import awais.instagrabber.customviews.CommentMentionClickSpan;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedBottomBinding;
import awais.instagrabber.databinding.ItemFeedTopBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Utils;
public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
public static final int MAX_CHARS = 255;
private final ItemFeedTopBinding topBinding;
private final ItemFeedBottomBinding bottomBinding;
private final MentionClickListener mentionClickListener;
boolean captionExpanded = false;
public FeedItemViewHolder(@NonNull final View root,
final ItemFeedTopBinding topBinding,
final ItemFeedBottomBinding bottomBinding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(root);
this.topBinding = topBinding;
this.bottomBinding = bottomBinding;
this.mentionClickListener = mentionClickListener;
topBinding.title.setMovementMethod(new LinkMovementMethod());
bottomBinding.btnComments.setOnClickListener(clickListener);
topBinding.viewStoryPost.setOnClickListener(clickListener);
topBinding.ivProfilePic.setOnClickListener(clickListener);
bottomBinding.btnDownload.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
}
public void bind(final FeedModel feedModel) {
if (feedModel == null) {
return;
}
topBinding.viewStoryPost.setTag(feedModel);
topBinding.ivProfilePic.setTag(feedModel);
bottomBinding.btnDownload.setTag(feedModel);
bottomBinding.viewerCaption.setTag(feedModel);
bottomBinding.btnComments.setTag(feedModel);
final ProfileModel profileModel = feedModel.getProfileModel();
if (profileModel != null) {
// glide.load(profileModel.getSdProfilePic())
// .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
// .into(topBinding.ivProfilePic);
topBinding.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
final int titleLen = profileModel.getUsername().length() + 1;
final SpannableString spannableString = new SpannableString("@" + profileModel.getUsername());
spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
topBinding.title.setText(spannableString);
topBinding.title.setMentionClickListener((view, text, isHashtag) -> mentionClickListener.onClick(null, profileModel.getUsername(), false));
}
bottomBinding.tvPostDate.setText(feedModel.getPostDate());
final long commentsCount = feedModel.getCommentsCount();
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
final JSONObject location = feedModel.getLocation();
setLocation(location);
CharSequence postCaption = feedModel.getPostCaption();
final boolean captionEmpty = Utils.isEmpty(postCaption);
bottomBinding.viewerCaption.setVisibility(captionEmpty ? View.GONE : View.VISIBLE);
if (!captionEmpty) {
if (Utils.hasMentions(postCaption)) {
postCaption = Utils.getMentionText(postCaption);
feedModel.setPostCaption(postCaption);
bottomBinding.viewerCaption.setText(postCaption, TextView.BufferType.SPANNABLE);
} else {
bottomBinding.viewerCaption.setText(postCaption);
}
}
expandCollapseTextView(bottomBinding.viewerCaption, feedModel.getPostCaption());
bindItem(feedModel);
}
private void setLocation(final JSONObject location) {
if (location == null) {
topBinding.location.setVisibility(View.GONE);
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
));
} else {
topBinding.location.setVisibility(View.VISIBLE);
topBinding.location.setText(location.optString("name"));
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT
));
topBinding.location.setOnClickListener(v -> {
new AlertDialog.Builder(v.getContext()).setTitle(location.optString("name"))
.setMessage(R.string.comment_view_mention_location_search)
.setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok,
(dialog, which) -> mentionClickListener.onClick(null, location.optString("id") + "/" + location.optString("slug"), false)).show();
});
}
}
/**
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation]
*
* @param textView the {@link RamboTextView} view, to expand and collapse
* @param caption
* @return isExpanded
*/
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, final CharSequence caption) {
if (Utils.isEmpty(caption)) return false;
final TextView.BufferType bufferType = caption instanceof Spanned ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL;
if (!textView.isCaptionExpanded()) {
int i = Utils.indexOfChar(caption, '\r', 0);
if (i == -1) i = Utils.indexOfChar(caption, '\n', 0);
if (i == -1) i = MAX_CHARS;
final int captionLen = caption.length();
final int minTrim = Math.min(MAX_CHARS, i);
if (captionLen <= minTrim) return false;
if (Utils.hasMentions(caption))
textView.setText(Utils.getMentionText(caption), TextView.BufferType.SPANNABLE);
textView.setCaptionIsExpandable(true);
textView.setCaptionIsExpanded(true);
} else {
textView.setText(caption, bufferType);
textView.setCaptionIsExpanded(false);
}
return true;
}
public abstract void bindItem(final FeedModel feedModel);
}

87
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java

@ -0,0 +1,87 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.bumptech.glide.RequestManager;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Utils;
public class FeedPhotoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedPhotoViewHolder";
private final ItemFeedPhotoBinding binding;
private final RequestManager glide;
private final ColorDrawable drawable;
// private final PipelineDraweeControllerBuilder controllerBuilder;
// private final CustomTarget<Bitmap> customTarget;
public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding,
final RequestManager glide,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding;
this.glide = glide;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
drawable = new ColorDrawable(Color.WHITE);
binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
binding.imageViewer.setHierarchy(hierarchy);
}
@Override
public void bindItem(final FeedModel feedModel) {
// glide.clear(customTarget);
if (feedModel == null) {
return;
}
final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = Utils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.imageViewer.requestLayout();
final String thumbnailUrl = feedModel.getThumbnailUrl();
String url = feedModel.getDisplayUrl();
if (Utils.isEmpty(url)) url = thumbnailUrl;
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
binding.imageViewer.setController(Fresco.newDraweeControllerBuilder()
.setImageRequest(requestBuilder)
.setOldController(binding.imageViewer.getController())
.setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl))
.build());
// binding.imageViewer.setImageURI(url);
// final RequestBuilder<Bitmap> thumbnailRequestBuilder = glide
// .asBitmap()
// .load(thumbnailUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL);
// glide.asBitmap()
// .load(url)
// .thumbnail(thumbnailRequestBuilder)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(customTarget);
}
}

339
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java

@ -0,0 +1,339 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ViewSwitcher;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedSliderViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedSliderViewHolder";
private static final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final ItemFeedSliderBinding binding;
private final DefaultDataSourceFactory dataSourceFactory;
private final PlayerChangeListener playerChangeListener = (position, player) -> {
pagerPlayer = player;
playerPosition = position;
};
private CacheDataSourceFactory cacheDataSourceFactory;
private SimpleExoPlayer pagerPlayer;
private int playerPosition = 0;
public FeedSliderViewHolder(@NonNull final ItemFeedSliderBinding binding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
layoutParams.height = Utils.displayMetrics.widthPixels + 1;
binding.mediaList.setLayoutParams(layoutParams);
final Context context = binding.getRoot().getContext();
dataSourceFactory = new DefaultDataSourceFactory(context, "instagram");
final SimpleCache simpleCache = Utils.getSimpleCacheInstance(context);
if (simpleCache != null) {
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory);
}
}
@Override
public void bindItem(final FeedModel feedModel) {
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0;
if (sliderItemLen <= 0) {
return;
}
final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen));
final PagerAdapter adapter = binding.mediaList.getAdapter();
if (adapter != null) {
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
adapter.destroyItem(binding.mediaList, i, binding.mediaList.getChildAt(i));
}
}
final ChildMediaItemsAdapter itemsAdapter = new ChildMediaItemsAdapter(sliderItems,
cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
playerChangeListener);
binding.mediaList.setAdapter(itemsAdapter);
//noinspection deprecation
binding.mediaList.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
private int prevPos = 0;
@Override
public void onPageSelected(final int position) {
ViewerPostModel sliderItem = sliderItems[prevPos];
if (sliderItem != null) {
sliderItem.setSelected(false);
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// stop playing prev video
final ViewSwitcher prevChild = (ViewSwitcher) binding.mediaList.getChildAt(prevPos);
if (prevChild == null || prevChild.getTag() == null || !(prevChild.getTag() instanceof SimpleExoPlayer)) {
return;
}
((SimpleExoPlayer) prevChild.getTag()).setPlayWhenReady(false);
}
}
sliderItem = sliderItems[position];
if (sliderItem == null) return;
sliderItem.setSelected(true);
final String text = (position + 1) + "/" + sliderItemLen;
binding.mediaCounter.setText(text);
prevPos = position;
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
if (shouldAutoPlay) {
autoPlay(position);
}
} else binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
}
});
final View.OnClickListener muteClickListener = v -> {
final int currentItem = binding.mediaList.getCurrentItem();
if (currentItem < 0 || currentItem >= binding.mediaList.getChildCount()) {
return;
}
final ViewerPostModel sliderItem = sliderItems[currentItem];
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
final View currentView = binding.mediaList.getChildAt(currentItem);
if (!(currentView instanceof ViewSwitcher)) {
return;
}
final ViewSwitcher viewSwitcher = (ViewSwitcher) currentView;
final Object tag = viewSwitcher.getTag();
if (!(tag instanceof SimpleExoPlayer)) {
return;
}
final SimpleExoPlayer player = (SimpleExoPlayer) tag;
final float intVol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(intVol);
binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute);
Utils.sessionVolumeFull = intVol == 1f;
};
final ViewerPostModel firstItem = sliderItems[0];
if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
}
binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.mute : R.drawable.vol);
binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener);
}
private void autoPlay(final int position) {
if (!shouldAutoPlay) {
return;
}
final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
if (adapter == null) {
return;
}
final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
loadPlayer(binding.getRoot().getContext(),
position, sliderItem.getDisplayUrl(),
viewSwitcher,
cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
playerChangeListener);
}
public void startPlayingVideo() {
autoPlay(playerPosition);
}
public void stopPlayingVideo() {
if (pagerPlayer == null) {
return;
}
pagerPlayer.setPlayWhenReady(false);
}
private interface PlayerChangeListener {
void playerChanged(final int position, final SimpleExoPlayer player);
}
private static void loadPlayer(final Context context,
final int position, final String displayUrl,
final ViewSwitcher viewSwitcher,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
if (viewSwitcher == null) {
return;
}
SimpleExoPlayer player = (SimpleExoPlayer) viewSwitcher.getTag();
if (player != null) {
player.setPlayWhenReady(true);
return;
}
player = new SimpleExoPlayer.Builder(context).build();
final PlayerView playerView = (PlayerView) viewSwitcher.getChildAt(1);
playerView.setPlayer(player);
if (viewSwitcher.getDisplayedChild() == 0) {
viewSwitcher.showNext();
}
playerView.setControllerShowTimeoutMs(1000);
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(factory).createMediaSource(Uri.parse(displayUrl));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setVolume(vol);
playerChangeListener.playerChanged(position, player);
viewSwitcher.setTag(player);
}
private static final class ChildMediaItemsAdapter extends PagerAdapter {
// private static final String TAG = "ChildMediaItemsAdapter";
private final ViewerPostModel[] sliderItems;
private final DataSource.Factory factory;
private final PlayerChangeListener playerChangeListener;
private final ViewGroup.LayoutParams layoutParams;
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
this.sliderItems = sliderItems;
this.factory = factory;
this.playerChangeListener = playerChangeListener;
layoutParams = new ViewGroup.LayoutParams(Utils.displayMetrics.widthPixels, Utils.displayMetrics.widthPixels + 1);
}
@NonNull
@Override
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
final Context context = container.getContext();
final ViewerPostModel sliderItem = sliderItems[position];
final String displayUrl = sliderItem.getDisplayUrl();
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getSliderDisplayUrl(), displayUrl);
container.addView(viewSwitcher);
return viewSwitcher;
}
final GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(container.getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
final SimpleDraweeView photoView = new SimpleDraweeView(context, hierarchy);
photoView.setLayoutParams(layoutParams);
final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(displayUrl))
.setLocalThumbnailPreviewsEnabled(true)
.setProgressiveRenderingEnabled(true)
.build();
photoView.setImageRequest(imageRequest);
container.addView(photoView);
return photoView;
}
@NonNull
private ViewSwitcher createViewSwitcher(final Context context, final int position, final String sliderDisplayUrl, final String displayUrl) {
final ViewSwitcher viewSwitcher = new ViewSwitcher(context);
viewSwitcher.setLayoutParams(layoutParams);
final FrameLayout frameLayout = new FrameLayout(context);
frameLayout.setLayoutParams(layoutParams);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build();
final SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context, hierarchy);
simpleDraweeView.setLayoutParams(layoutParams);
simpleDraweeView.setImageURI(sliderDisplayUrl);
frameLayout.addView(simpleDraweeView);
final AppCompatImageView imageView = new AppCompatImageView(context);
final int px = Utils.convertDpToPx(50);
final FrameLayout.LayoutParams playButtonLayoutParams = new FrameLayout.LayoutParams(px, px);
playButtonLayoutParams.gravity = Gravity.CENTER;
imageView.setLayoutParams(playButtonLayoutParams);
imageView.setImageResource(R.drawable.exo_icon_play);
frameLayout.addView(imageView);
viewSwitcher.addView(frameLayout);
final PlayerView playerView = new PlayerView(context);
viewSwitcher.addView(playerView);
if (shouldAutoPlay && position == 0) {
loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener);
} else
frameLayout.setOnClickListener(v -> loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener));
return viewSwitcher;
}
@Override
public void destroyItem(@NonNull final ViewGroup container, final int position, @NonNull final Object object) {
final View view = container.getChildAt(position);
// Log.d(TAG, "destroy position: " + position + ", view: " + view);
if (view instanceof ViewSwitcher) {
final Object tag = view.getTag();
if (tag instanceof SimpleExoPlayer) {
final SimpleExoPlayer player = (SimpleExoPlayer) tag;
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;
}
public ViewerPostModel getItemAtPosition(final int position) {
return sliderItems[0];
}
}
}

156
app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java

@ -0,0 +1,156 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedVideoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedVideoViewHolder";
private final ItemFeedVideoBinding binding;
private final Handler handler;
private final DefaultDataSourceFactory dataSourceFactory;
private CacheDataSourceFactory cacheDataSourceFactory;
private FeedModel feedModel;
private SimpleExoPlayer player;
final Runnable loadRunnable = new Runnable() {
@Override
public void run() {
loadPlayer(feedModel);
}
};
public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding,
final MentionClickListener mentionClickListener,
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.VISIBLE);
handler = new Handler(Looper.getMainLooper());
final Context context = binding.getRoot().getContext();
dataSourceFactory = new DefaultDataSourceFactory(context, "instagram");
final SimpleCache simpleCache = Utils.getSimpleCacheInstance(context);
if (simpleCache != null) {
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory);
}
}
@Override
public void bindItem(final FeedModel feedModel) {
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
this.feedModel = feedModel;
setThumbnail(feedModel);
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(feedModel.getViewCount()));
}
private void setThumbnail(final FeedModel feedModel) {
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams();
layoutParams.width = feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight();
binding.thumbnailParent.requestLayout();
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl()))
.setProgressiveRenderingEnabled(true)
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(thumbnailRequest)
.build();
binding.thumbnail.setController(controller);
binding.thumbnailParent.setOnClickListener(v -> loadPlayer(feedModel));
}
private void loadPlayer(final FeedModel feedModel) {
if (feedModel == null) {
return;
}
// Log.d(TAG, "playing post:" + feedModel.getPostId());
if (binding.viewSwitcher.getDisplayedChild() == 0) {
binding.viewSwitcher.showNext();
}
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = Utils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.playerView.requestLayout();
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
setMuteIcon(vol);
player = (SimpleExoPlayer) binding.playerView.getPlayer();
if (player != null) {
player.release();
}
player = new SimpleExoPlayer.Builder(itemView.getContext())
.setLooper(Looper.getMainLooper())
.build();
player.setVolume(vol);
player.setPlayWhenReady(true);
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
binding.playerView.setPlayer(player);
final SimpleExoPlayer finalPlayer = player;
binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
final float intVol = finalPlayer.getVolume() == 0f ? 1f : 0f;
finalPlayer.setVolume(intVol);
setMuteIcon(intVol);
Utils.sessionVolumeFull = intVol == 1f;
});
binding.playerView.setOnClickListener(v -> finalPlayer.setPlayWhenReady(!finalPlayer.getPlayWhenReady()));
}
private void setMuteIcon(final float vol) {
binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
}
public FeedModel getCurrentFeedModel() {
return feedModel;
}
public void stopPlaying() {
// Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
handler.removeCallbacks(loadRunnable);
if (player != null) {
player.release();
}
if (binding.viewSwitcher.getDisplayedChild() == 1) {
binding.viewSwitcher.showPrevious();
}
}
public void startPlaying() {
handler.removeCallbacks(loadRunnable);
handler.postDelayed(loadRunnable, 800);
}
}

162
app/src/main/java/awais/instagrabber/customviews/CircularImageView.java

@ -1,22 +1,23 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.Resources;
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;
import androidx.annotation.Nullable;
public final class CircularImageView extends AppCompatImageView {
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.drawee.view.SimpleDraweeView;
public final class CircularImageView extends SimpleDraweeView {
private final int borderSize = 8;
private int color = Color.TRANSPARENT;
private final Paint paint = new Paint();
@ -24,82 +25,115 @@ public final class CircularImageView extends AppCompatImageView {
private BitmapShader shader;
private Bitmap bitmap;
public CircularImageView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
setup();
}
public CircularImageView(final Context context) {
super(context);
inflateHierarchy(context, null);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
inflateHierarchy(context, attrs);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflateHierarchy(context, attrs);
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);
}
}
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
Resources resources = context.getResources();
final RoundingParams roundingParams = RoundingParams.asCircle();
GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(resources)
.setRoundingParams(roundingParams)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER);
GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setLayerType(LAYER_TYPE_HARDWARE, null);
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);
// }
// });
// }
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
// .setRoundingParams(RoundingParams.)
// .build();
// setHierarchy(hierarchy);
// invalidate();
}
@Override
protected void onDetachedFromWindow() {
setLayerType(LAYER_TYPE_NONE, null);
super.onDetachedFromWindow();
}
// @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();
// invalidate();
// final RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f);
//
RoundingParams roundingParams = getHierarchy().getRoundingParams();
if (roundingParams == null) {
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);
}
roundingParams.setBorder(color, 5.0f);
getHierarchy().setRoundingParams(roundingParams);
}
}

4
app/src/main/java/awais/instagrabber/customviews/RamboTextView.java

@ -176,4 +176,8 @@ public final class RamboTextView extends AppCompatTextView {
return null;
}
public boolean isCaptionExpanded() {
return isExpanded;
}
}

170
app/src/main/java/awais/instagrabber/customviews/drawee/AbstractAnimatedZoomableController.java

@ -0,0 +1,170 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.graphics.Matrix;
import android.graphics.PointF;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
/**
* Abstract class for ZoomableController that adds animation capabilities to
* DefaultZoomableController.
*/
public abstract class AbstractAnimatedZoomableController extends DefaultZoomableController {
private boolean mIsAnimating;
private final float[] mStartValues = new float[9];
private final float[] mStopValues = new float[9];
private final float[] mCurrentValues = new float[9];
private final Matrix mNewTransform = new Matrix();
private final Matrix mWorkingTransform = new Matrix();
public AbstractAnimatedZoomableController(TransformGestureDetector transformGestureDetector) {
super(transformGestureDetector);
}
@Override
public void reset() {
FLog.v(getLogTag(), "reset");
stopAnimation();
mWorkingTransform.reset();
mNewTransform.reset();
super.reset();
}
/**
* Returns true if the zoomable transform is identity matrix, and the controller is idle.
*/
@Override
public boolean isIdentity() {
return !isAnimating() && super.isIdentity();
}
/**
* Zooms to the desired scale and positions the image so that the given image point corresponds to
* the given view point.
*
* <p>If this method is called while an animation or gesture is already in progress, the current
* animation or gesture will be stopped first.
*
* @param scale desired scale, will be limited to {min, max} scale factor
* @param imagePoint 2D point in image's relative coordinate system (i.e. 0 <= x, y <= 1)
* @param viewPoint 2D point in view's absolute coordinate system
*/
@Override
public void zoomToPoint(float scale, PointF imagePoint, PointF viewPoint) {
zoomToPoint(scale, imagePoint, viewPoint, LIMIT_ALL, 0, null);
}
/**
* Zooms to the desired scale and positions the image so that the given image point corresponds to
* the given view point.
*
* <p>If this method is called while an animation or gesture is already in progress, the current
* animation or gesture will be stopped first.
*
* @param scale desired scale, will be limited to {min, max} scale factor
* @param imagePoint 2D point in image's relative coordinate system (i.e. 0 <= x, y <= 1)
* @param viewPoint 2D point in view's absolute coordinate system
* @param limitFlags whether to limit translation and/or scale.
* @param durationMs length of animation of the zoom, or 0 if no animation desired
* @param onAnimationComplete code to run when the animation completes. Ignored if durationMs=0
*/
public void zoomToPoint(
float scale,
PointF imagePoint,
PointF viewPoint,
@LimitFlag int limitFlags,
long durationMs,
@Nullable Runnable onAnimationComplete) {
FLog.v(getLogTag(), "zoomToPoint: duration %d ms", durationMs);
calculateZoomToPointTransform(mNewTransform, scale, imagePoint, viewPoint, limitFlags);
setTransform(mNewTransform, durationMs, onAnimationComplete);
}
/**
* Sets a new zoomable transformation and animates to it if desired.
*
* <p>If this method is called while an animation or gesture is already in progress, the current
* animation or gesture will be stopped first.
*
* @param newTransform new transform to make active
* @param durationMs duration of the animation, or 0 to not animate
* @param onAnimationComplete code to run when the animation completes. Ignored if durationMs=0
*/
public void setTransform(
Matrix newTransform, long durationMs, @Nullable Runnable onAnimationComplete) {
FLog.v(getLogTag(), "setTransform: duration %d ms", durationMs);
if (durationMs <= 0) {
setTransformImmediate(newTransform);
} else {
setTransformAnimated(newTransform, durationMs, onAnimationComplete);
}
}
private void setTransformImmediate(final Matrix newTransform) {
FLog.v(getLogTag(), "setTransformImmediate");
stopAnimation();
mWorkingTransform.set(newTransform);
super.setTransform(newTransform);
getDetector().restartGesture();
}
protected boolean isAnimating() {
return mIsAnimating;
}
protected void setAnimating(boolean isAnimating) {
mIsAnimating = isAnimating;
}
protected float[] getStartValues() {
return mStartValues;
}
protected float[] getStopValues() {
return mStopValues;
}
protected Matrix getWorkingTransform() {
return mWorkingTransform;
}
@Override
public void onGestureBegin(TransformGestureDetector detector) {
FLog.v(getLogTag(), "onGestureBegin");
stopAnimation();
super.onGestureBegin(detector);
}
@Override
public void onGestureUpdate(TransformGestureDetector detector) {
FLog.v(getLogTag(), "onGestureUpdate %s", isAnimating() ? "(ignored)" : "");
if (isAnimating()) {
return;
}
super.onGestureUpdate(detector);
}
protected void calculateInterpolation(Matrix outMatrix, float fraction) {
for (int i = 0; i < 9; i++) {
mCurrentValues[i] = (1 - fraction) * mStartValues[i] + fraction * mStopValues[i];
}
outMatrix.setValues(mCurrentValues);
}
public abstract void setTransformAnimated(
final Matrix newTransform, long durationMs, @Nullable final Runnable onAnimationComplete);
protected abstract void stopAnimation();
protected abstract Class<?> getLogTag();
}

105
app/src/main/java/awais/instagrabber/customviews/drawee/AnimatedZoomableController.java

@ -0,0 +1,105 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.Nullable;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
/**
* ZoomableController that adds animation capabilities to DefaultZoomableController using standard
* Android animation classes
*/
public class AnimatedZoomableController extends AbstractAnimatedZoomableController {
private static final Class<?> TAG = AnimatedZoomableController.class;
private final ValueAnimator mValueAnimator;
public static AnimatedZoomableController newInstance() {
return new AnimatedZoomableController(TransformGestureDetector.newInstance());
}
@SuppressLint("NewApi")
public AnimatedZoomableController(TransformGestureDetector transformGestureDetector) {
super(transformGestureDetector);
mValueAnimator = ValueAnimator.ofFloat(0, 1);
mValueAnimator.setInterpolator(new DecelerateInterpolator());
}
@SuppressLint("NewApi")
@Override
public void setTransformAnimated(
final Matrix newTransform, long durationMs, @Nullable final Runnable onAnimationComplete) {
FLog.v(getLogTag(), "setTransformAnimated: duration %d ms", durationMs);
stopAnimation();
Preconditions.checkArgument(durationMs > 0);
Preconditions.checkState(!isAnimating());
setAnimating(true);
mValueAnimator.setDuration(durationMs);
getTransform().getValues(getStartValues());
newTransform.getValues(getStopValues());
mValueAnimator.addUpdateListener(
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
calculateInterpolation(getWorkingTransform(), (float) valueAnimator.getAnimatedValue());
AnimatedZoomableController.super.setTransform(getWorkingTransform());
}
});
mValueAnimator.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
FLog.v(getLogTag(), "setTransformAnimated: animation cancelled");
onAnimationStopped();
}
@Override
public void onAnimationEnd(Animator animation) {
FLog.v(getLogTag(), "setTransformAnimated: animation finished");
onAnimationStopped();
}
private void onAnimationStopped() {
if (onAnimationComplete != null) {
onAnimationComplete.run();
}
setAnimating(false);
getDetector().restartGesture();
}
});
mValueAnimator.start();
}
@SuppressLint("NewApi")
@Override
public void stopAnimation() {
if (!isAnimating()) {
return;
}
FLog.v(getLogTag(), "stopAnimation");
mValueAnimator.cancel();
mValueAnimator.removeAllUpdateListeners();
mValueAnimator.removeAllListeners();
}
@Override
protected Class<?> getLogTag() {
return TAG;
}
}

720
app/src/main/java/awais/instagrabber/customviews/drawee/DefaultZoomableController.java

@ -0,0 +1,720 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.view.MotionEvent;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Zoomable controller that calculates transformation based on touch events.
*/
public class DefaultZoomableController
implements ZoomableController, TransformGestureDetector.Listener {
/**
* Interface for handling call backs when the image bounds are set.
*/
public interface ImageBoundsListener {
void onImageBoundsSet(RectF imageBounds);
}
@IntDef(
flag = true,
value = {LIMIT_NONE, LIMIT_TRANSLATION_X, LIMIT_TRANSLATION_Y, LIMIT_SCALE, LIMIT_ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface LimitFlag {}
public static final int LIMIT_NONE = 0;
public static final int LIMIT_TRANSLATION_X = 1;
public static final int LIMIT_TRANSLATION_Y = 2;
public static final int LIMIT_SCALE = 4;
public static final int LIMIT_ALL = LIMIT_TRANSLATION_X | LIMIT_TRANSLATION_Y | LIMIT_SCALE;
private static final float EPS = 1e-3f;
private static final Class<?> TAG = DefaultZoomableController.class;
private static final RectF IDENTITY_RECT = new RectF(0, 0, 1, 1);
private TransformGestureDetector mGestureDetector;
private @Nullable
ImageBoundsListener mImageBoundsListener;
private @Nullable
Listener mListener = null;
private boolean mIsEnabled = false;
private boolean mIsRotationEnabled = false;
private boolean mIsScaleEnabled = true;
private boolean mIsTranslationEnabled = true;
private boolean mIsGestureZoomEnabled = true;
private float mMinScaleFactor = 1.0f;
private float mMaxScaleFactor = 2.0f;
// View bounds, in view-absolute coordinates
private final RectF mViewBounds = new RectF();
// Non-transformed image bounds, in view-absolute coordinates
private final RectF mImageBounds = new RectF();
// Transformed image bounds, in view-absolute coordinates
private final RectF mTransformedImageBounds = new RectF();
private final Matrix mPreviousTransform = new Matrix();
private final Matrix mActiveTransform = new Matrix();
private final Matrix mActiveTransformInverse = new Matrix();
private final float[] mTempValues = new float[9];
private final RectF mTempRect = new RectF();
private boolean mWasTransformCorrected;
public static DefaultZoomableController newInstance() {
return new DefaultZoomableController(TransformGestureDetector.newInstance());
}
public DefaultZoomableController(TransformGestureDetector gestureDetector) {
mGestureDetector = gestureDetector;
mGestureDetector.setListener(this);
}
/**
* Rests the controller.
*/
public void reset() {
FLog.v(TAG, "reset");
mGestureDetector.reset();
mPreviousTransform.reset();
mActiveTransform.reset();
onTransformChanged();
}
/**
* Sets the zoomable listener.
*/
@Override
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Sets whether the controller is enabled or not.
*/
@Override
public void setEnabled(boolean enabled) {
mIsEnabled = enabled;
if (!enabled) {
reset();
}
}
/**
* Gets whether the controller is enabled or not.
*/
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Sets whether the rotation gesture is enabled or not.
*/
public void setRotationEnabled(boolean enabled) {
mIsRotationEnabled = enabled;
}
/**
* Gets whether the rotation gesture is enabled or not.
*/
public boolean isRotationEnabled() {
return mIsRotationEnabled;
}
/**
* Sets whether the scale gesture is enabled or not.
*/
public void setScaleEnabled(boolean enabled) {
mIsScaleEnabled = enabled;
}
/**
* Gets whether the scale gesture is enabled or not.
*/
public boolean isScaleEnabled() {
return mIsScaleEnabled;
}
/**
* Sets whether the translation gesture is enabled or not.
*/
public void setTranslationEnabled(boolean enabled) {
mIsTranslationEnabled = enabled;
}
/**
* Gets whether the translations gesture is enabled or not.
*/
public boolean isTranslationEnabled() {
return mIsTranslationEnabled;
}
/**
* Sets the minimum scale factor allowed.
*
* <p>Hierarchy's scaling (if any) is not taken into account.
*/
public void setMinScaleFactor(float minScaleFactor) {
mMinScaleFactor = minScaleFactor;
}
/**
* Gets the minimum scale factor allowed.
*/
public float getMinScaleFactor() {
return mMinScaleFactor;
}
/**
* Sets the maximum scale factor allowed.
*
* <p>Hierarchy's scaling (if any) is not taken into account.
*/
public void setMaxScaleFactor(float maxScaleFactor) {
mMaxScaleFactor = maxScaleFactor;
}
/**
* Gets the maximum scale factor allowed.
*/
public float getMaxScaleFactor() {
return mMaxScaleFactor;
}
/**
* Sets whether gesture zooms are enabled or not.
*/
public void setGestureZoomEnabled(boolean isGestureZoomEnabled) {
mIsGestureZoomEnabled = isGestureZoomEnabled;
}
/**
* Gets whether gesture zooms are enabled or not.
*/
public boolean isGestureZoomEnabled() {
return mIsGestureZoomEnabled;
}
/**
* Gets the current scale factor.
*/
@Override
public float getScaleFactor() {
return getMatrixScaleFactor(mActiveTransform);
}
/**
* Sets the image bounds, in view-absolute coordinates.
*/
@Override
public void setImageBounds(RectF imageBounds) {
if (!imageBounds.equals(mImageBounds)) {
mImageBounds.set(imageBounds);
onTransformChanged();
if (mImageBoundsListener != null) {
mImageBoundsListener.onImageBoundsSet(mImageBounds);
}
}
}
/**
* Gets the non-transformed image bounds, in view-absolute coordinates.
*/
public RectF getImageBounds() {
return mImageBounds;
}
/**
* Gets the transformed image bounds, in view-absolute coordinates
*/
private RectF getTransformedImageBounds() {
return mTransformedImageBounds;
}
/**
* Sets the view bounds.
*/
@Override
public void setViewBounds(RectF viewBounds) {
mViewBounds.set(viewBounds);
}
/**
* Gets the view bounds.
*/
public RectF getViewBounds() {
return mViewBounds;
}
/**
* Sets the image bounds listener.
*/
public void setImageBoundsListener(@Nullable ImageBoundsListener imageBoundsListener) {
mImageBoundsListener = imageBoundsListener;
}
/**
* Gets the image bounds listener.
*/
public @Nullable
ImageBoundsListener getImageBoundsListener() {
return mImageBoundsListener;
}
/**
* Returns true if the zoomable transform is identity matrix.
*/
@Override
public boolean isIdentity() {
return isMatrixIdentity(mActiveTransform, 1e-3f);
}
/**
* Returns true if the transform was corrected during the last update.
*
* <p>We should rename this method to `wasTransformedWithoutCorrection` and just return the
* internal flag directly. However, this requires interface change and negation of meaning.
*/
@Override
public boolean wasTransformCorrected() {
return mWasTransformCorrected;
}
/**
* Gets the matrix that transforms image-absolute coordinates to view-absolute coordinates. The
* zoomable transformation is taken into account.
*
* <p>Internal matrix is exposed for performance reasons and is not to be modified by the callers.
*/
@Override
public Matrix getTransform() {
return mActiveTransform;
}
/**
* Gets the matrix that transforms image-relative coordinates to view-absolute coordinates. The
* zoomable transformation is taken into account.
*/
public void getImageRelativeToViewAbsoluteTransform(Matrix outMatrix) {
outMatrix.setRectToRect(IDENTITY_RECT, mTransformedImageBounds, Matrix.ScaleToFit.FILL);
}
/**
* Maps point from view-absolute to image-relative coordinates. This takes into account the
* zoomable transformation.
*/
public PointF mapViewToImage(PointF viewPoint) {
float[] points = mTempValues;
points[0] = viewPoint.x;
points[1] = viewPoint.y;
mActiveTransform.invert(mActiveTransformInverse);
mActiveTransformInverse.mapPoints(points, 0, points, 0, 1);
mapAbsoluteToRelative(points, points, 1);
return new PointF(points[0], points[1]);
}
/**
* Maps point from image-relative to view-absolute coordinates. This takes into account the
* zoomable transformation.
*/
public PointF mapImageToView(PointF imagePoint) {
float[] points = mTempValues;
points[0] = imagePoint.x;
points[1] = imagePoint.y;
mapRelativeToAbsolute(points, points, 1);
mActiveTransform.mapPoints(points, 0, points, 0, 1);
return new PointF(points[0], points[1]);
}
/**
* Maps array of 2D points from view-absolute to image-relative coordinates. This does NOT take
* into account the zoomable transformation. Points are represented by a float array of [x0, y0,
* x1, y1, ...].
*
* @param destPoints destination array (may be the same as source array)
* @param srcPoints source array
* @param numPoints number of points to map
*/
private void mapAbsoluteToRelative(float[] destPoints, float[] srcPoints, int numPoints) {
for (int i = 0; i < numPoints; i++) {
destPoints[i * 2 + 0] = (srcPoints[i * 2 + 0] - mImageBounds.left) / mImageBounds.width();
destPoints[i * 2 + 1] = (srcPoints[i * 2 + 1] - mImageBounds.top) / mImageBounds.height();
}
}
/**
* Maps array of 2D points from image-relative to view-absolute coordinates. This does NOT take
* into account the zoomable transformation. Points are represented by float array of [x0, y0, x1,
* y1, ...].
*
* @param destPoints destination array (may be the same as source array)
* @param srcPoints source array
* @param numPoints number of points to map
*/
private void mapRelativeToAbsolute(float[] destPoints, float[] srcPoints, int numPoints) {
for (int i = 0; i < numPoints; i++) {
destPoints[i * 2 + 0] = srcPoints[i * 2 + 0] * mImageBounds.width() + mImageBounds.left;
destPoints[i * 2 + 1] = srcPoints[i * 2 + 1] * mImageBounds.height() + mImageBounds.top;
}
}
/**
* Zooms to the desired scale and positions the image so that the given image point corresponds to
* the given view point.
*
* @param scale desired scale, will be limited to {min, max} scale factor
* @param imagePoint 2D point in image's relative coordinate system (i.e. 0 <= x, y <= 1)
* @param viewPoint 2D point in view's absolute coordinate system
*/
public void zoomToPoint(float scale, PointF imagePoint, PointF viewPoint) {
FLog.v(TAG, "zoomToPoint");
calculateZoomToPointTransform(mActiveTransform, scale, imagePoint, viewPoint, LIMIT_ALL);
onTransformChanged();
}
/**
* Calculates the zoom transformation that would zoom to the desired scale and position the image
* so that the given image point corresponds to the given view point.
*
* @param outTransform the matrix to store the result to
* @param scale desired scale, will be limited to {min, max} scale factor
* @param imagePoint 2D point in image's relative coordinate system (i.e. 0 <= x, y <= 1)
* @param viewPoint 2D point in view's absolute coordinate system
* @param limitFlags whether to limit translation and/or scale.
* @return whether or not the transform has been corrected due to limitation
*/
protected boolean calculateZoomToPointTransform(
Matrix outTransform,
float scale,
PointF imagePoint,
PointF viewPoint,
@LimitFlag int limitFlags) {
float[] viewAbsolute = mTempValues;
viewAbsolute[0] = imagePoint.x;
viewAbsolute[1] = imagePoint.y;
mapRelativeToAbsolute(viewAbsolute, viewAbsolute, 1);
float distanceX = viewPoint.x - viewAbsolute[0];
float distanceY = viewPoint.y - viewAbsolute[1];
boolean transformCorrected = false;
outTransform.setScale(scale, scale, viewAbsolute[0], viewAbsolute[1]);
transformCorrected |= limitScale(outTransform, viewAbsolute[0], viewAbsolute[1], limitFlags);
outTransform.postTranslate(distanceX, distanceY);
transformCorrected |= limitTranslation(outTransform, limitFlags);
return transformCorrected;
}
/**
* Sets a new zoom transformation.
*/
public void setTransform(Matrix newTransform) {
FLog.v(TAG, "setTransform");
mActiveTransform.set(newTransform);
onTransformChanged();
}
/**
* Gets the gesture detector.
*/
protected TransformGestureDetector getDetector() {
return mGestureDetector;
}
/**
* Notifies controller of the received touch event.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
FLog.v(TAG, "onTouchEvent: action: ", event.getAction());
if (mIsEnabled && mIsGestureZoomEnabled) {
return mGestureDetector.onTouchEvent(event);
}
return false;
}
/* TransformGestureDetector.Listener methods */
@Override
public void onGestureBegin(TransformGestureDetector detector) {
FLog.v(TAG, "onGestureBegin");
mPreviousTransform.set(mActiveTransform);
onTransformBegin();
// We only received a touch down event so far, and so we don't know yet in which direction a
// future move event will follow. Therefore, if we can't scroll in all directions, we have to
// assume the worst case where the user tries to scroll out of edge, which would cause
// transformation to be corrected.
mWasTransformCorrected = !canScrollInAllDirection();
}
@Override
public void onGestureUpdate(TransformGestureDetector detector) {
FLog.v(TAG, "onGestureUpdate");
boolean transformCorrected = calculateGestureTransform(mActiveTransform, LIMIT_ALL);
onTransformChanged();
if (transformCorrected) {
mGestureDetector.restartGesture();
}
// A transformation happened, but was it without correction?
mWasTransformCorrected = transformCorrected;
}
@Override
public void onGestureEnd(TransformGestureDetector detector) {
FLog.v(TAG, "onGestureEnd");
onTransformEnd();
}
/**
* Calculates the zoom transformation based on the current gesture.
*
* @param outTransform the matrix to store the result to
* @param limitTypes whether to limit translation and/or scale.
* @return whether or not the transform has been corrected due to limitation
*/
protected boolean calculateGestureTransform(Matrix outTransform, @LimitFlag int limitTypes) {
TransformGestureDetector detector = mGestureDetector;
boolean transformCorrected = false;
outTransform.set(mPreviousTransform);
if (mIsRotationEnabled) {
float angle = detector.getRotation() * (float) (180 / Math.PI);
outTransform.postRotate(angle, detector.getPivotX(), detector.getPivotY());
}
if (mIsScaleEnabled) {
float scale = detector.getScale();
outTransform.postScale(scale, scale, detector.getPivotX(), detector.getPivotY());
}
transformCorrected |=
limitScale(outTransform, detector.getPivotX(), detector.getPivotY(), limitTypes);
if (mIsTranslationEnabled) {
outTransform.postTranslate(detector.getTranslationX(), detector.getTranslationY());
}
transformCorrected |= limitTranslation(outTransform, limitTypes);
return transformCorrected;
}
private void onTransformBegin() {
if (mListener != null && isEnabled()) {
mListener.onTransformBegin(mActiveTransform);
}
}
private void onTransformChanged() {
mActiveTransform.mapRect(mTransformedImageBounds, mImageBounds);
if (mListener != null && isEnabled()) {
mListener.onTransformChanged(mActiveTransform);
}
}
private void onTransformEnd() {
if (mListener != null && isEnabled()) {
mListener.onTransformEnd(mActiveTransform);
}
}
/**
* Keeps the scaling factor within the specified limits.
*
* @param pivotX x coordinate of the pivot point
* @param pivotY y coordinate of the pivot point
* @param limitTypes whether to limit scale.
* @return whether limiting has been applied or not
*/
private boolean limitScale(
Matrix transform, float pivotX, float pivotY, @LimitFlag int limitTypes) {
if (!shouldLimit(limitTypes, LIMIT_SCALE)) {
return false;
}
float currentScale = getMatrixScaleFactor(transform);
float targetScale = limit(currentScale, mMinScaleFactor, mMaxScaleFactor);
if (targetScale != currentScale) {
float scale = targetScale / currentScale;
transform.postScale(scale, scale, pivotX, pivotY);
return true;
}
return false;
}
/**
* Limits the translation so that there are no empty spaces on the sides if possible.
*
* <p>The image is attempted to be centered within the view bounds if the transformed image is
* smaller. There will be no empty spaces within the view bounds if the transformed image is
* bigger. This applies to each dimension (horizontal and vertical) independently.
*
* @param limitTypes whether to limit translation along the specific axis.
* @return whether limiting has been applied or not
*/
private boolean limitTranslation(Matrix transform, @LimitFlag int limitTypes) {
if (!shouldLimit(limitTypes, LIMIT_TRANSLATION_X | LIMIT_TRANSLATION_Y)) {
return false;
}
RectF b = mTempRect;
b.set(mImageBounds);
transform.mapRect(b);
float offsetLeft =
shouldLimit(limitTypes, LIMIT_TRANSLATION_X)
? getOffset(
b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
: 0;
float offsetTop =
shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
? getOffset(
b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
: 0;
if (offsetLeft != 0 || offsetTop != 0) {
transform.postTranslate(offsetLeft, offsetTop);
return true;
}
return false;
}
/**
* Checks whether the specified limit flag is present in the limits provided.
*
* <p>If the flag contains multiple flags together using a bitwise OR, this only checks that at
* least one of the flags is included.
*
* @param limits the limits to apply
* @param flag the limit flag(s) to check for
* @return true if the flag (or one of the flags) is included in the limits
*/
private static boolean shouldLimit(@LimitFlag int limits, @LimitFlag int flag) {
return (limits & flag) != LIMIT_NONE;
}
/**
* Returns the offset necessary to make sure that: - the image is centered within the limit if the
* image is smaller than the limit - there is no empty space on left/right if the image is bigger
* than the limit
*/
private float getOffset(
float imageStart, float imageEnd, float limitStart, float limitEnd, float limitCenter) {
float imageWidth = imageEnd - imageStart, limitWidth = limitEnd - limitStart;
float limitInnerWidth = Math.min(limitCenter - limitStart, limitEnd - limitCenter) * 2;
// center if smaller than limitInnerWidth
if (imageWidth < limitInnerWidth) {
return limitCenter - (imageEnd + imageStart) / 2;
}
// to the edge if in between and limitCenter is not (limitLeft + limitRight) / 2
if (imageWidth < limitWidth) {
if (limitCenter < (limitStart + limitEnd) / 2) {
return limitStart - imageStart;
} else {
return limitEnd - imageEnd;
}
}
// to the edge if larger than limitWidth and empty space visible
if (imageStart > limitStart) {
return limitStart - imageStart;
}
if (imageEnd < limitEnd) {
return limitEnd - imageEnd;
}
return 0;
}
/**
* Limits the value to the given min and max range.
*/
private float limit(float value, float min, float max) {
return Math.min(Math.max(min, value), max);
}
/**
* Gets the scale factor for the given matrix. This method assumes the equal scaling factor for X
* and Y axis.
*/
private float getMatrixScaleFactor(Matrix transform) {
transform.getValues(mTempValues);
return mTempValues[Matrix.MSCALE_X];
}
/**
* Same as {@code Matrix.isIdentity()}, but with tolerance {@code eps}.
*/
private boolean isMatrixIdentity(Matrix transform, float eps) {
// Checks whether the given matrix is close enough to the identity matrix:
// 1 0 0
// 0 1 0
// 0 0 1
// Or equivalently to the zero matrix, after subtracting 1.0f from the diagonal elements:
// 0 0 0
// 0 0 0
// 0 0 0
transform.getValues(mTempValues);
mTempValues[0] -= 1.0f; // m00
mTempValues[4] -= 1.0f; // m11
mTempValues[8] -= 1.0f; // m22
for (int i = 0; i < 9; i++) {
if (Math.abs(mTempValues[i]) > eps) {
return false;
}
}
return true;
}
/**
* Returns whether the scroll can happen in all directions. I.e. the image is not on any edge.
*/
private boolean canScrollInAllDirection() {
return mTransformedImageBounds.left < mViewBounds.left - EPS
&& mTransformedImageBounds.top < mViewBounds.top - EPS
&& mTransformedImageBounds.right > mViewBounds.right + EPS
&& mTransformedImageBounds.bottom > mViewBounds.bottom + EPS;
}
@Override
public int computeHorizontalScrollRange() {
return (int) mTransformedImageBounds.width();
}
@Override
public int computeHorizontalScrollOffset() {
return (int) (mViewBounds.left - mTransformedImageBounds.left);
}
@Override
public int computeHorizontalScrollExtent() {
return (int) mViewBounds.width();
}
@Override
public int computeVerticalScrollRange() {
return (int) mTransformedImageBounds.height();
}
@Override
public int computeVerticalScrollOffset() {
return (int) (mViewBounds.top - mTransformedImageBounds.top);
}
@Override
public int computeVerticalScrollExtent() {
return (int) mViewBounds.height();
}
public Listener getListener() {
return mListener;
}
}

84
app/src/main/java/awais/instagrabber/customviews/drawee/DoubleTapGestureListener.java

@ -0,0 +1,84 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
/**
* Tap gesture listener for double tap to zoom / unzoom and double-tap-and-drag to zoom.
*
* @see ZoomableDraweeView#setTapListener(GestureDetector.SimpleOnGestureListener)
*/
public class DoubleTapGestureListener extends GestureDetector.SimpleOnGestureListener {
private static final int DURATION_MS = 300;
private static final int DOUBLE_TAP_SCROLL_THRESHOLD = 20;
private final ZoomableDraweeView mDraweeView;
private final PointF mDoubleTapViewPoint = new PointF();
private final PointF mDoubleTapImagePoint = new PointF();
private float mDoubleTapScale = 1;
private boolean mDoubleTapScroll = false;
public DoubleTapGestureListener(ZoomableDraweeView zoomableDraweeView) {
mDraweeView = zoomableDraweeView;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
AbstractAnimatedZoomableController zc =
(AbstractAnimatedZoomableController) mDraweeView.getZoomableController();
PointF vp = new PointF(e.getX(), e.getY());
PointF ip = zc.mapViewToImage(vp);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mDoubleTapViewPoint.set(vp);
mDoubleTapImagePoint.set(ip);
mDoubleTapScale = zc.getScaleFactor();
break;
case MotionEvent.ACTION_MOVE:
mDoubleTapScroll = mDoubleTapScroll || shouldStartDoubleTapScroll(vp);
if (mDoubleTapScroll) {
float scale = calcScale(vp);
zc.zoomToPoint(scale, mDoubleTapImagePoint, mDoubleTapViewPoint);
}
break;
case MotionEvent.ACTION_UP:
if (mDoubleTapScroll) {
float scale = calcScale(vp);
zc.zoomToPoint(scale, mDoubleTapImagePoint, mDoubleTapViewPoint);
} else {
final float maxScale = zc.getMaxScaleFactor();
final float minScale = zc.getMinScaleFactor();
if (zc.getScaleFactor() < (maxScale + minScale) / 2) {
zc.zoomToPoint(
maxScale, ip, vp, DefaultZoomableController.LIMIT_ALL, DURATION_MS, null);
} else {
zc.zoomToPoint(
minScale, ip, vp, DefaultZoomableController.LIMIT_ALL, DURATION_MS, null);
}
}
mDoubleTapScroll = false;
break;
}
return true;
}
private boolean shouldStartDoubleTapScroll(PointF viewPoint) {
double dist =
Math.hypot(viewPoint.x - mDoubleTapViewPoint.x, viewPoint.y - mDoubleTapViewPoint.y);
return dist > DOUBLE_TAP_SCROLL_THRESHOLD;
}
private float calcScale(PointF currentViewPoint) {
float dy = (currentViewPoint.y - mDoubleTapViewPoint.y);
float t = 1 + Math.abs(dy) * 0.001f;
return (dy < 0) ? mDoubleTapScale / t : mDoubleTapScale * t;
}
}

72
app/src/main/java/awais/instagrabber/customviews/drawee/GestureListenerWrapper.java

@ -0,0 +1,72 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.view.GestureDetector;
import android.view.MotionEvent;
/**
* Wrapper for SimpleOnGestureListener as GestureDetector does not allow changing its listener.
*/
public class GestureListenerWrapper extends GestureDetector.SimpleOnGestureListener {
private GestureDetector.SimpleOnGestureListener mDelegate;
public GestureListenerWrapper() {
mDelegate = new GestureDetector.SimpleOnGestureListener();
}
public void setListener(GestureDetector.SimpleOnGestureListener listener) {
mDelegate = listener;
}
@Override
public void onLongPress(MotionEvent e) {
mDelegate.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return mDelegate.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return mDelegate.onFling(e1, e2, velocityX, velocityY);
}
@Override
public void onShowPress(MotionEvent e) {
mDelegate.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {
return mDelegate.onDown(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return mDelegate.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return mDelegate.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return mDelegate.onSingleTapConfirmed(e);
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return mDelegate.onSingleTapUp(e);
}
}

156
app/src/main/java/awais/instagrabber/customviews/drawee/MultiGestureListener.java

@ -0,0 +1,156 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.view.GestureDetector;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Gesture listener that allows multiple child listeners to be added and notified about gesture
* events.
*
* <p>NOTE: The order of the listeners is important. Listeners can consume gesture events. For
* example, if one of the child listeners consumes {@link #onLongPress(MotionEvent)} (the listener
* returned true), subsequent listeners will not be notified about the event any more since it has
* been consumed.
*/
public class MultiGestureListener extends GestureDetector.SimpleOnGestureListener {
private final List<GestureDetector.SimpleOnGestureListener> mListeners = new ArrayList<>();
/**
* Adds a listener to the multi gesture listener.
*
* <p>NOTE: The order of the listeners is important since gesture events can be consumed.
*
* @param listener the listener to be added
*/
public synchronized void addListener(GestureDetector.SimpleOnGestureListener listener) {
mListeners.add(listener);
}
/**
* Removes the given listener so that it will not be notified about future events.
*
* <p>NOTE: The order of the listeners is important since gesture events can be consumed.
*
* @param listener the listener to remove
*/
public synchronized void removeListener(GestureDetector.SimpleOnGestureListener listener) {
mListeners.remove(listener);
}
@Override
public synchronized boolean onSingleTapUp(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onSingleTapUp(e)) {
return true;
}
}
return false;
}
@Override
public synchronized void onLongPress(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
mListeners.get(i).onLongPress(e);
}
}
@Override
public synchronized boolean onScroll(
MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onScroll(e1, e2, distanceX, distanceY)) {
return true;
}
}
return false;
}
@Override
public synchronized boolean onFling(
MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onFling(e1, e2, velocityX, velocityY)) {
return true;
}
}
return false;
}
@Override
public synchronized void onShowPress(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
mListeners.get(i).onShowPress(e);
}
}
@Override
public synchronized boolean onDown(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onDown(e)) {
return true;
}
}
return false;
}
@Override
public synchronized boolean onDoubleTap(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onDoubleTap(e)) {
return true;
}
}
return false;
}
@Override
public synchronized boolean onDoubleTapEvent(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onDoubleTapEvent(e)) {
return true;
}
}
return false;
}
@Override
public synchronized boolean onSingleTapConfirmed(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onSingleTapConfirmed(e)) {
return true;
}
}
return false;
}
@Override
public synchronized boolean onContextClick(MotionEvent e) {
final int size = mListeners.size();
for (int i = 0; i < size; i++) {
if (mListeners.get(i).onContextClick(e)) {
return true;
}
}
return false;
}
}

286
app/src/main/java/awais/instagrabber/customviews/drawee/MultiPointerGestureDetector.java

@ -0,0 +1,286 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.view.MotionEvent;
/**
* Component that detects and tracks multiple pointers based on touch events.
*
* <p>Each time a pointer gets pressed or released, the current gesture (if any) will end, and a new
* one will be started (if there are still pressed pointers left). It is guaranteed that the number
* of pointers within the single gesture will remain the same during the whole gesture.
*/
public class MultiPointerGestureDetector {
/**
* The listener for receiving notifications when gestures occur.
*/
public interface Listener {
/**
* A callback called right before the gesture is about to start.
*/
public void onGestureBegin(MultiPointerGestureDetector detector);
/**
* A callback called each time the gesture gets updated.
*/
public void onGestureUpdate(MultiPointerGestureDetector detector);
/**
* A callback called right after the gesture has finished.
*/
public void onGestureEnd(MultiPointerGestureDetector detector);
}
private static final int MAX_POINTERS = 2;
private boolean mGestureInProgress;
private int mPointerCount;
private int mNewPointerCount;
private final int mId[] = new int[MAX_POINTERS];
private final float mStartX[] = new float[MAX_POINTERS];
private final float mStartY[] = new float[MAX_POINTERS];
private final float mCurrentX[] = new float[MAX_POINTERS];
private final float mCurrentY[] = new float[MAX_POINTERS];
private Listener mListener = null;
public MultiPointerGestureDetector() {
reset();
}
/**
* Factory method that creates a new instance of MultiPointerGestureDetector
*/
public static MultiPointerGestureDetector newInstance() {
return new MultiPointerGestureDetector();
}
/**
* Sets the listener.
*
* @param listener listener to set
*/
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Resets the component to the initial state.
*/
public void reset() {
mGestureInProgress = false;
mPointerCount = 0;
for (int i = 0; i < MAX_POINTERS; i++) {
mId[i] = MotionEvent.INVALID_POINTER_ID;
}
}
/**
* This method can be overridden in order to perform threshold check or something similar.
*
* @return whether or not to start a new gesture
*/
protected boolean shouldStartGesture() {
return true;
}
/**
* Starts a new gesture and calls the listener just before starting it.
*/
private void startGesture() {
if (!mGestureInProgress) {
if (mListener != null) {
mListener.onGestureBegin(this);
}
mGestureInProgress = true;
}
}
/**
* Stops the current gesture and calls the listener right after stopping it.
*/
private void stopGesture() {
if (mGestureInProgress) {
mGestureInProgress = false;
if (mListener != null) {
mListener.onGestureEnd(this);
}
}
}
/**
* Gets the index of the i-th pressed pointer. Normally, the index will be equal to i, except in
* the case when the pointer is released.
*
* @return index of the specified pointer or -1 if not found (i.e. not enough pointers are down)
*/
private int getPressedPointerIndex(MotionEvent event, int i) {
final int count = event.getPointerCount();
final int action = event.getActionMasked();
final int index = event.getActionIndex();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
if (i >= index) {
i++;
}
}
return (i < count) ? i : -1;
}
/**
* Gets the number of pressed pointers (fingers down).
*/
private static int getPressedPointerCount(MotionEvent event) {
int count = event.getPointerCount();
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
count--;
}
return count;
}
private void updatePointersOnTap(MotionEvent event) {
mPointerCount = 0;
for (int i = 0; i < MAX_POINTERS; i++) {
int index = getPressedPointerIndex(event, i);
if (index == -1) {
mId[i] = MotionEvent.INVALID_POINTER_ID;
} else {
mId[i] = event.getPointerId(index);
mCurrentX[i] = mStartX[i] = event.getX(index);
mCurrentY[i] = mStartY[i] = event.getY(index);
mPointerCount++;
}
}
}
private void updatePointersOnMove(MotionEvent event) {
for (int i = 0; i < MAX_POINTERS; i++) {
int index = event.findPointerIndex(mId[i]);
if (index != -1) {
mCurrentX[i] = event.getX(index);
mCurrentY[i] = event.getY(index);
}
}
}
/**
* Handles the given motion event.
*
* @param event event to handle
* @return whether or not the event was handled
*/
public boolean onTouchEvent(final MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE: {
// update pointers
updatePointersOnMove(event);
// start a new gesture if not already started
if (!mGestureInProgress && mPointerCount > 0 && shouldStartGesture()) {
startGesture();
}
// notify listener
if (mGestureInProgress && mListener != null) {
mListener.onGestureUpdate(this);
}
break;
}
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP: {
// restart gesture whenever the number of pointers changes
mNewPointerCount = getPressedPointerCount(event);
stopGesture();
updatePointersOnTap(event);
if (mPointerCount > 0 && shouldStartGesture()) {
startGesture();
}
break;
}
case MotionEvent.ACTION_CANCEL: {
mNewPointerCount = 0;
stopGesture();
reset();
break;
}
}
return true;
}
/**
* Restarts the current gesture (if any).
*/
public void restartGesture() {
if (!mGestureInProgress) {
return;
}
stopGesture();
for (int i = 0; i < MAX_POINTERS; i++) {
mStartX[i] = mCurrentX[i];
mStartY[i] = mCurrentY[i];
}
startGesture();
}
/**
* Gets whether there is a gesture in progress
*/
public boolean isGestureInProgress() {
return mGestureInProgress;
}
/**
* Gets the number of pointers after the current gesture
*/
public int getNewPointerCount() {
return mNewPointerCount;
}
/**
* Gets the number of pointers in the current gesture
*/
public int getPointerCount() {
return mPointerCount;
}
/**
* Gets the start X coordinates for the all pointers Mutable array is exposed for performance
* reasons and is not to be modified by the callers.
*/
public float[] getStartX() {
return mStartX;
}
/**
* Gets the start Y coordinates for the all pointers Mutable array is exposed for performance
* reasons and is not to be modified by the callers.
*/
public float[] getStartY() {
return mStartY;
}
/**
* Gets the current X coordinates for the all pointers Mutable array is exposed for performance
* reasons and is not to be modified by the callers.
*/
public float[] getCurrentX() {
return mCurrentX;
}
/**
* Gets the current Y coordinates for the all pointers Mutable array is exposed for performance
* reasons and is not to be modified by the callers.
*/
public float[] getCurrentY() {
return mCurrentY;
}
}

51
app/src/main/java/awais/instagrabber/customviews/drawee/MultiZoomableControllerListener.java

@ -0,0 +1,51 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.graphics.Matrix;
import java.util.ArrayList;
import java.util.List;
/**
* An implementation of {@link ZoomableController.Listener} that allows multiple child listeners to
* be added and notified about {@link ZoomableController} events.
*/
public class MultiZoomableControllerListener implements ZoomableController.Listener {
private final List<ZoomableController.Listener> mListeners = new ArrayList<>();
@Override
public synchronized void onTransformBegin(Matrix transform) {
for (ZoomableController.Listener listener : mListeners) {
listener.onTransformBegin(transform);
}
}
@Override
public synchronized void onTransformChanged(Matrix transform) {
for (ZoomableController.Listener listener : mListeners) {
listener.onTransformChanged(transform);
}
}
@Override
public synchronized void onTransformEnd(Matrix transform) {
for (ZoomableController.Listener listener : mListeners) {
listener.onTransformEnd(transform);
}
}
public synchronized void addListener(ZoomableController.Listener listener) {
mListeners.add(listener);
}
public synchronized void removeListener(ZoomableController.Listener listener) {
mListeners.remove(listener);
}
}

203
app/src/main/java/awais/instagrabber/customviews/drawee/TransformGestureDetector.java

@ -0,0 +1,203 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.view.MotionEvent;
/**
* Component that detects translation, scale and rotation based on touch events.
*
* <p>This class notifies its listeners whenever a gesture begins, updates or ends. The instance of
* this detector is passed to the listeners, so it can be queried for pivot, translation, scale or
* rotation.
*/
public class TransformGestureDetector implements MultiPointerGestureDetector.Listener {
/**
* The listener for receiving notifications when gestures occur.
*/
public interface Listener {
/**
* A callback called right before the gesture is about to start.
*/
public void onGestureBegin(TransformGestureDetector detector);
/**
* A callback called each time the gesture gets updated.
*/
public void onGestureUpdate(TransformGestureDetector detector);
/**
* A callback called right after the gesture has finished.
*/
public void onGestureEnd(TransformGestureDetector detector);
}
private final MultiPointerGestureDetector mDetector;
private Listener mListener = null;
public TransformGestureDetector(MultiPointerGestureDetector multiPointerGestureDetector) {
mDetector = multiPointerGestureDetector;
mDetector.setListener(this);
}
/**
* Factory method that creates a new instance of TransformGestureDetector
*/
public static TransformGestureDetector newInstance() {
return new TransformGestureDetector(MultiPointerGestureDetector.newInstance());
}
/**
* Sets the listener.
*
* @param listener listener to set
*/
public void setListener(Listener listener) {
mListener = listener;
}
/**
* Resets the component to the initial state.
*/
public void reset() {
mDetector.reset();
}
/**
* Handles the given motion event.
*
* @param event event to handle
* @return whether or not the event was handled
*/
public boolean onTouchEvent(final MotionEvent event) {
return mDetector.onTouchEvent(event);
}
@Override
public void onGestureBegin(MultiPointerGestureDetector detector) {
if (mListener != null) {
mListener.onGestureBegin(this);
}
}
@Override
public void onGestureUpdate(MultiPointerGestureDetector detector) {
if (mListener != null) {
mListener.onGestureUpdate(this);
}
}
@Override
public void onGestureEnd(MultiPointerGestureDetector detector) {
if (mListener != null) {
mListener.onGestureEnd(this);
}
}
private float calcAverage(float[] arr, int len) {
float sum = 0;
for (int i = 0; i < len; i++) {
sum += arr[i];
}
return (len > 0) ? sum / len : 0;
}
/**
* Restarts the current gesture (if any).
*/
public void restartGesture() {
mDetector.restartGesture();
}
/**
* Gets whether there is a gesture in progress
*/
public boolean isGestureInProgress() {
return mDetector.isGestureInProgress();
}
/**
* Gets the number of pointers after the current gesture
*/
public int getNewPointerCount() {
return mDetector.getNewPointerCount();
}
/**
* Gets the number of pointers in the current gesture
*/
public int getPointerCount() {
return mDetector.getPointerCount();
}
/**
* Gets the X coordinate of the pivot point
*/
public float getPivotX() {
return calcAverage(mDetector.getStartX(), mDetector.getPointerCount());
}
/**
* Gets the Y coordinate of the pivot point
*/
public float getPivotY() {
return calcAverage(mDetector.getStartY(), mDetector.getPointerCount());
}
/**
* Gets the X component of the translation
*/
public float getTranslationX() {
return calcAverage(mDetector.getCurrentX(), mDetector.getPointerCount())
- calcAverage(mDetector.getStartX(), mDetector.getPointerCount());
}
/**
* Gets the Y component of the translation
*/
public float getTranslationY() {
return calcAverage(mDetector.getCurrentY(), mDetector.getPointerCount())
- calcAverage(mDetector.getStartY(), mDetector.getPointerCount());
}
/**
* Gets the scale
*/
public float getScale() {
if (mDetector.getPointerCount() < 2) {
return 1;
} else {
float startDeltaX = mDetector.getStartX()[1] - mDetector.getStartX()[0];
float startDeltaY = mDetector.getStartY()[1] - mDetector.getStartY()[0];
float currentDeltaX = mDetector.getCurrentX()[1] - mDetector.getCurrentX()[0];
float currentDeltaY = mDetector.getCurrentY()[1] - mDetector.getCurrentY()[0];
float startDist = (float) Math.hypot(startDeltaX, startDeltaY);
float currentDist = (float) Math.hypot(currentDeltaX, currentDeltaY);
return currentDist / startDist;
}
}
/**
* Gets the rotation in radians
*/
public float getRotation() {
if (mDetector.getPointerCount() < 2) {
return 0;
} else {
float startDeltaX = mDetector.getStartX()[1] - mDetector.getStartX()[0];
float startDeltaY = mDetector.getStartY()[1] - mDetector.getStartY()[0];
float currentDeltaX = mDetector.getCurrentX()[1] - mDetector.getCurrentX()[0];
float currentDeltaY = mDetector.getCurrentY()[1] - mDetector.getCurrentY()[0];
float startAngle = (float) Math.atan2(startDeltaY, startDeltaX);
float currentAngle = (float) Math.atan2(currentDeltaY, currentDeltaX);
return currentAngle - startAngle;
}
}
}

134
app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableController.java

@ -0,0 +1,134 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.view.MotionEvent;
/**
* Interface for implementing a controller that works with {@link ZoomableDraweeView} to control the
* zoom.
*/
public interface ZoomableController {
/**
* Listener interface.
*/
interface Listener {
/**
* Notifies the view that the transform began.
*
* @param transform the current transform matrix
*/
void onTransformBegin(Matrix transform);
/**
* Notifies the view that the transform changed.
*
* @param transform the new matrix
*/
void onTransformChanged(Matrix transform);
/**
* Notifies the view that the transform ended.
*
* @param transform the current transform matrix
*/
void onTransformEnd(Matrix transform);
}
/**
* Enables the controller. The controller is enabled when the image has been loaded.
*
* @param enabled whether to enable the controller
*/
void setEnabled(boolean enabled);
/**
* Gets whether the controller is enabled. This should return the last value passed to {@link
* #setEnabled}.
*
* @return whether the controller is enabled.
*/
boolean isEnabled();
/**
* Sets the listener for the controller to call back when the matrix changes.
*
* @param listener the listener
*/
void setListener(Listener listener);
/**
* Gets the current scale factor. A convenience method for calculating the scale from the
* transform.
*
* @return the current scale factor
*/
float getScaleFactor();
/**
* Returns true if the zoomable transform is identity matrix, and the controller is idle.
*/
boolean isIdentity();
/**
* Returns true if the transform was corrected during the last update.
*
* <p>This mainly happens when a gesture would cause the image to get out of limits and the
* transform gets corrected in order to prevent that.
*/
boolean wasTransformCorrected();
/**
* See {@link androidx.core.view.ScrollingView}.
*/
int computeHorizontalScrollRange();
int computeHorizontalScrollOffset();
int computeHorizontalScrollExtent();
int computeVerticalScrollRange();
int computeVerticalScrollOffset();
int computeVerticalScrollExtent();
/**
* Gets the current transform.
*
* @return the transform
*/
Matrix getTransform();
/**
* Sets the bounds of the image post transform prior to application of the zoomable
* transformation.
*
* @param imageBounds the bounds of the image
*/
void setImageBounds(RectF imageBounds);
/**
* Sets the bounds of the view.
*
* @param viewBounds the bounds of the view
*/
void setViewBounds(RectF viewBounds);
/**
* Allows the controller to handle a touch event.
*
* @param event the touch event
* @return whether the controller handled the event
*/
boolean onTouchEvent(MotionEvent event);
}

416
app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java

@ -0,0 +1,416 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package awais.instagrabber.customviews.drawee;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewParent;
import androidx.annotation.Nullable;
import androidx.core.view.ScrollingView;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
import com.facebook.drawee.controller.AbstractDraweeController;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeView;
/**
* DraweeView that has zoomable capabilities.
*
* <p>Once the image loads, pinch-to-zoom and translation gestures are enabled.
*/
public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
implements ScrollingView {
private static final Class<?> TAG = ZoomableDraweeView.class;
private static final float HUGE_IMAGE_SCALE_FACTOR_THRESHOLD = 1.1f;
private final RectF mImageBounds = new RectF();
private final RectF mViewBounds = new RectF();
private DraweeController mHugeImageController;
private ZoomableController mZoomableController;
private GestureDetector mTapGestureDetector;
private boolean mAllowTouchInterceptionWhileZoomed = true;
private boolean mIsDialtoneEnabled = false;
private boolean mZoomingEnabled = true;
private final ControllerListener mControllerListener =
new BaseControllerListener<Object>() {
@Override
public void onFinalImageSet(
String id, @Nullable Object imageInfo, @Nullable Animatable animatable) {
ZoomableDraweeView.this.onFinalImageSet();
}
@Override
public void onRelease(String id) {
ZoomableDraweeView.this.onRelease();
}
};
private final ZoomableController.Listener mZoomableListener =
new ZoomableController.Listener() {
@Override
public void onTransformBegin(Matrix transform) {}
@Override
public void onTransformChanged(Matrix transform) {
ZoomableDraweeView.this.onTransformChanged(transform);
}
@Override
public void onTransformEnd(Matrix transform) {}
};
private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper();
public ZoomableDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
init();
}
public ZoomableDraweeView(Context context) {
super(context);
inflateHierarchy(context, null);
init();
}
public ZoomableDraweeView(Context context, AttributeSet attrs) {
super(context, attrs);
inflateHierarchy(context, attrs);
init();
}
public ZoomableDraweeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflateHierarchy(context, attrs);
init();
}
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
Resources resources = context.getResources();
GenericDraweeHierarchyBuilder builder =
new GenericDraweeHierarchyBuilder(resources)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER);
GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
}
private void init() {
mZoomableController = createZoomableController();
mZoomableController.setListener(mZoomableListener);
mTapGestureDetector = new GestureDetector(getContext(), mTapListenerWrapper);
}
public void setIsDialtoneEnabled(boolean isDialtoneEnabled) {
mIsDialtoneEnabled = isDialtoneEnabled;
}
/**
* Gets the original image bounds, in view-absolute coordinates.
*
* <p>The original image bounds are those reported by the hierarchy. The hierarchy itself may
* apply scaling on its own (e.g. due to scale type) so the reported bounds are not necessarily
* the same as the actual bitmap dimensions. In other words, the original image bounds correspond
* to the image bounds within this view when no zoomable transformation is applied, but including
* the potential scaling of the hierarchy. Having the actual bitmap dimensions abstracted away
* from this view greatly simplifies implementation because the actual bitmap may change (e.g.
* when a high-res image arrives and replaces the previously set low-res image). With proper
* hierarchy scaling (e.g. FIT_CENTER), this underlying change will not affect this view nor the
* zoomable transformation in any way.
*/
protected void getImageBounds(RectF outBounds) {
getHierarchy().getActualImageBounds(outBounds);
}
/**
* Gets the bounds used to limit the translation, in view-absolute coordinates.
*
* <p>These bounds are passed to the zoomable controller in order to limit the translation. The
* image is attempted to be centered within the limit bounds if the transformed image is smaller.
* There will be no empty spaces within the limit bounds if the transformed image is bigger. This
* applies to each dimension (horizontal and vertical) independently.
*
* <p>Unless overridden by a subclass, these bounds are same as the view bounds.
*/
protected void getLimitBounds(RectF outBounds) {
outBounds.set(0, 0, getWidth(), getHeight());
}
/**
* Sets a custom zoomable controller, instead of using the default one.
*/
public void setZoomableController(ZoomableController zoomableController) {
Preconditions.checkNotNull(zoomableController);
mZoomableController.setListener(null);
mZoomableController = zoomableController;
mZoomableController.setListener(mZoomableListener);
}
/**
* Gets the zoomable controller.
*
* <p>Zoomable controller can be used to zoom to point, or to map point from view to image
* coordinates for instance.
*/
public ZoomableController getZoomableController() {
return mZoomableController;
}
/**
* Check whether the parent view can intercept touch events while zoomed. This can be used, for
* example, to swipe between images in a view pager while zoomed.
*
* @return true if touch events can be intercepted
*/
public boolean allowsTouchInterceptionWhileZoomed() {
return mAllowTouchInterceptionWhileZoomed;
}
/**
* If this is set to true, parent views can intercept touch events while the view is zoomed. For
* example, this can be used to swipe between images in a view pager while zoomed.
*
* @param allowTouchInterceptionWhileZoomed true if the parent needs to intercept touches
*/
public void setAllowTouchInterceptionWhileZoomed(boolean allowTouchInterceptionWhileZoomed) {
mAllowTouchInterceptionWhileZoomed = allowTouchInterceptionWhileZoomed;
}
/**
* Sets the tap listener.
*/
public void setTapListener(GestureDetector.SimpleOnGestureListener tapListener) {
mTapListenerWrapper.setListener(tapListener);
}
/**
* Sets whether long-press tap detection is enabled. Unfortunately, long-press conflicts with
* onDoubleTapEvent.
*/
public void setIsLongpressEnabled(boolean enabled) {
mTapGestureDetector.setIsLongpressEnabled(enabled);
}
public void setZoomingEnabled(boolean zoomingEnabled) {
mZoomingEnabled = zoomingEnabled;
mZoomableController.setEnabled(false);
}
/**
* Sets the image controller.
*/
@Override
public void setController(@Nullable DraweeController controller) {
setControllers(controller, null);
}
/**
* Sets the controllers for the normal and huge image.
*
* <p>The huge image controller is used after the image gets scaled above a certain threshold.
*
* <p>IMPORTANT: in order to avoid a flicker when switching to the huge image, the huge image
* controller should have the normal-image-uri set as its low-res-uri.
*
* @param controller controller to be initially used
* @param hugeImageController controller to be used after the client starts zooming-in
*/
public void setControllers(
@Nullable DraweeController controller, @Nullable DraweeController hugeImageController) {
setControllersInternal(null, null);
mZoomableController.setEnabled(false);
setControllersInternal(controller, hugeImageController);
}
private void setControllersInternal(
@Nullable DraweeController controller, @Nullable DraweeController hugeImageController) {
removeControllerListener(getController());
addControllerListener(controller);
mHugeImageController = hugeImageController;
super.setController(controller);
}
private void maybeSetHugeImageController() {
if (mHugeImageController != null
&& mZoomableController.getScaleFactor() > HUGE_IMAGE_SCALE_FACTOR_THRESHOLD) {
setControllersInternal(mHugeImageController, null);
}
}
private void removeControllerListener(DraweeController controller) {
if (controller instanceof AbstractDraweeController) {
((AbstractDraweeController) controller).removeControllerListener(mControllerListener);
}
}
private void addControllerListener(DraweeController controller) {
if (controller instanceof AbstractDraweeController) {
((AbstractDraweeController) controller).addControllerListener(mControllerListener);
}
}
@Override
protected void onDraw(Canvas canvas) {
int saveCount = canvas.save();
canvas.concat(mZoomableController.getTransform());
try {
super.onDraw(canvas);
} catch (Exception e) {
DraweeController controller = getController();
if (controller != null && controller instanceof AbstractDraweeController) {
Object callerContext = ((AbstractDraweeController) controller).getCallerContext();
if (callerContext != null) {
throw new RuntimeException(
String.format("Exception in onDraw, callerContext=%s", callerContext.toString()), e);
}
}
throw e;
}
canvas.restoreToCount(saveCount);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int a = event.getActionMasked();
FLog.v(getLogTag(), "onTouchEvent: %d, view %x, received", a, this.hashCode());
if (!mIsDialtoneEnabled && mTapGestureDetector.onTouchEvent(event)) {
FLog.v(
getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
return true;
}
if (!mIsDialtoneEnabled && mZoomableController.onTouchEvent(event)) {
FLog.v(
getLogTag(),
"onTouchEvent: %d, view %x, handled by zoomable controller",
a,
this.hashCode());
if (!mAllowTouchInterceptionWhileZoomed && !mZoomableController.isIdentity()) {
final ViewParent parent = getParent();
parent.requestDisallowInterceptTouchEvent(true);
}
return true;
}
if (super.onTouchEvent(event)) {
FLog.v(getLogTag(), "onTouchEvent: %d, view %x, handled by the super", a, this.hashCode());
return true;
}
// None of our components reported that they handled the touch event. Upon returning false
// from this method, our parent won't send us any more events for this gesture. Unfortunately,
// some components may have started a delayed action, such as a long-press timer, and since we
// won't receive an ACTION_UP that would cancel that timer, a false event may be triggered.
// To prevent that we explicitly send one last cancel event when returning false.
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
mTapGestureDetector.onTouchEvent(cancelEvent);
mZoomableController.onTouchEvent(cancelEvent);
cancelEvent.recycle();
return false;
}
@Override
public int computeHorizontalScrollRange() {
return mZoomableController.computeHorizontalScrollRange();
}
@Override
public int computeHorizontalScrollOffset() {
return mZoomableController.computeHorizontalScrollOffset();
}
@Override
public int computeHorizontalScrollExtent() {
return mZoomableController.computeHorizontalScrollExtent();
}
@Override
public int computeVerticalScrollRange() {
return mZoomableController.computeVerticalScrollRange();
}
@Override
public int computeVerticalScrollOffset() {
return mZoomableController.computeVerticalScrollOffset();
}
@Override
public int computeVerticalScrollExtent() {
return mZoomableController.computeVerticalScrollExtent();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
FLog.v(getLogTag(), "onLayout: view %x", this.hashCode());
super.onLayout(changed, left, top, right, bottom);
updateZoomableControllerBounds();
}
private void onFinalImageSet() {
FLog.v(getLogTag(), "onFinalImageSet: view %x", this.hashCode());
if (!mZoomableController.isEnabled() && mZoomingEnabled) {
mZoomableController.setEnabled(true);
updateZoomableControllerBounds();
}
}
private void onRelease() {
FLog.v(getLogTag(), "onRelease: view %x", this.hashCode());
mZoomableController.setEnabled(false);
}
protected void onTransformChanged(Matrix transform) {
FLog.v(getLogTag(), "onTransformChanged: view %x, transform: %s", this.hashCode(), transform);
maybeSetHugeImageController();
invalidate();
}
protected void updateZoomableControllerBounds() {
getImageBounds(mImageBounds);
getLimitBounds(mViewBounds);
mZoomableController.setImageBounds(mImageBounds);
mZoomableController.setViewBounds(mViewBounds);
FLog.v(
getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,
mImageBounds);
}
protected Class<?> getLogTag() {
return TAG;
}
protected ZoomableController createZoomableController() {
return AnimatedZoomableController.newInstance();
}
}

43
app/src/main/java/awais/instagrabber/customviews/helpers/ImageResizingControllerListener.java

@ -0,0 +1,43 @@
package awais.instagrabber.customviews.helpers;
import android.graphics.drawable.Animatable;
import android.view.ViewGroup;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.view.DraweeView;
import com.facebook.imagepipeline.image.ImageInfo;
import awais.instagrabber.utils.Utils;
public class ImageResizingControllerListener<T extends DraweeView<GenericDraweeHierarchy>> extends BaseControllerListener<ImageInfo> {
private static final String TAG = "ImageResizingController";
private T imageView;
private final int requiredWidth;
public ImageResizingControllerListener(final T imageView, final int requiredWidth) {
this.imageView = imageView;
this.requiredWidth = requiredWidth;
}
@Override
public void onIntermediateImageSet(final String id, final ImageInfo imageInfo) {
super.onIntermediateImageSet(id, imageInfo);
}
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
if (imageInfo != null) {
// updateViewSize(imageInfo);
final int height = imageInfo.getHeight();
final int width = imageInfo.getWidth();
// final float aspectRatio = ((float) width) / height;
final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = Utils.getResultingHeight(requiredWidth, height, width);
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
imageView.requestLayout();
}
}
}

45
app/src/main/java/awais/instagrabber/customviews/helpers/PauseGlideOnFlingScrollListener.java

@ -0,0 +1,45 @@
package awais.instagrabber.customviews.helpers;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestManager;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
public class PauseGlideOnFlingScrollListener extends RecyclerView.OnScrollListener {
private static final int FLING_JUMP_LOW_THRESHOLD = 80;
private static final int FLING_JUMP_HIGH_THRESHOLD = 120;
private final RequestManager glide;
private boolean dragging = false;
public PauseGlideOnFlingScrollListener(final RequestManager glide) {
this.glide = glide;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
dragging = newState == SCROLL_STATE_DRAGGING;
if (glide.isPaused()) {
if (newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_IDLE) {
// user is touchy or the scroll finished, show images
glide.resumeRequests();
} // settling means the user let the screen go, but it can still be flinging
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!dragging) {
// TODO can be made better by a rolling average of last N calls to smooth out patterns like a,b,a
int currentSpeed = Math.abs(dy);
boolean paused = glide.isPaused();
if (paused && currentSpeed < FLING_JUMP_LOW_THRESHOLD) {
glide.resumeRequests();
} else if (!paused && FLING_JUMP_HIGH_THRESHOLD < currentSpeed) {
glide.pauseRequests();
}
}
}
}

497
app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java

@ -1,297 +1,318 @@
package awais.instagrabber.customviews.helpers;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
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.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.CommentsViewer;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_DRAGGING;
import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
private static final String TAG = "VideoAwareRecScroll";
private static final int FLING_JUMP_LOW_THRESHOLD = 80;
private static final int FLING_JUMP_HIGH_THRESHOLD = 120;
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 SimpleExoPlayer player;
private ImageView btnMute;
private CacheDataSourceFactory cacheDataSourceFactory;
private final List<FeedModel> feedModels;
private final Context context;
private final VideoChangeCallback videoChangeCallback;
private final DefaultDataSourceFactory dataSourceFactory;
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())
.putExtra(Constants.EXTRAS_POST, ((FeedModel) tag).getPostId())
.putExtra(Constants.EXTRAS_POST, ((FeedModel) tag).getProfileModel().getId()), 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.mute : R.drawable.vol);
Utils.sessionVolumeFull = intVol == 1f;
};
private boolean dragging;
private boolean isLoadingPaused = false;
private FeedVideoViewHolder currentlyPlayingViewHolder;
public VideoAwareRecyclerScroller(final Context context, final List<FeedModel> feedModels,
final VideoChangeCallback videoChangeCallback) {
this.context = context;
this.feedModels = feedModels;
this.videoChangeCallback = videoChangeCallback;
dataSourceFactory = new DefaultDataSourceFactory(context, "instagram");
final SimpleCache simpleCache = Utils.getSimpleCacheInstance(context);
if (simpleCache != null) {
cacheDataSourceFactory = new CacheDataSourceFactory(simpleCache, dataSourceFactory);
@Override
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
dragging = newState == SCROLL_STATE_DRAGGING;
if (isLoadingPaused) {
if (newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_IDLE) {
// user is touchy or the scroll finished, show videos
isLoadingPaused = false;
} // settling means the user let the screen go, but it can still be flinging
}
}
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
if (!dragging) {
// TODO can be made better by a rolling average of last N calls to smooth out patterns like a,b,a
int currentSpeed = Math.abs(dy);
if (isLoadingPaused && currentSpeed < FLING_JUMP_LOW_THRESHOLD) {
isLoadingPaused = false;
} else if (!isLoadingPaused && FLING_JUMP_HIGH_THRESHOLD < currentSpeed) {
isLoadingPaused = true;
// stop playing video
}
}
if (isLoadingPaused) return;
if (layoutManager == null) {
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager)
this.layoutManager = (LinearLayoutManager) layoutManager;
}
if (feedModels.size() == 0 || layoutManager == null) {
if (layoutManager == null) {
return;
}
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);
synchronized (LOCK) {
final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos);
if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) {
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
currentlyPlayingViewHolder = null;
}
return;
}
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 (currentlyPlayingViewHolder != null && currentlyPlayingViewHolder.getCurrentFeedModel().getPostId()
.equals(videoHolder.getCurrentFeedModel().getPostId())) {
return;
}
}
if (videoPosShown != -1) {
lastVideoPos = videoPosShown;
if (!videoAttached) {
if ((currView = layoutManager.findViewByPosition(videoPosShown)) != null && currView.getId() == R.id.videoHolder)
attachVideo(videoPosShown, recyclerView, currView);
videoAttached = true;
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
}
videoHolder.startPlaying();
currentlyPlayingViewHolder = videoHolder;
}
// 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;
// }
// }
// if (firstItemView == null && lastItemView == null) {
// return;
// }
// if (firstItemView != null) {
//
// Log.d(TAG, "view" + viewHolder);
// }
// if (lastItemView != null) {
// final FeedVideoViewHolder viewHolder = (FeedVideoViewHolder) recyclerView.getChildViewHolder(lastItemView);
// Log.d(TAG, "view" + viewHolder);
// }
// Log.d(TAG, firstItemView + " " + lastItemView);
// 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;
}
final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
player = new SimpleExoPlayer.Builder(context)
.setUseLazyPreparation(!shouldAutoplay)
.build();
player.setPlayWhenReady(shouldAutoplay);
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);
}
private FeedVideoViewHolder getFirstVideoHolder(final RecyclerView recyclerView, final int firstVisibleItemPos, final int lastVisibleItemPos) {
final Rect visibleItemRect = new Rect();
final Point offset = new Point();
for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) {
final View view = layoutManager.findViewByPosition(pos);
if (view != null && view.getId() == R.id.videoHolder) {
final View viewSwitcher = view.findViewById(R.id.view_switcher);
if (viewSwitcher == null) {
continue;
}
final PlayerView playerView = itemView.findViewById(R.id.playerView);
if (playerView == null) return;
playerView.setPlayer(player);
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);
final boolean result = viewSwitcher.getGlobalVisibleRect(visibleItemRect, offset);
if (!result) continue;
final FeedVideoViewHolder viewHolder = (FeedVideoViewHolder) recyclerView.getChildViewHolder(view);
final FeedModel currentFeedModel = viewHolder.getCurrentFeedModel();
visibleItemRect.offset(-offset.x, -offset.y);
final int visibleHeight = visibleItemRect.height();
if (visibleHeight < currentFeedModel.getImageHeight()) {
continue;
}
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModels.get(itemPos).getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setVolume(vol);
playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
// Log.d(TAG, "post:" + currentFeedModel.getPostId() + ", visibleHeight: " + visibleHeight + ", post height: " + currentFeedModel.getImageHeight());
return viewHolder;
}
if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
}
return null;
}
private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// Log.d("AWAISKING_APP", "release: " + itemPos);
// 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 (itemView == null) {
// return;
// }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// }
// }
//
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) {
// return;
// }
// if (player != null) {
// player.stop(true);
// player.release();
// player = null;
// }
// 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();
}
}
//
// player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay)
// .build();
// player.setPlayWhenReady(shouldAutoplay);
//
// final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else {
// btnComments.setTag(feedModel);
// btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener);
// }
// }
// playerView.setPlayer(player);
// 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);
// }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
//
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource);
// player.setVolume(vol);
//
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
//
// 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);

22
app/src/main/java/awais/instagrabber/models/FeedModel.java

@ -1,15 +1,17 @@
package awais.instagrabber.models;
import awais.instagrabber.models.enums.MediaItemType;
import org.json.JSONObject;
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 final JSONObject location;
private ViewerPostModel[] sliderItems;
private int imageWidth;
private int imageHeight;
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,
@ -61,4 +63,20 @@ public final class FeedModel extends PostModel {
public void toggleCaption() {
captionExpanded = !captionExpanded;
}
public int getImageWidth() {
return imageWidth;
}
public void setImageWidth(final int imageWidth) {
this.imageWidth = imageWidth;
}
public void setImageHeight(final int imageHeight) {
this.imageHeight = imageHeight;
}
public int getImageHeight() {
return imageHeight;
}
}

31
app/src/main/java/awais/instagrabber/models/enums/MediaItemType.java

@ -1,10 +1,33 @@
package awais.instagrabber.models.enums;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
public enum MediaItemType implements Serializable {
MEDIA_TYPE_IMAGE,
MEDIA_TYPE_VIDEO,
MEDIA_TYPE_SLIDER,
MEDIA_TYPE_VOICE,
MEDIA_TYPE_IMAGE(1),
MEDIA_TYPE_VIDEO(2),
MEDIA_TYPE_SLIDER(3),
MEDIA_TYPE_VOICE(4);
private final int id;
private static Map<Integer, MediaItemType> map = new HashMap<>();
static {
for (MediaItemType type : MediaItemType.values()) {
map.put(type.id, type);
}
}
MediaItemType(final int id) {
this.id = id;
}
public int getId() {
return id;
}
public static MediaItemType valueOf(final int id) {
return map.get(id);
}
}

92
app/src/main/java/awais/instagrabber/utils/Utils.java

@ -245,8 +245,7 @@ public final class Utils {
|| nextChar == '\r' || nextChar == '\n') {
break;
}
}
else if (currChar == '.')
} else if (currChar == '.')
break;
// for merged hashtags
@ -293,8 +292,7 @@ public final class Utils {
if (currRes > lastResMain && !low) {
lastResMain = currRes;
lastIndexMain = i;
}
else if (currRes < lastResMain && low) {
} else if (currRes < lastResMain && low) {
lastResMain = currRes;
lastIndexMain = i;
}
@ -302,8 +300,7 @@ public final class Utils {
if (currRes > lastResBase && !low) {
lastResBase = currRes;
lastIndexBase = i;
}
else if (currRes < lastResBase && low) {
} else if (currRes < lastResBase && low) {
lastResBase = currRes;
lastIndexBase = i;
}
@ -326,7 +323,8 @@ public final class Utils {
public static String getHighQualityImage(final JSONObject resources) {
String src = null;
try {
if (resources.has("display_resources")) src = getHighQualityPost(resources.getJSONArray("display_resources"), false, false, false);
if (resources.has("display_resources"))
src = getHighQualityPost(resources.getJSONArray("display_resources"), false, false, false);
else if (resources.has("image_versions2"))
src = getHighQualityPost(resources.getJSONObject("image_versions2").getJSONArray("candidates"), false, true, false);
if (src == null) return resources.getString("display_url");
@ -390,8 +388,9 @@ public final class Utils {
} else if (mediaType == MediaItemType.MEDIA_TYPE_SLIDER) {
final JSONArray carouselMedia = mediaObj.optJSONArray("carousel_media");
if (carouselMedia != null) thumbnail = Utils.getItemThumbnail(carouselMedia.getJSONObject(0)
.getJSONObject("image_versions2").getJSONArray("candidates"));
if (carouselMedia != null)
thumbnail = Utils.getItemThumbnail(carouselMedia.getJSONObject(0)
.getJSONObject("image_versions2").getJSONArray("candidates"));
}
return thumbnail;
@ -624,13 +623,15 @@ public final class Utils {
final JSONArray seenUserIdsArray = visualMedia.getJSONArray("seen_user_ids");
final int seenUsersLen = seenUserIdsArray.length();
final String[] seenUserIds = new String[seenUsersLen];
for (int j = 0; j < seenUsersLen; j++) seenUserIds[j] = seenUserIdsArray.getString(j);
for (int j = 0; j < seenUsersLen; j++)
seenUserIds[j] = seenUserIdsArray.getString(j);
RavenExpiringMediaActionSummaryModel expiringSummaryModel = null;
final JSONObject actionSummary = visualMedia.optJSONObject("expiring_media_action_summary");
if (actionSummary != null) expiringSummaryModel = new RavenExpiringMediaActionSummaryModel(
actionSummary.getLong("timestamp"), actionSummary.getInt("count"),
getExpiringMediaType(actionSummary.getString("type")));
if (actionSummary != null)
expiringSummaryModel = new RavenExpiringMediaActionSummaryModel(
actionSummary.getLong("timestamp"), actionSummary.getInt("count"),
getExpiringMediaType(actionSummary.getString("type")));
final RavenMediaViewType viewType;
final String viewMode = visualMedia.getString("view_mode");
@ -692,11 +693,11 @@ public final class Utils {
final JSONObject actionLog = itemObject.getJSONObject("action_log");
String desc = actionLog.getString("description");
JSONArray bold = actionLog.getJSONArray("bold");
for (int q=0; q < bold.length(); ++q) {
for (int q = 0; q < bold.length(); ++q) {
JSONObject boldItem = bold.getJSONObject(q);
desc = desc.substring(0, boldItem.getInt("start") + q*7) + "<b>"
+ desc.substring(boldItem.getInt("start") + q*7, boldItem.getInt("end") + q*7)
+ "</b>" + desc.substring(boldItem.getInt("end") + q*7);
desc = desc.substring(0, boldItem.getInt("start") + q * 7) + "<b>"
+ desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7)
+ "</b>" + desc.substring(boldItem.getInt("end") + q * 7);
}
actionLogModel = new DirectItemActionLogModel(desc);
break;
@ -972,14 +973,17 @@ public final class Utils {
saveFile,
file -> {
model.setDownloaded(true);
if (saved != null) saved.deselectSelection(selectedItem);
else if (mainActivity != null) mainActivity.mainHelper.deselectSelection(selectedItem);
if (saved != null)
saved.deselectSelection(selectedItem);
else if (mainActivity != null)
mainActivity.mainHelper.deselectSelection(selectedItem);
else if (pv != null) pv.deselectSelection(selectedItem);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
if (saved != null) saved.deselectSelection(selectedItem);
else if (mainActivity != null) mainActivity.mainHelper.deselectSelection(selectedItem);
else if (mainActivity != null)
mainActivity.mainHelper.deselectSelection(selectedItem);
else if (pv != null) pv.deselectSelection(selectedItem);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -990,7 +994,7 @@ public final class Utils {
}
public static void dmDownload(@NonNull final Context context, @Nullable final String username, final DownloadMethod method,
final List<? extends DirectItemMediaModel> itemsToDownload) {
final List<? extends DirectItemMediaModel> itemsToDownload) {
if (settingsHelper == null) settingsHelper = new SettingsHelper(context);
if (itemsToDownload == null || itemsToDownload.size() < 1) return;
@ -1002,7 +1006,7 @@ public final class Utils {
}
private static void dmDownloadImpl(@NonNull final Context context, @Nullable final String username,
final DownloadMethod method, final List<? extends DirectItemMediaModel> itemsToDownload) {
final DownloadMethod method, final List<? extends DirectItemMediaModel> itemsToDownload) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
@ -1129,10 +1133,12 @@ public final class Utils {
if (v == passwordParent) importExportBinding.cbPassword.performClick();
else if (v == exportLoginsParent) importExportBinding.cbExportLogins.performClick();
else if (v == exportFavoritesParent) importExportBinding.cbExportFavorites.performClick();
else if (v == exportFavoritesParent)
importExportBinding.cbExportFavorites.performClick();
else if (v == importLoginsParent) importExportBinding.cbImportLogins.performClick();
else if (v == importFavoritesParent) importExportBinding.cbImportFavorites.performClick();
else if (v == importFavoritesParent)
importExportBinding.cbImportFavorites.performClick();
else if (v == exportSettingsParent) importExportBinding.cbExportSettings.performClick();
else if (v == importSettingsParent) importExportBinding.cbImportSettings.performClick();
@ -1151,9 +1157,12 @@ public final class Utils {
final File file = new File(path, "InstaGrabber_Settings_" + System.currentTimeMillis() + ".zaai");
final String password = passwordChecked ? text.toString() : null;
int flags = 0;
if (importExportBinding.cbExportFavorites.isChecked()) flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbExportSettings.isChecked()) flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbExportLogins.isChecked()) flags |= ExportImportUtils.FLAG_COOKIES;
if (importExportBinding.cbExportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbExportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbExportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Export(password, flags, file, result -> {
Toast.makeText(context, result ? R.string.dialog_export_success : R.string.dialog_export_failed, Toast.LENGTH_SHORT).show();
@ -1166,9 +1175,12 @@ public final class Utils {
} else if (v == importExportBinding.btnImport) {
new DirectoryChooser().setInitialDirectory(folderPath).setShowZaAiConfigFiles(true).setInteractionListener(path -> {
int flags = 0;
if (importExportBinding.cbImportFavorites.isChecked()) flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbImportSettings.isChecked()) flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbImportLogins.isChecked()) flags |= ExportImportUtils.FLAG_COOKIES;
if (importExportBinding.cbImportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbImportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbImportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Import(context, flags, new File(path), result -> {
((AppCompatActivity) context).recreate();
@ -1205,9 +1217,8 @@ public final class Utils {
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return "ig_sig_key_version="+Constants.SIGNATURE_VERSION+"&signed_body=" + hexString.toString() + "." + message;
}
catch (Exception e) {
return "ig_sig_key_version=" + Constants.SIGNATURE_VERSION + "&signed_body=" + hexString.toString() + "." + message;
} catch (Exception e) {
Log.e(TAG, "Error signing", e);
return null;
}
@ -1251,9 +1262,10 @@ public final class Utils {
final String scheme = itemUri.getScheme();
if (isEmpty(scheme))
mimeType = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(itemUri.toString()).toLowerCase());
else mimeType = scheme.equals(ContentResolver.SCHEME_CONTENT) ? contentResolver.getType(itemUri)
: mimeTypeMap.getMimeTypeFromExtension
(MimeTypeMap.getFileExtensionFromUrl(itemUri.toString()).toLowerCase());
else
mimeType = scheme.equals(ContentResolver.SCHEME_CONTENT) ? contentResolver.getType(itemUri)
: mimeTypeMap.getMimeTypeFromExtension
(MimeTypeMap.getFileExtensionFromUrl(itemUri.toString()).toLowerCase());
if (isEmpty(mimeType)) return true;
mimeType = mimeType.toLowerCase();
@ -1418,4 +1430,12 @@ public final class Utils {
}
return simpleCache;
}
public static int getResultingHeight(final int requiredWidth, final int height, final int width) {
return requiredWidth * height / width;
}
public static int getResultingWidth(final int requiredHeight, final int height, final int width) {
return requiredHeight * width / height;
}
}

11
app/src/main/res/drawable/rounder_corner_semi_black_bg.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/semi_transparent_black" />
<padding
android:left="2dp"
android:right="2dp"
android:bottom="2dp"
android:top="2dp" />
<corners android:radius="5dp" />
</shape>

2
app/src/main/res/layout/activity_main.xml

@ -71,7 +71,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed" />
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</awais.instagrabber.customviews.RemixDrawerLayout>

17
app/src/main/res/layout/item_feed.xml

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:viewBindingIgnore="true">
<include layout="@layout/item_feed_top" />
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/item_feed_bottom" />
</LinearLayout>

11
app/src/main/res/layout/item_feed_bottom.xml

@ -16,8 +16,9 @@
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="32dp"
android:layout_marginLeft="4dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
app:srcCompat="@drawable/comments" />
<androidx.appcompat.widget.AppCompatTextView
@ -32,7 +33,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/llVideoViews"
android:id="@+id/videoViewsContainer"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_toEndOf="@id/btnComments"
@ -62,8 +63,8 @@
android:layout_alignRight="@id/btnMute"
android:layout_marginEnd="48dp"
android:layout_marginRight="48dp"
android:layout_toEndOf="@id/llVideoViews"
android:layout_toRightOf="@id/llVideoViews">
android:layout_toEndOf="@id/videoViewsContainer"
android:layout_toRightOf="@id/videoViewsContainer">
<View
android:layout_width="1dip"
@ -131,7 +132,7 @@
android:padding="12dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="BOTTOM TEXT HAHA FUNNY MEME" />
tools:text="BOTTOM TEXT" />
</FrameLayout>
</ScrollView>
</RelativeLayout>

25
app/src/main/res/layout/item_feed_photo.xml

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/item_feed_top"
layout="@layout/item_feed_top" />
<!--<com.github.chrisbanes.photoview.PhotoView-->
<!-- android:id="@+id/imageViewer"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content" />-->
<awais.instagrabber.customviews.drawee.ZoomableDraweeView
android:id="@+id/imageViewer"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@android:color/transparent" />
<include
android:id="@+id/item_feed_bottom"
layout="@layout/item_feed_bottom" />
</LinearLayout>

19
app/src/main/res/layout/item_feed_slider.xml

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:viewBindingIgnore="true">
android:orientation="vertical">
<include layout="@layout/item_feed_top" />
<include
android:id="@+id/item_feed_top"
layout="@layout/item_feed_top" />
<FrameLayout
android:layout_width="match_parent"
@ -16,7 +16,7 @@
<androidx.viewpager.widget.ViewPager
android:id="@+id/media_list"
android:layout_width="match_parent"
android:layout_height="200dp" />
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="24dp"
@ -31,8 +31,13 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:gravity="center"
android:textSize="20sp" />
android:padding="5dp"
android:textColor="@android:color/white"
android:background="@drawable/rounder_corner_semi_black_bg"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"/>
</FrameLayout>
<include layout="@layout/item_feed_bottom" />
<include
android:id="@+id/item_feed_bottom"
layout="@layout/item_feed_bottom" />
</LinearLayout>

15
app/src/main/res/layout/item_feed_top.xml

@ -1,48 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<awais.instagrabber.customviews.CircularImageView
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/ivProfilePic"
android:layout_width="@dimen/feed_profile_size"
android:layout_height="@dimen/feed_profile_size"
android:adjustViewBounds="true"
app:roundAsCircle="true"
android:background="?selectableItemBackgroundBorderless" />
<RelativeLayout
android:id="@+id/infoContainer"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:animateLayoutChanges="true"
android:background="@null"
android:orientation="vertical"
android:weightSum="2"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp">
android:paddingRight="4dp"
android:weightSum="2">
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:textColor="@color/feed_text_primary_color"
android:textSize="18sp"/>
android:textSize="18sp" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/location"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:layout_weight="1"
android:gravity="center_vertical"
android:textColor="@color/feed_text_primary_color"
android:textSize="18sp" />

53
app/src/main/res/layout/item_feed_video.xml

@ -1,23 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/videoHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:viewBindingIgnore="true">
android:orientation="vertical">
<include layout="@layout/item_feed_top" />
<include
android:id="@+id/item_feed_top"
layout="@layout/item_feed_top" />
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
<ViewSwitcher
android:id="@+id/view_switcher"
android:layout_width="match_parent"
android:layout_height="240dp"
app:auto_show="true"
app:controller_layout_id="@layout/layout_controls"
app:repeat_toggle_modes="all"
app:use_controller="true" />
android:layout_height="wrap_content">
<include layout="@layout/item_feed_bottom" />
<!--<androidx.appcompat.widget.AppCompatImageView-->
<!-- android:id="@+id/thumbnail"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:adjustViewBounds="true"-->
<!-- android:scaleType="fitCenter"/>-->
<FrameLayout
android:id="@+id/thumbnail_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:actualImageScaleType="fitCenter" />
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
app:srcCompat="@drawable/exo_controls_play" />
</FrameLayout>
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_timeout="1000" />
</ViewSwitcher>
<include
android:id="@+id/item_feed_bottom"
layout="@layout/item_feed_bottom" />
</LinearLayout>

2
app/src/main/res/layout/layout_feed_view.xml

@ -44,6 +44,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed" />
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

59
app/src/main/res/layout/layout_profile_view.xml

@ -1,16 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:background="@null"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:background="@null">
<!-- for users -->
<com.google.android.material.appbar.CollapsingToolbarLayout
@ -86,8 +85,8 @@
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="https://austinhuang.me/"
android:visibility="gone" />
android:visibility="gone"
tools:text="https://austinhuang.me/" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/profileActions"
@ -99,47 +98,47 @@
android:id="@+id/btnFollow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_pink_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRestrict"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/restrict"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_orange_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<!-- only for invisible private accounts -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnBlock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/block"
android:textColor="@color/btn_red_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_red_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_red_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
@ -152,47 +151,47 @@
android:id="@+id/btnTagged"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/tagged"
android:textColor="@color/btn_blue_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_blue_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" />
<!-- also used as block -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/saved"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_orange_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLiked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginStart="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color"
android:textSize="16sp"
app:backgroundTint="@color/btn_lightpink_background"
android:visibility="gone" />
android:visibility="gone"
app:backgroundTint="@color/btn_lightpink_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
@ -219,9 +218,7 @@
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainProfileImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless" />
android:layout_height="@dimen/profile_picture_size" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainPostCount"
@ -372,15 +369,15 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/locationFullName"
android:layout_below="@id/locInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/locInfo"
android:layout_weight="1"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
@ -413,8 +410,8 @@
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="https://austinhuang.me/"
android:visibility="gone" />
android:visibility="gone"
tools:text="https://austinhuang.me/" />
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>

1
app/src/main/res/values/color.xml

@ -31,6 +31,7 @@
<color name="feed_text_primary_color">@color/text_color_light</color>
<color name="dm_profile_button_color">#efefef</color>
<color name="semi_transparent_black">#80000000</color>
<!-- Amoled Colors -->
<color name="background_amoled">#000000</color>
Loading…
Cancel
Save