diff --git a/app/build.gradle b/app/build.gradle index e5b0c4fa..c05bbec6 100755 --- a/app/build.gradle +++ b/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' } diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index 1f2b41b4..38c62547 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/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); } diff --git a/app/src/main/java/awais/instagrabber/MainHelper.java b/app/src/main/java/awais/instagrabber/MainHelper.java index cf213043..c5f6bef5 100755 --- a/app/src/main/java/awais/instagrabber/MainHelper.java +++ b/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 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 postsFetchListener = new FetchListener() { @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); - - 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); - } + if (result == null) { + return; } + final int oldSize = mainActivity.feedItems.size(); + final HashMap thumbToFeedMap = new HashMap<>(); + for (final FeedModel feedModel : result) { + thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel); + } + final BaseDataSubscriber subscriber = new BaseDataSubscriber() { + int success = 0; + int failed = 0; - mainActivity.mainBinding.feedView.feedSwipeRefreshLayout.setRefreshing(false); + @Override + protected void onNewResultImpl(@NonNull final DataSource dataSource) { + // dataSource + final Map 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(); + } + + @Override + protected void onFailureImpl(@NonNull final DataSource dataSource) { + failed++; + updateAdapter(); + } + + 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 ds = Fresco.getImagePipeline().prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null); + ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance()); + } } }; private final FetchListener discoverFetchListener = new FetchListener() { @@ -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 spinnerArrayAdapter = new ArrayAdapter( - 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 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 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 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 { - 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 { + // 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; } diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 9fe739d2..40f0996e 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/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 scanHack; public static ItemGetter itemGetter; - // -------- items -------- + public final ArrayList allItems = new ArrayList<>(); public final ArrayList feedItems = new ArrayList<>(); public final ArrayList discoverItems = new ArrayList<>(); - // -------- items -------- public final ArrayList selectedItems = new ArrayList<>(); public final ArrayList 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 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) diff --git a/app/src/main/java/awais/instagrabber/activities/PostViewer.java b/app/src/main/java/awais/instagrabber/activities/PostViewer.java index 61270011..1ef75aa9 100755 --- a/app/src/main/java/awais/instagrabber/activities/PostViewer.java +++ b/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() { - private boolean loaded = true; - - @Override - public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target 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 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() { + // private boolean loaded = true; + // + // @Override + // public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target 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 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(); } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java b/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java index 587f6427..2705746f 100755 --- a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java +++ b/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 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 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(); } } diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java index c9dd3810..c3209451 100755 --- a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java +++ b/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 { - private final static String ellipsize = "… more"; - private final Activity activity; - private final LayoutInflater layoutInflater; - private final ArrayList feedModels; +public final class FeedAdapter extends ListAdapter { + private static final String TAG = "FeedAdapter"; + // private final static String ellipsize = "… more"; + private final RequestManager glide; + private final View.OnClickListener clickListener; private final MentionClickListener mentionClickListener; - 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 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 diffCallback = new DiffUtil.ItemCallback() { + @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 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 - )); + case MEDIA_TYPE_SLIDER: { + final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false); + return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener); } - 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); - } - } - - 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; + @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 interface PlayerChangeListener { - void playerChanged(final int childPos, final SimpleExoPlayer player); - } - - 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(); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java index 05769834..b9d5c33f 100755 --- a/app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java +++ b/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 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); +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java new file mode 100644 index 00000000..324676e9 --- /dev/null +++ b/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 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 thumbnailRequestBuilder = glide + // .asBitmap() + // .load(thumbnailUrl) + // .diskCacheStrategy(DiskCacheStrategy.ALL); + // glide.asBitmap() + // .load(url) + // .thumbnail(thumbnailRequestBuilder) + // .diskCacheStrategy(DiskCacheStrategy.ALL) + // .into(customTarget); + + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java new file mode 100644 index 00000000..572286f2 --- /dev/null +++ b/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]; + } + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java new file mode 100644 index 00000000..2df6c499 --- /dev/null +++ b/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); + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/CircularImageView.java b/app/src/main/java/awais/instagrabber/customviews/CircularImageView.java index 4ebffb6b..441c21e3 100755 --- a/app/src/main/java/awais/instagrabber/customviews/CircularImageView.java +++ b/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(); } + 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()); + } + 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); - } - }); - } + // 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 - 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(); - } + // @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); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/customviews/RamboTextView.java b/app/src/main/java/awais/instagrabber/customviews/RamboTextView.java index 93ea7cf0..ac8b2c9b 100755 --- a/app/src/main/java/awais/instagrabber/customviews/RamboTextView.java +++ b/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; + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/AbstractAnimatedZoomableController.java b/app/src/main/java/awais/instagrabber/customviews/drawee/AbstractAnimatedZoomableController.java new file mode 100644 index 00000000..b473ef35 --- /dev/null +++ b/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. + * + *

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. + * + *

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. + * + *

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(); +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/AnimatedZoomableController.java b/app/src/main/java/awais/instagrabber/customviews/drawee/AnimatedZoomableController.java new file mode 100644 index 00000000..963ac632 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/DefaultZoomableController.java b/app/src/main/java/awais/instagrabber/customviews/drawee/DefaultZoomableController.java new file mode 100644 index 00000000..0ee5d0cd --- /dev/null +++ b/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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

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; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/DoubleTapGestureListener.java b/app/src/main/java/awais/instagrabber/customviews/drawee/DoubleTapGestureListener.java new file mode 100644 index 00000000..59b9c7f0 --- /dev/null +++ b/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; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/GestureListenerWrapper.java b/app/src/main/java/awais/instagrabber/customviews/drawee/GestureListenerWrapper.java new file mode 100644 index 00000000..933cf69d --- /dev/null +++ b/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); + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/MultiGestureListener.java b/app/src/main/java/awais/instagrabber/customviews/drawee/MultiGestureListener.java new file mode 100644 index 00000000..e19d0f35 --- /dev/null +++ b/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. + * + *

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 mListeners = new ArrayList<>(); + + /** + * Adds a listener to the multi gesture listener. + * + *

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. + * + *

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; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/MultiPointerGestureDetector.java b/app/src/main/java/awais/instagrabber/customviews/drawee/MultiPointerGestureDetector.java new file mode 100644 index 00000000..8c453dd7 --- /dev/null +++ b/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. + * + *

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; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/MultiZoomableControllerListener.java b/app/src/main/java/awais/instagrabber/customviews/drawee/MultiZoomableControllerListener.java new file mode 100644 index 00000000..b459ece6 --- /dev/null +++ b/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 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/TransformGestureDetector.java b/app/src/main/java/awais/instagrabber/customviews/drawee/TransformGestureDetector.java new file mode 100644 index 00000000..b8a9518c --- /dev/null +++ b/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. + * + *

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; + } + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableController.java b/app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableController.java new file mode 100644 index 00000000..93d83586 --- /dev/null +++ b/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. + * + *

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); +} diff --git a/app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java b/app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java new file mode 100644 index 00000000..6a91b2aa --- /dev/null +++ b/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. + * + *

Once the image loads, pinch-to-zoom and translation gestures are enabled. + */ +public class ZoomableDraweeView extends DraweeView + 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() { + @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. + * + *

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. + * + *

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. + * + *

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. + * + *

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. + * + *

The huge image controller is used after the image gets scaled above a certain threshold. + * + *

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(); + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/ImageResizingControllerListener.java b/app/src/main/java/awais/instagrabber/customviews/helpers/ImageResizingControllerListener.java new file mode 100644 index 00000000..2e9a0516 --- /dev/null +++ b/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> extends BaseControllerListener { + 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(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/PauseGlideOnFlingScrollListener.java b/app/src/main/java/awais/instagrabber/customviews/helpers/PauseGlideOnFlingScrollListener.java new file mode 100644 index 00000000..0249c1fa --- /dev/null +++ b/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(); + } + } + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java b/app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java index e1086d54..ae237d60 100755 --- a/app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java +++ b/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 boolean dragging; + private boolean isLoadingPaused = false; + private FeedVideoViewHolder currentlyPlayingViewHolder; - private final List 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; - }; - - public VideoAwareRecyclerScroller(final Context context, final List 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); - } - } - - 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); + final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos); + if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) { + if (currentlyPlayingViewHolder != null) { + currentlyPlayingViewHolder.stopPlaying(); + currentlyPlayingViewHolder = null; } + return; } - - if (player != null) { - player.stop(true); - player.release(); - player = null; + if (currentlyPlayingViewHolder != null && currentlyPlayingViewHolder.getCurrentFeedModel().getPostId() + .equals(videoHolder.getCurrentFeedModel().getPostId())) { + return; } - - 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); - } - } - - 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 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())); + if (currentlyPlayingViewHolder != null) { + currentlyPlayingViewHolder.stopPlaying(); } - - if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player); + 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 void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { -// Log.d("AWAISKING_APP", "release: " + itemPos); + 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 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; + } + // Log.d(TAG, "post:" + currentFeedModel.getPostId() + ", visibleHeight: " + visibleHeight + ", post height: " + currentFeedModel.getImageHeight()); + return viewHolder; + } + } + return null; + } + +// 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); diff --git a/app/src/main/java/awais/instagrabber/models/FeedModel.java b/app/src/main/java/awais/instagrabber/models/FeedModel.java index 9eeeda1a..1a63233f 100755 --- a/app/src/main/java/awais/instagrabber/models/FeedModel.java +++ b/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; + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/enums/MediaItemType.java b/app/src/main/java/awais/instagrabber/models/enums/MediaItemType.java index 97433550..727b0a1b 100755 --- a/app/src/main/java/awais/instagrabber/models/enums/MediaItemType.java +++ b/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 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); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 22af0b1b..f96920e3 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/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) + "" - + desc.substring(boldItem.getInt("start") + q*7, boldItem.getInt("end") + q*7) - + "" + desc.substring(boldItem.getInt("end") + q*7); + desc = desc.substring(0, boldItem.getInt("start") + q * 7) + "" + + desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7) + + "" + 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 itemsToDownload) { + final List 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 itemsToDownload) { + final DownloadMethod method, final List 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; + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml new file mode 100644 index 00000000..a0706c60 --- /dev/null +++ b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 74b7e606..cbefb539 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/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" /> diff --git a/app/src/main/res/layout/item_feed.xml b/app/src/main/res/layout/item_feed.xml deleted file mode 100755 index e48d0cbb..00000000 --- a/app/src/main/res/layout/item_feed.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_bottom.xml b/app/src/main/res/layout/item_feed_bottom.xml index 2e5e104b..5f37390d 100755 --- a/app/src/main/res/layout/item_feed_bottom.xml +++ b/app/src/main/res/layout/item_feed_bottom.xml @@ -16,8 +16,9 @@ + android:layout_toEndOf="@id/videoViewsContainer" + android:layout_toRightOf="@id/videoViewsContainer"> + tools:text="BOTTOM TEXT" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_photo.xml b/app/src/main/res/layout/item_feed_photo.xml new file mode 100644 index 00000000..f52b2483 --- /dev/null +++ b/app/src/main/res/layout/item_feed_photo.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_slider.xml b/app/src/main/res/layout/item_feed_slider.xml index 9da36039..782c8bfa 100755 --- a/app/src/main/res/layout/item_feed_slider.xml +++ b/app/src/main/res/layout/item_feed_slider.xml @@ -1,13 +1,13 @@ + android:orientation="vertical"> - + + android:layout_height="wrap_content" /> + android:padding="5dp" + android:textColor="@android:color/white" + android:background="@drawable/rounder_corner_semi_black_bg" + android:textAppearance="@style/TextAppearance.AppCompat.Caption"/> - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_top.xml b/app/src/main/res/layout/item_feed_top.xml index 0e0991d1..84211c59 100755 --- a/app/src/main/res/layout/item_feed_top.xml +++ b/app/src/main/res/layout/item_feed_top.xml @@ -1,48 +1,45 @@ - + android:paddingRight="4dp" + android:weightSum="2"> + android:textSize="18sp" /> diff --git a/app/src/main/res/layout/item_feed_video.xml b/app/src/main/res/layout/item_feed_video.xml index dd11ce5e..fa25c3a5 100755 --- a/app/src/main/res/layout/item_feed_video.xml +++ b/app/src/main/res/layout/item_feed_video.xml @@ -1,23 +1,52 @@ + android:orientation="vertical"> - + - + android:layout_height="wrap_content"> - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_feed_view.xml b/app/src/main/res/layout/layout_feed_view.xml index d77333e0..adcf4f50 100644 --- a/app/src/main/res/layout/layout_feed_view.xml +++ b/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" /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_profile_view.xml b/app/src/main/res/layout/layout_profile_view.xml index 15554aef..1aefe2b5 100644 --- a/app/src/main/res/layout/layout_profile_view.xml +++ b/app/src/main/res/layout/layout_profile_view.xml @@ -1,16 +1,15 @@ - + android:layout_height="wrap_content" + android:background="@null"> + android:visibility="gone" + tools:text="https://austinhuang.me/" /> + android:visibility="gone" + app:backgroundTint="@color/btn_pink_background" /> + android:visibility="gone" + app:backgroundTint="@color/btn_orange_background" /> + android:visibility="gone" + app:backgroundTint="@color/btn_red_background" /> + android:visibility="gone" + app:backgroundTint="@color/btn_blue_background" /> + android:visibility="gone" + app:backgroundTint="@color/btn_orange_background" /> + android:visibility="gone" + app:backgroundTint="@color/btn_lightpink_background" /> @@ -219,9 +218,7 @@ + android:layout_height="@dimen/profile_picture_size" /> + android:visibility="gone" + tools:text="https://austinhuang.me/" /> diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml index 113cdba4..28705129 100755 --- a/app/src/main/res/values/color.xml +++ b/app/src/main/res/values/color.xml @@ -31,6 +31,7 @@ @color/text_color_light #efefef + #80000000 #000000