Browse Source

Update Discover tab

renovate/org.robolectric-robolectric-4.x
Ammar Githam 4 years ago
parent
commit
efd9a9c29d
  1. 5
      app/build.gradle
  2. 21
      app/src/main/java/awais/instagrabber/activities/MainActivity.java
  3. 53
      app/src/main/java/awais/instagrabber/adapters/DiscoverTopicsAdapter.java
  4. 99
      app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
  5. 56
      app/src/main/java/awais/instagrabber/asyncs/DiscoverPostFetchService.java
  6. 8
      app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
  7. 8
      app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java
  8. 3
      app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
  9. 18
      app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java
  10. 2
      app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java
  11. 68
      app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
  12. 4
      app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
  13. 363
      app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
  14. 354
      app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java
  15. 16
      app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
  16. 57
      app/src/main/java/awais/instagrabber/models/TopicCluster.java
  17. 12
      app/src/main/java/awais/instagrabber/repositories/DiscoverRepository.java
  18. 1
      app/src/main/java/awais/instagrabber/utils/Constants.java
  19. 24
      app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
  20. 4
      app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
  21. 19
      app/src/main/java/awais/instagrabber/viewmodels/TopicClusterViewModel.java
  22. 501
      app/src/main/java/awais/instagrabber/webservices/DiscoverService.java
  23. 44
      app/src/main/res/layout/fragment_discover.xml
  24. 14
      app/src/main/res/layout/fragment_profile.xml
  25. 58
      app/src/main/res/layout/fragment_topic_posts.xml
  26. 45
      app/src/main/res/layout/item_discover_topic.xml
  27. 9
      app/src/main/res/menu/topic_posts_menu.xml
  28. 76
      app/src/main/res/navigation/discover_nav_graph.xml

5
app/build.gradle

@ -49,7 +49,7 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
def appcompat_version = "1.2.0"
def nav_version = "2.3.0"
def nav_version = '2.3.1'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'com.google.android.exoplayer:exoplayer:2.12.0'
@ -61,9 +61,10 @@ dependencies {
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.2"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.work:work-runtime:2.4.0"
implementation 'androidx.palette:palette:1.0.0'
implementation 'com.google.guava:guava:27.0.1-android'

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

@ -421,7 +421,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.main_nav_host,
getIntent(),
firstFragmentGraphIndex);
navControllerLiveData.observe(this, this::setupNavigation);
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
currentNavControllerLiveData = navControllerLiveData;
}
@ -446,8 +446,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return mainNavList;
}
private void setupNavigation(final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController);
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
if (navController == null) {
return;
}
NavigationUI.setupWithNavController(toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
// below is a hack to check if we are at the end of the current stack, to setup the search view
binding.appBarLayout.setExpanded(true, true);
@ -640,4 +643,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
public int getNavHostContainerId() {
return binding.mainNavHost.getId();
}
public void setToolbar(final Toolbar toolbar) {
binding.appBarLayout.setVisibility(View.GONE);
setSupportActionBar(toolbar);
setupNavigation(toolbar, currentNavControllerLiveData.getValue());
}
public void resetToolbar() {
binding.appBarLayout.setVisibility(View.VISIBLE);
setSupportActionBar(binding.toolbar);
setupNavigation(binding.toolbar, currentNavControllerLiveData.getValue());
}
}

53
app/src/main/java/awais/instagrabber/adapters/DiscoverTopicsAdapter.java

@ -0,0 +1,53 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
import awais.instagrabber.models.TopicCluster;
public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicClusterViewHolder> {
private static final DiffUtil.ItemCallback<TopicCluster> DIFF_CALLBACK = new DiffUtil.ItemCallback<TopicCluster>() {
@Override
public boolean areItemsTheSame(@NonNull final TopicCluster oldItem, @NonNull final TopicCluster newItem) {
return oldItem.getId().equals(newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final TopicCluster oldItem, @NonNull final TopicCluster newItem) {
return oldItem.getCoverMedia().getDisplayUrl().equals(newItem.getCoverMedia().getDisplayUrl())
&& oldItem.getTitle().equals(newItem.getTitle());
}
};
private final OnTopicClickListener onTopicClickListener;
public DiscoverTopicsAdapter(final OnTopicClickListener onTopicClickListener) {
super(DIFF_CALLBACK);
this.onTopicClickListener = onTopicClickListener;
}
@NonNull
@Override
public TopicClusterViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemDiscoverTopicBinding binding = ItemDiscoverTopicBinding.inflate(layoutInflater, parent, false);
return new TopicClusterViewHolder(binding, onTopicClickListener);
}
@Override
public void onBindViewHolder(@NonNull final TopicClusterViewHolder holder, final int position) {
final TopicCluster topicCluster = getItem(position);
holder.bind(topicCluster);
}
public interface OnTopicClickListener {
void onTopicClick(TopicCluster topicCluster, View root, View cover, View title, int titleColor, int backgroundColor);
}
}

99
app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java

@ -0,0 +1,99 @@
package awais.instagrabber.adapters.viewholder;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.palette.graphics.Palette;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.util.concurrent.atomic.AtomicInteger;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DiscoverTopicsAdapter;
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
import awais.instagrabber.models.TopicCluster;
public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
private final ItemDiscoverTopicBinding binding;
private final DiscoverTopicsAdapter.OnTopicClickListener onTopicClickListener;
public TopicClusterViewHolder(@NonNull final ItemDiscoverTopicBinding binding,
final DiscoverTopicsAdapter.OnTopicClickListener onTopicClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onTopicClickListener = onTopicClickListener;
}
public void bind(final TopicCluster topicCluster) {
if (topicCluster == null) {
return;
}
final AtomicInteger titleColor = new AtomicInteger(-1);
final AtomicInteger backgroundColor = new AtomicInteger(-1);
if (onTopicClickListener != null) {
itemView.setOnClickListener(v -> onTopicClickListener.onTopicClick(
topicCluster,
binding.getRoot(),
binding.cover,
binding.title,
titleColor.get(),
backgroundColor.get()
));
}
// binding.title.setTransitionName("title-" + topicCluster.getId());
binding.cover.setTransitionName("cover-" + topicCluster.getId());
final ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(topicCluster.getCoverMedia().getDisplayUrl()))
.build();
final ImagePipeline imagePipeline = Fresco.getImagePipeline();
final DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline
.fetchDecodedImage(imageRequest, CallerThreadExecutor.getInstance());
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (dataSource.isFinished()) {
dataSource.close();
}
if (bitmap != null) {
Palette.from(bitmap).generate(p -> {
final Palette.Swatch swatch = p.getDominantSwatch();
final Resources resources = itemView.getResources();
int titleTextColor = resources.getColor(R.color.white);
if (swatch != null) {
backgroundColor.set(swatch.getRgb());
GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor.get()});
titleTextColor = swatch.getTitleTextColor();
binding.background.setBackground(gd);
}
titleColor.set(titleTextColor);
binding.title.setTextColor(titleTextColor);
});
}
}
@Override
public void onFailureImpl(@NonNull DataSource dataSource) {
dataSource.close();
}
}, CallerThreadExecutor.getInstance());
binding.cover.setImageRequest(imageRequest);
binding.title.setText(topicCluster.getTitle());
}
}

56
app/src/main/java/awais/instagrabber/asyncs/DiscoverPostFetchService.java

@ -0,0 +1,56 @@
package awais.instagrabber.asyncs;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.webservices.ServiceCallback;
public class DiscoverPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "DiscoverPostFetchService";
private final DiscoverService discoverService;
private final DiscoverService.TopicalExploreRequest topicalExploreRequest;
private boolean moreAvailable = false;
public DiscoverPostFetchService(final DiscoverService.TopicalExploreRequest topicalExploreRequest) {
this.topicalExploreRequest = topicalExploreRequest;
discoverService = DiscoverService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
discoverService.topicalExplore(topicalExploreRequest, new ServiceCallback<DiscoverService.TopicalExploreResponse>() {
@Override
public void onSuccess(final DiscoverService.TopicalExploreResponse result) {
if (result == null) {
onFailure(new RuntimeException("result is null"));
return;
}
moreAvailable = result.isMoreAvailable();
topicalExploreRequest.setMaxId(result.getNextMaxId());
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public void reset() {
topicalExploreRequest.setMaxId(-1);
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

8
app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java

@ -20,8 +20,8 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService {
}
@Override
public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, cursor, new ServiceCallback<PostsFetchResponse>() {
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
@ -43,8 +43,8 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService {
}
@Override
public String getNextCursor() {
return nextCursor;
public void reset() {
nextCursor = null;
}
@Override

8
app/src/main/java/awais/instagrabber/asyncs/ProfilePostFetchService.java

@ -23,8 +23,8 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
}
@Override
public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, cursor, new ServiceCallback<PostsFetchResponse>() {
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
@ -46,8 +46,8 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
}
@Override
public String getNextCursor() {
return nextCursor;
public void reset() {
nextCursor = null;
}
@Override

3
app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java

@ -167,7 +167,7 @@ public class PostsRecyclerView extends RecyclerView {
setNestedScrollingEnabled(true);
lazyLoader = new RecyclerLazyLoaderAtBottom(layoutManager, (page) -> {
if (postFetcher.hasMore()) {
postFetcher.fetchNextPage();
postFetcher.fetch();
dispatchFetchStatus();
}
});
@ -204,6 +204,7 @@ public class PostsRecyclerView extends RecyclerView {
public void refresh() {
lazyLoader.resetState();
postFetcher.reset();
postFetcher.fetch();
dispatchFetchStatus();
}

18
app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java

@ -17,21 +17,17 @@ public class PostFetcher {
}
public void fetch() {
fetch(null);
}
public void fetchNextPage() {
fetch(postFetchService.getNextCursor());
}
public void fetch(final String cursor) {
fetching = true;
postFetchService.fetch(cursor, result -> {
postFetchService.fetch(result -> {
fetching = false;
fetchListener.onResult(result);
});
}
public void reset() {
postFetchService.reset();
}
public boolean isFetching() {
return fetching;
}
@ -41,9 +37,9 @@ public class PostFetcher {
}
public interface PostFetchService {
void fetch(String cursor, FetchListener<List<FeedModel>> fetchListener);
void fetch(FetchListener<List<FeedModel>> fetchListener);
String getNextCursor();
void reset();
boolean hasNextPage();
}

2
app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java

@ -30,7 +30,7 @@ public final class RecyclerLazyLoaderAtBottom extends RecyclerView.OnScrollListe
if (!recyclerView.canScrollVertically(RecyclerView.SCROLL_AXIS_HORIZONTAL) && newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!loading && lazyLoadListener != null) {
loading = true;
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 1000);
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage), 500);
}
}
}

68
app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java

@ -4,7 +4,6 @@ import android.content.Context;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableStringBuilder;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
@ -209,7 +208,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
hashtag = fragmentArgs.getHashtag();
setTitle();
// setTitle();
setupPosts();
fetchHashtagModel();
}
@ -284,6 +283,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
setTitle();
fetchPosts();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -324,9 +324,35 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.btnFollowTag.setOnClickListener(v -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
if (csrfToken != null) {
binding.btnFollowTag.setClickable(false);
if (!hashtagModel.getFollowing()) {
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(root,
message != null ? message
: getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
return;
}
tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
@ -349,31 +375,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
.show();
}
});
return;
}
tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
return;
}
onRefresh();
}
@Override
public void onFailure(@NonNull final Throwable t) {
binding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(root,
message != null ? message
: getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
}
});
});
} else {
binding.btnFollowTag.setVisibility(View.GONE);
@ -425,7 +427,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
NavHostFragment.findNavController(this).navigate(action);
return;
}
});
}
@ -445,9 +446,10 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void setTitle() {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler();
handler.postDelayed(() -> actionBar.setTitle(hashtag), 200);
// Log.d(TAG, "setting title: " + hashtag);
actionBar.setTitle(hashtag);
// final Handler handler = new Handler();
// handler.postDelayed(() -> , 1000);
}
}

4
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java

@ -920,7 +920,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
binding.playerControls.getRoot().setVisibility(View.GONE);
binding.sliderParent.setVisibility(View.VISIBLE);
binding.mediaCounter.setVisibility(View.VISIBLE);
if (sharedMainPostElement != null) {
if (!wasPaused && sharedMainPostElement != null) {
addSharedElement(sharedMainPostElement, binding.sliderParent);
}
sliderItemsAdapter = new SliderItemsAdapter(onVerticalDragListener, binding.playerControls, true, new SliderCallbackAdapter() {
@ -1014,7 +1014,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
binding.sliderParent.setVisibility(View.GONE);
binding.mediaCounter.setVisibility(View.GONE);
// binding.playerControls.getRoot().setVisibility(View.VISIBLE);
if (sharedMainPostElement != null) {
if (!wasPaused && sharedMainPostElement != null) {
final GenericDraweeHierarchy hierarchy = binding.videoPost.thumbnail.getHierarchy();
hierarchy.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP);
addSharedElement(sharedMainPostElement, binding.videoPost.thumbnailParent);

363
app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java

@ -0,0 +1,363 @@
package awais.instagrabber.fragments;
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.PermissionChecker;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.transition.ChangeBounds;
import androidx.transition.TransitionInflater;
import androidx.transition.TransitionSet;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.DiscoverPostFetchService;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.databinding.FragmentTopicPostsBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.DiscoverFragmentDirections;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.TopicCluster;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DiscoverService;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private MainActivity fragmentActivity;
private FragmentTopicPostsBinding binding;
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true;
private TopicCluster topicCluster;
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = DiscoverFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
showDownloadDialog(feedModel);
return;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = DiscoverFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = DiscoverFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
final TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition(new ChangeBounds())
.addTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move))
.setDuration(200);
setSharedElementEnterTransition(transitionSet);
postponeEnterTransition();
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentTopicPostsBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.topic_posts_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRefresh() {
binding.posts.refresh();
}
@Override
public void onDestroy() {
super.onDestroy();
resetToolbar();
}
@Override
public void onDestroyView() {
super.onDestroyView();
resetToolbar();
}
private void resetToolbar() {
fragmentActivity.resetToolbar();
}
private void init() {
if (getArguments() == null) return;
final TopicPostsFragmentArgs fragmentArgs = TopicPostsFragmentArgs.fromBundle(getArguments());
topicCluster = fragmentArgs.getTopicCluster();
setupToolbar(fragmentArgs.getTitleColor(), fragmentArgs.getBackgroundColor());
setupPosts();
}
private void setupToolbar(final int titleColor, final int backgroundColor) {
if (topicCluster == null) {
return;
}
binding.cover.setTransitionName("cover-" + topicCluster.getId());
fragmentActivity.setToolbar(binding.toolbar);
binding.collapsingToolbarLayout.setTitle(topicCluster.getTitle());
final int collapsedTitleTextColor = ColorUtils.setAlphaComponent(titleColor, 0xFF);
final int expandedTitleTextColor = ColorUtils.setAlphaComponent(titleColor, 0x99);
binding.collapsingToolbarLayout.setExpandedTitleColor(expandedTitleTextColor);
binding.collapsingToolbarLayout.setCollapsedTitleTextColor(collapsedTitleTextColor);
binding.collapsingToolbarLayout.setContentScrimColor(backgroundColor);
final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
final Drawable overflowIcon = binding.toolbar.getOverflowIcon();
if (navigationIcon != null && overflowIcon != null) {
final Drawable navDrawable = navigationIcon.mutate();
final Drawable overflowDrawable = overflowIcon.mutate();
navDrawable.setAlpha(0xFF);
overflowDrawable.setAlpha(0xFF);
final ArgbEvaluator argbEvaluator = new ArgbEvaluator();
binding.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
final int totalScrollRange = appBarLayout.getTotalScrollRange();
final float current = totalScrollRange + verticalOffset;
final float fraction = current / totalScrollRange;
final int tempColor = (int) argbEvaluator.evaluate(fraction, collapsedTitleTextColor, expandedTitleTextColor);
navDrawable.setColorFilter(tempColor, PorterDuff.Mode.SRC_ATOP);
overflowDrawable.setColorFilter(tempColor, PorterDuff.Mode.SRC_ATOP);
});
}
final GradientDrawable gd = new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.TRANSPARENT, backgroundColor});
binding.background.setBackground(gd);
setupCover();
}
private void setupCover() {
final String coverUrl = topicCluster.getCoverMedia().getDisplayUrl();
final DraweeController controller = Fresco
.newDraweeControllerBuilder()
.setOldController(binding.cover.getController())
.setUri(coverUrl)
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
super.onFailure(id, throwable);
startPostponedEnterTransition();
}
@Override
public void onFinalImageSet(final String id,
@Nullable final ImageInfo imageInfo,
@Nullable final Animatable animatable) {
startPostponedEnterTransition();
}
})
.build();
binding.cover.setController(controller);
}
private void setupPosts() {
final DiscoverService.TopicalExploreRequest topicalExploreRequest = new DiscoverService.TopicalExploreRequest();
topicalExploreRequest.setClusterId(topicCluster.getId());
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new DiscoverPostFetchService(topicalExploreRequest))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_TOPIC_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
}
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching());
}
private void showDownloadDialog(final FeedModel feedModel) {
final Context context = getContext();
if (context == null) return;
DownloadUtils.download(context, feedModel);
// switch (feedModel.getItemType()) {
// case MEDIA_TYPE_IMAGE:
// case MEDIA_TYPE_VIDEO:
// break;
// case MEDIA_TYPE_SLIDER:
// break;
// }
// final List<ViewerPostModel> postModelsToDownload = new ArrayList<>();
// // if (!session) {
// final DialogInterface.OnClickListener clickListener = (dialog, which) -> {
// if (which == DialogInterface.BUTTON_NEGATIVE) {
// postModelsToDownload.addAll(postModels);
// } else if (which == DialogInterface.BUTTON_POSITIVE) {
// postModelsToDownload.add(postModels.get(childPosition));
// } else {
// session = true;
// postModelsToDownload.add(postModels.get(childPosition));
// }
// if (postModelsToDownload.size() > 0) {
// DownloadUtils.batchDownload(context,
// username,
// DownloadMethod.DOWNLOAD_POST_VIEWER,
// postModelsToDownload);
// }
// };
// new AlertDialog.Builder(context)
// .setTitle(R.string.post_viewer_download_dialog_title)
// .setMessage(R.string.post_viewer_download_message)
// .setNeutralButton(R.string.post_viewer_download_session, clickListener)
// .setPositiveButton(R.string.post_viewer_download_current, clickListener)
// .setNegativeButton(R.string.post_viewer_download_album, clickListener).show();
// } else {
// DownloadUtils.batchDownload(context,
// username,
// DownloadMethod.DOWNLOAD_POST_VIEWER,
// Collections.singletonList(postModels.get(childPosition)));
}
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_TOPIC_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

354
app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java

@ -1,126 +1,84 @@
package awais.instagrabber.fragments.main;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.FragmentNavigator;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DiscoverAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.i.iTopicFetcher;
import awais.instagrabber.adapters.DiscoverTopicsAdapter;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDiscoverBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.DiscoverTopicModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.DiscoverItemViewModel;
import awais.instagrabber.viewmodels.TopicClusterViewModel;
import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.webservices.ServiceCallback;
public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DiscoverFragment";
private MainActivity fragmentActivity;
private CoordinatorLayout root;
private FragmentDiscoverBinding binding;
private DiscoverAdapter discoverAdapter;
private RecyclerLazyLoader lazyLoader;
private boolean discoverHasMore = false;
private String[] topicIds;
private String rankToken;
private String currentTopic;
private String discoverEndMaxId;
private ActionMode actionMode;
private DiscoverItemViewModel discoverItemViewModel;
private TopicClusterViewModel topicClusterViewModel;
private boolean shouldRefresh = true;
private boolean isPullToRefresh;
private final FetchListener<DiscoverTopicModel> topicFetchListener = new FetchListener<DiscoverTopicModel>() {
@Override
public void doBefore() {}
@Override
public void onResult(final DiscoverTopicModel result) {
if (result != null) {
topicIds = result.getIds();
rankToken = result.getToken();
final Context context = getContext();
if (context == null) return;
final ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>(
context,
android.R.layout.simple_spinner_dropdown_item,
result.getNames()
);
binding.discoverType.setAdapter(spinnerArrayAdapter);
}
}
};
private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() {
@Override
public void doBefore() {}
private DiscoverService discoverService;
@Override
public void onResult(final DiscoverItemModel[] result) {
if (result == null || result.length <= 0) {
binding.discoverSwipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, R.string.discover_empty, Toast.LENGTH_SHORT).show();
return;
}
List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
final List<DiscoverItemModel> resultList = Arrays.asList(result);
current = current == null ? new ArrayList<>() : new ArrayList<>(current); // copy to modifiable list
if (isPullToRefresh) {
current = resultList;
isPullToRefresh = false;
} else {
current.addAll(resultList);
}
discoverItemViewModel.getList().postValue(current);
binding.discoverSwipeRefreshLayout.setRefreshing(false);
final DiscoverItemModel discoverItemModel = result[result.length - 1];
if (discoverItemModel != null) {
discoverEndMaxId = discoverItemModel.getNextMaxId();
discoverHasMore = discoverItemModel.hasMore();
discoverItemModel.setMore(false, null);
}
}
};
// private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() {
// @Override
// public void doBefore() {}
//
// @Override
// public void onResult(final DiscoverItemModel[] result) {
// if (result == null || result.length <= 0) {
// binding.swipeRefreshLayout.setRefreshing(false);
// final Context context = getContext();
// if (context == null) return;
// Toast.makeText(context, R.string.discover_empty, Toast.LENGTH_SHORT).show();
// return;
// }
// List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
// final List<DiscoverItemModel> resultList = Arrays.asList(result);
// current = current == null ? new ArrayList<>() : new ArrayList<>(current); // copy to modifiable list
// if (isPullToRefresh) {
// current = resultList;
// isPullToRefresh = false;
// } else {
// current.addAll(resultList);
// }
// discoverItemViewModel.getList().postValue(current);
// binding.swipeRefreshLayout.setRefreshing(false);
// final DiscoverItemModel discoverItemModel = result[result.length - 1];
// if (discoverItemModel != null) {
// discoverEndMaxId = discoverItemModel.getNextMaxId();
// discoverHasMore = discoverItemModel.hasMore();
// discoverItemModel.setMore(false, null);
// }
// }
// };
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (discoverAdapter == null) return;
discoverAdapter.clearSelection();
// if (discoverAdapter == null) return;
// discoverAdapter.clearSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -134,14 +92,14 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (discoverAdapter == null) return false;
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
null,
DownloadMethod.DOWNLOAD_DISCOVER,
discoverAdapter.getSelectedModels());
checkAndResetAction();
// if (discoverAdapter == null) return false;
// final Context context = getContext();
// if (context == null) return false;
// DownloadUtils.batchDownload(context,
// null,
// DownloadMethod.DOWNLOAD_DISCOVER,
// discoverAdapter.getSelectedModels());
// checkAndResetAction();
return true;
}
return false;
@ -152,6 +110,7 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
discoverService = DiscoverService.getInstance();
}
@Override
@ -170,95 +129,158 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.discoverSwipeRefreshLayout.setOnRefreshListener(this);
setupExplore();
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
private void init() {
// setExitSharedElementCallback(new SharedElementCallback() {
// @Override
// public void onSharedElementsArrived(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final OnSharedElementsReadyListener listener) {
// super.onSharedElementsArrived(sharedElementNames, sharedElements, listener);
// Log.d(TAG, "onSharedElementsArrived: sharedElementNames: " + sharedElementNames);
// }
//
// @Override
// public void onSharedElementEnd(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final List<View> sharedElementSnapshots) {
// super.onSharedElementEnd(sharedElementNames, sharedElements, sharedElementSnapshots);
// Log.d(TAG, "onSharedElementEnd: sharedElementNames: " + sharedElementNames);
// }
//
// @Override
// public void onSharedElementStart(final List<String> sharedElementNames,
// final List<View> sharedElements,
// final List<View> sharedElementSnapshots) {
// super.onSharedElementStart(sharedElementNames, sharedElements, sharedElementSnapshots);
// Log.d(TAG, "onSharedElementStart: sharedElementNames: " + sharedElementNames);
// }
// });
setupTopics();
fetchTopics();
}
@Override
public void onRefresh() {
isPullToRefresh = true;
discoverEndMaxId = null;
lazyLoader.resetState();
fetchPosts();
fetchTopics();
}
public void setupTopics() {
topicClusterViewModel = new ViewModelProvider(fragmentActivity).get(TopicClusterViewModel.class);
binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2)));
final DiscoverTopicsAdapter adapter = new DiscoverTopicsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> {
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder()
.addSharedElement(cover, "cover-" + topicCluster.getId());
// .addSharedElement(title, "title-" + topicCluster.getId());
final DiscoverFragmentDirections.ActionDiscoverFragmentToTopicPostsFragment action = DiscoverFragmentDirections
.actionDiscoverFragmentToTopicPostsFragment(topicCluster, titleColor, backgroundColor);
NavHostFragment.findNavController(this).navigate(action, builder.build());
});
binding.topicsRecyclerView.setAdapter(adapter);
topicClusterViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
}
private void setupExplore() {
discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
final Context context = getContext();
if (context == null) return;
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
binding.discoverPosts.setLayoutManager(layoutManager);
binding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
binding.discoverType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
// final Context context = getContext();
// if (context == null) return;
// final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
// binding.postsRecyclerView.setLayoutManager(layoutManager);
// binding.postsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
// binding.discoverType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// @Override
// public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
// if (topicIds == null || topicIds.length <= 0) return;
// currentTopic = topicIds[pos];
// onRefresh();
// }
//
// @Override
// public void onNothingSelected(AdapterView<?> parent) {}
// });
// discoverAdapter = new DiscoverAdapter((model, position) -> {
// if (discoverAdapter.isSelecting()) {
// if (actionMode == null) return;
// final String title = getString(R.string.number_selected, discoverAdapter.getSelectedModels().size());
// actionMode.setTitle(title);
// return;
// }
// if (checkAndResetAction()) return;
// final List<DiscoverItemModel> discoverItemModels = discoverItemViewModel.getList().getValue();
// if (discoverItemModels == null || discoverItemModels.size() == 0) return;
// if (discoverItemModels.get(0) == null) return;
// final String postId = discoverItemModels.get(0).getPostId();
// final boolean isId = postId != null;
// final String[] idsOrShortCodes = new String[discoverItemModels.size()];
// for (int i = 0; i < discoverItemModels.size(); i++) {
// idsOrShortCodes[i] = isId ? discoverItemModels.get(i).getPostId()
// : discoverItemModels.get(i).getShortCode();
// }
// final NavDirections action = DiscoverFragmentDirections.actionGlobalPostViewFragment(
// position,
// idsOrShortCodes,
// isId);
// NavHostFragment.findNavController(this).navigate(action);
// }, (model, position) -> {
// if (!discoverAdapter.isSelecting()) {
// checkAndResetAction();
// return true;
// }
// final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
// if (onBackPressedCallback.isEnabled()) {
// return true;
// }
// actionMode = fragmentActivity.startActionMode(multiSelectAction);
// final String title = getString(R.string.number_selected, 1);
// actionMode.setTitle(title);
// onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
// return true;
// });
// binding.postsRecyclerView.setAdapter(discoverAdapter);
// discoverItemViewModel.getList().observe(fragmentActivity, discoverAdapter::submitList);
// lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
// if (discoverHasMore) {
// fetchPosts();
// }
// }, 3);
// binding.postsRecyclerView.addOnScrollListener(lazyLoader);
// binding.postsRecyclerView.setViewModelStoreOwner(this)
// .setLifeCycleOwner(this)
// .setPostFetchService(new DiscoverPostFetchService())
// .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT)))
// .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
// .setFeedItemCallback(feedItemCallback)
// .init();
// binding.swipeRefreshLayout.setRefreshing(true);
}
private void fetchTopics() {
// new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
binding.swipeRefreshLayout.setRefreshing(true);
discoverService.topicalExplore(new DiscoverService.TopicalExploreRequest(), new ServiceCallback<DiscoverService.TopicalExploreResponse>() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (topicIds == null || topicIds.length <= 0) return;
currentTopic = topicIds[pos];
onRefresh();
public void onSuccess(final DiscoverService.TopicalExploreResponse result) {
topicClusterViewModel.getList().postValue(result.getClusters());
binding.swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
});
discoverAdapter = new DiscoverAdapter((model, position) -> {
if (discoverAdapter.isSelecting()) {
if (actionMode == null) return;
final String title = getString(R.string.number_selected, discoverAdapter.getSelectedModels().size());
actionMode.setTitle(title);
return;
}
if (checkAndResetAction()) return;
final List<DiscoverItemModel> discoverItemModels = discoverItemViewModel.getList().getValue();
if (discoverItemModels == null || discoverItemModels.size() == 0) return;
if (discoverItemModels.get(0) == null) return;
final String postId = discoverItemModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[discoverItemModels.size()];
for (int i = 0; i < discoverItemModels.size(); i++) {
idsOrShortCodes[i] = isId ? discoverItemModels.get(i).getPostId()
: discoverItemModels.get(i).getShortCode();
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure", t);
binding.swipeRefreshLayout.setRefreshing(false);
}
final NavDirections action = DiscoverFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!discoverAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedCallback.isEnabled()) {
return true;
}
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
binding.discoverPosts.setAdapter(discoverAdapter);
discoverItemViewModel.getList().observe(fragmentActivity, discoverAdapter::submitList);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (discoverHasMore) {
fetchPosts();
}
}, 3);
binding.discoverPosts.addOnScrollListener(lazyLoader);
fetchTopics();
}
private void fetchTopics() {
new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void fetchPosts() {
binding.discoverSwipeRefreshLayout.setRefreshing(true);
new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, postsFetchListener, false)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
// private void fetchPosts() {
// binding.swipeRefreshLayout.setRefreshing(true);
// new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, postsFetchListener, false)
// .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {

16
app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java

@ -58,17 +58,10 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private CoordinatorLayout root;
private FragmentFeedBinding binding;
private StoriesService storiesService;
// private boolean feedHasNextPage = false;
// private String feedEndCursor = null;
// private FeedViewModel feedViewModel;
// private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
private boolean shouldRefresh = true;
// private boolean isPullToRefresh;
private FeedStoriesViewModel feedStoriesViewModel;
// private StaggeredGridLayoutManager gridLayoutManager;
private boolean storiesFetching;
// private final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
@ -226,15 +219,11 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override
public void onRefresh() {
// isPullToRefresh = true;
// feedEndCursor = null;
binding.feedRecyclerView.refresh();
fetchStories();
}
private void setupFeed() {
final Context context = getContext();
if (context == null) return;
binding.feedRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new FeedPostFetchService())
@ -331,8 +320,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(Constants.PREF_POSTS_LAYOUT, preferences -> new Handler()
.postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

57
app/src/main/java/awais/instagrabber/models/TopicCluster.java

@ -0,0 +1,57 @@
package awais.instagrabber.models;
import java.io.Serializable;
public class TopicCluster implements Serializable {
private String id;
private String title;
private String type;
private boolean canMute;
private boolean isMuted;
private int rankedPosition;
private FeedModel coverMedia;
public TopicCluster(final String id,
final String title,
final String type,
final boolean canMute,
final boolean isMuted,
final int rankedPosition,
final FeedModel coverMedia) {
this.id = id;
this.title = title;
this.type = type;
this.canMute = canMute;
this.isMuted = isMuted;
this.rankedPosition = rankedPosition;
this.coverMedia = coverMedia;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getType() {
return type;
}
public boolean isCanMute() {
return canMute;
}
public boolean isMuted() {
return isMuted;
}
public int getRankedPosition() {
return rankedPosition;
}
public FeedModel getCoverMedia() {
return coverMedia;
}
}

12
app/src/main/java/awais/instagrabber/repositories/DiscoverRepository.java

@ -0,0 +1,12 @@
package awais.instagrabber.repositories;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;
public interface DiscoverRepository {
@GET("/api/v1/discover/topical_explore/")
Call<String> topicalExplore(@QueryMap Map<String, String> queryParams);
}

1
app/src/main/java/awais/instagrabber/utils/Constants.java

@ -89,4 +89,5 @@ public final class Constants {
public static final String SHARED_PREFERENCES_NAME = "settings";
public static final String PREF_POSTS_LAYOUT = "posts_layout";
public static final String PREF_PROFILE_POSTS_LAYOUT = "profile_posts_layout";
public static final String PREF_TOPIC_POSTS_LAYOUT = "topic_posts_layout";
}

24
app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java

@ -99,7 +99,7 @@ public final class ResponseBodyUtils {
if (Utils.logCollector != null)
Utils.logCollector.appendException(e, LogCollector.LogFile.UTILS, "getLowQualityImage",
new Pair<>("resourcesNull", resources == null));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "Error in getLowQualityImage", e);
}
return src;
}
@ -152,12 +152,24 @@ public final class ResponseBodyUtils {
}
public static String getVideoUrl(@NonNull final JSONObject mediaObj) {
String thumbnail = null;
final JSONArray imageVersions = mediaObj.optJSONArray("video_versions");
if (imageVersions != null) {
thumbnail = getItemThumbnail(imageVersions).url;
String url = null;
final JSONArray videoVersions = mediaObj.optJSONArray("video_versions");
if (videoVersions == null) {
return null;
}
return thumbnail;
int largestWidth = 0;
for (int i = 0; i < videoVersions.length(); i++) {
final JSONObject videoVersionJson = videoVersions.optJSONObject(i);
if (videoVersionJson == null) {
continue;
}
final int width = videoVersionJson.optInt("width");
if (largestWidth == 0 || width > largestWidth) {
largestWidth = width;
url = videoVersionJson.optString("url");
}
}
return url;
}
@Nullable

4
app/src/main/java/awais/instagrabber/utils/SettingsHelper.java

@ -32,6 +32,7 @@ import static awais.instagrabber.utils.Constants.PREF_DARK_THEME;
import static awais.instagrabber.utils.Constants.PREF_LIGHT_THEME;
import static awais.instagrabber.utils.Constants.PREF_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_PROFILE_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
import static awais.instagrabber.utils.Constants.SKIPPED_VERSION;
@ -115,7 +116,8 @@ public final class SettingsHelper {
@StringDef(
{APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT,
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT})
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT,
PREF_TOPIC_POSTS_LAYOUT})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,

19
app/src/main/java/awais/instagrabber/viewmodels/TopicClusterViewModel.java

@ -0,0 +1,19 @@
package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.TopicCluster;
public class TopicClusterViewModel extends ViewModel {
private MutableLiveData<List<TopicCluster>> list;
public MutableLiveData<List<TopicCluster>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

501
app/src/main/java/awais/instagrabber/webservices/DiscoverService.java

@ -0,0 +1,501 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableBiMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.TopicCluster;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.DiscoverRepository;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class DiscoverService extends BaseService {
private static final String TAG = "DiscoverService";
private final DiscoverRepository repository;
private static DiscoverService instance;
private DiscoverService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(DiscoverRepository.class);
}
public static DiscoverService getInstance() {
if (instance == null) {
instance = new DiscoverService();
}
return instance;
}
public void topicalExplore(@NonNull final TopicalExploreRequest request,
final ServiceCallback<TopicalExploreResponse> callback) {
final ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.<String, String>builder()
.put("module", "explore_popular");
if (!TextUtils.isEmpty(request.getModule())) {
builder.put("module", request.getModule());
}
if (!TextUtils.isEmpty(request.getClusterId())) {
builder.put("cluster_id", request.getClusterId());
}
if (request.getMaxId() >= 0) {
builder.put("max_id", String.valueOf(request.getMaxId()));
}
final Call<String> req = repository.topicalExplore(builder.build());
req.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
try {
final TopicalExploreResponse topicalExploreResponse = parseTopicalExploreResponse(body);
callback.onSuccess(topicalExploreResponse);
} catch (JSONException e) {
callback.onFailure(e);
// Log.e(TAG, "Error parsing topicalExplore response", e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
private TopicalExploreResponse parseTopicalExploreResponse(@NonNull final String body) throws JSONException {
final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available");
final int nextMaxId = root.optInt("next_max_id", -1);
final int numResults = root.optInt("num_results");
final String status = root.optString("status");
final JSONArray clustersJson = root.optJSONArray("clusters");
final List<TopicCluster> clusters = parseClusters(clustersJson);
final JSONArray itemsJson = root.optJSONArray("items");
final List<FeedModel> items = parseItems(itemsJson);
return new TopicalExploreResponse(
moreAvailable,
nextMaxId,
numResults,
status,
clusters,
items
);
}
private List<TopicCluster> parseClusters(final JSONArray clustersJson) throws JSONException {
if (clustersJson == null) {
return Collections.emptyList();
}
final List<TopicCluster> clusters = new ArrayList<>();
for (int i = 0; i < clustersJson.length(); i++) {
final JSONObject clusterJson = clustersJson.getJSONObject(i);
final String id = clusterJson.optString("id");
final String title = clusterJson.optString("title");
if (id == null || title == null) {
continue;
}
final String type = clusterJson.optString("type");
final boolean canMute = clusterJson.optBoolean("can_mute");
final boolean isMuted = clusterJson.optBoolean("is_muted");
final JSONObject coverMediaJson = clusterJson.optJSONObject("cover_media");
final int rankedPosition = clusterJson.optInt("ranked_position");
final FeedModel feedModel = parseClusterCover(coverMediaJson);
final TopicCluster topicCluster = new TopicCluster(
id,
title,
type,
canMute,
isMuted,
rankedPosition,
feedModel
);
clusters.add(topicCluster);
}
return clusters;
}
private FeedModel parseClusterCover(final JSONObject coverMediaJson) throws JSONException {
if (coverMediaJson == null) {
return null;
}
ProfileModel profileModel = null;
if (coverMediaJson.has("user")) {
final JSONObject user = coverMediaJson.getJSONObject("user");
profileModel = new ProfileModel(
user.optBoolean("is_private"),
false,
user.optBoolean("is_verified"),
user.getString("pk"),
user.getString(Constants.EXTRAS_USERNAME),
user.optString("full_name"),
null,
null,
user.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
}
final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson);
final String thumbnailUrl = ResponseBodyUtils.getLowQualityImage(coverMediaJson);
final int width = coverMediaJson.optInt("original_width");
final int height = coverMediaJson.optInt("original_height");
return new FeedModel.Builder()
.setProfileModel(profileModel)
.setItemType(MediaItemType.MEDIA_TYPE_IMAGE)
.setViewCount(0)
.setPostId(coverMediaJson.getString(Constants.EXTRAS_ID))
.setDisplayUrl(resourceUrl)
.setThumbnailUrl(thumbnailUrl)
.setShortCode(coverMediaJson.getString("code"))
.setPostCaption(null)
.setCommentsCount(0)
.setTimestamp(coverMediaJson.optLong("taken_at", -1))
.setLiked(false)
.setBookmarked(false)
.setLikesCount(0)
.setLocationName(null)
.setLocationId(null)
.setImageHeight(height)
.setImageWidth(width)
.build();
}
private List<FeedModel> parseItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FeedModel> feedModels = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
final JSONObject itemJson = items.optJSONObject(i);
if (itemJson == null) {
continue;
}
final JSONObject mediaJson = itemJson.optJSONObject("media");
final FeedModel feedModel = parseClusterItemMedia(mediaJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
private FeedModel parseClusterItemMedia(final JSONObject mediaJson) throws JSONException {
if (mediaJson == null) {
return null;
}
ProfileModel profileModel = null;
if (mediaJson.has("user")) {
final JSONObject user = mediaJson.getJSONObject("user");
final JSONObject friendshipStatus = user.optJSONObject("friendship_status");
boolean following = false;
boolean restricted = false;
boolean requested = false;
if (friendshipStatus != null) {
following = friendshipStatus.optBoolean("following");
requested = friendshipStatus.optBoolean("outgoing_request");
restricted = friendshipStatus.optBoolean("is_restricted");
}
profileModel = new ProfileModel(
user.optBoolean("is_private"),
false, // if you can see it then you def follow
user.optBoolean("is_verified"),
user.getString("pk"),
user.getString(Constants.EXTRAS_USERNAME),
user.optString("full_name"),
null,
null,
user.getString("profile_pic_url"),
null,
0,
0,
0,
following,
restricted,
false,
requested);
}
final JSONObject captionJson = mediaJson.optJSONObject("caption");
final JSONObject locationJson = mediaJson.optJSONObject("location");
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(mediaJson.optInt("media_type"));
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
.setItemType(mediaType)
.setProfileModel(profileModel)
.setPostId(mediaJson.getString(Constants.EXTRAS_ID))
.setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(mediaJson) : null)
.setShortCode(mediaJson.getString("code"))
.setPostCaption(captionJson != null ? captionJson.optString("text") : null)
.setCommentsCount(mediaJson.optInt("comment_count"))
.setTimestamp(mediaJson.optLong("taken_at", -1))
.setLiked(mediaJson.optBoolean("has_liked"))
// .setBookmarked()
.setLikesCount(mediaJson.optInt("like_count"))
.setLocationName(locationJson != null ? locationJson.optString("name") : null)
.setLocationId(locationJson != null ? String.valueOf(locationJson.optInt("pk")) : null)
.setImageHeight(mediaJson.optInt("original_height"))
.setImageWidth(mediaJson.optInt("original_width"));
switch (mediaType) {
case MEDIA_TYPE_VIDEO:
final long videoViews = mediaJson.optLong("view_count", 0);
feedModelBuilder.setViewCount(videoViews)
.setDisplayUrl(ResponseBodyUtils.getVideoUrl(mediaJson));
break;
case MEDIA_TYPE_IMAGE:
feedModelBuilder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(mediaJson));
break;
case MEDIA_TYPE_SLIDER:
final List<PostChild> childPosts = getChildPosts(mediaJson);
feedModelBuilder.setSliderItems(childPosts);
break;
}
return feedModelBuilder.build();
}
private List<PostChild> getChildPosts(final JSONObject mediaJson) throws JSONException {
if (mediaJson == null) {
return Collections.emptyList();
}
final JSONArray carouselMedia = mediaJson.optJSONArray("carousel_media");
if (carouselMedia == null) {
return Collections.emptyList();
}
final List<PostChild> children = new ArrayList<>();
for (int i = 0; i < carouselMedia.length(); i++) {
final JSONObject childJson = carouselMedia.optJSONObject(i);
final PostChild childPost = getChildPost(childJson);
if (childPost != null) {
children.add(childPost);
}
}
return children;
}
private PostChild getChildPost(final JSONObject childJson) throws JSONException {
if (childJson == null) {
return null;
}
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(childJson.optInt("media_type"));
final PostChild.Builder builder = new PostChild.Builder();
switch (mediaType) {
case MEDIA_TYPE_VIDEO:
builder.setDisplayUrl(ResponseBodyUtils.getVideoUrl(childJson));
break;
case MEDIA_TYPE_IMAGE:
builder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(childJson));
break;
}
return builder.setItemType(mediaType)
.setPostId(childJson.getString("id"))
.setThumbnailUrl(ResponseBodyUtils.getLowQualityImage(childJson))
.setHeight(childJson.optInt("original_height"))
.setWidth(childJson.optInt("original_width"))
.build();
}
public static class TopicalExploreRequest {
private String module;
private String clusterId;
private int maxId = -1;
public TopicalExploreRequest() {}
public TopicalExploreRequest(final String module, final String clusterId, final int maxId) {
this.module = module;
this.clusterId = clusterId;
this.maxId = maxId;
}
public String getModule() {
return module;
}
public TopicalExploreRequest setModule(final String module) {
this.module = module;
return this;
}
public String getClusterId() {
return clusterId;
}
public TopicalExploreRequest setClusterId(final String clusterId) {
this.clusterId = clusterId;
return this;
}
public int getMaxId() {
return maxId;
}
public TopicalExploreRequest setMaxId(final int maxId) {
this.maxId = maxId;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final TopicalExploreRequest that = (TopicalExploreRequest) o;
return maxId == that.maxId &&
Objects.equals(module, that.module) &&
Objects.equals(clusterId, that.clusterId);
}
@Override
public int hashCode() {
return Objects.hash(module, clusterId, maxId);
}
@Override
public String toString() {
return "TopicalExploreRequest{" +
"module='" + module + '\'' +
", clusterId='" + clusterId + '\'' +
", maxId=" + maxId +
'}';
}
}
public static class TopicalExploreResponse {
private boolean moreAvailable;
private int nextMaxId;
private int numResults;
private String status;
private List<TopicCluster> clusters;
private List<FeedModel> items;
public TopicalExploreResponse() {}
public TopicalExploreResponse(final boolean moreAvailable,
final int nextMaxId,
final int numResults,
final String status,
final List<TopicCluster> clusters, final List<FeedModel> items) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
this.numResults = numResults;
this.status = status;
this.clusters = clusters;
this.items = items;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public TopicalExploreResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public int getNextMaxId() {
return nextMaxId;
}
public TopicalExploreResponse setNextMaxId(final int nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public TopicalExploreResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public TopicalExploreResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<TopicCluster> getClusters() {
return clusters;
}
public TopicalExploreResponse setClusters(final List<TopicCluster> clusters) {
this.clusters = clusters;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public TopicalExploreResponse setItems(final List<FeedModel> items) {
this.items = items;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final TopicalExploreResponse that = (TopicalExploreResponse) o;
return moreAvailable == that.moreAvailable &&
nextMaxId == that.nextMaxId &&
numResults == that.numResults &&
Objects.equals(status, that.status) &&
Objects.equals(clusters, that.clusters) &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, clusters, items);
}
@Override
public String toString() {
return "TopicalExploreResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId=" + nextMaxId +
", numResults=" + numResults +
", status='" + status + '\'' +
", clusters=" + clusters +
", items=" + items +
'}';
}
}
}

44
app/src/main/res/layout/fragment_discover.xml

@ -7,37 +7,39 @@
android:animateLayoutChanges="true"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorSurface"
app:elevation="0dp">
<!--<com.google.android.material.appbar.AppBarLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:background="?attr/colorSurface"-->
<!-- app:elevation="0dp">-->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways">
<!-- <com.google.android.material.appbar.CollapsingToolbarLayout-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- app:layout_scrollFlags="scroll|snap|enterAlways">-->
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/discoverType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/discover_placeholder"
android:spinnerMode="dialog" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<!-- <androidx.appcompat.widget.AppCompatSpinner-->
<!-- android:id="@+id/discoverType"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:entries="@array/discover_placeholder"-->
<!-- android:spinnerMode="dialog" />-->
<!-- </com.google.android.material.appbar.CollapsingToolbarLayout>-->
<!--</com.google.android.material.appbar.AppBarLayout>-->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverSwipeRefreshLayout"
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverPosts"
android:id="@+id/topics_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_post" />
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"
tools:listitem="@layout/item_discover_topic" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

14
app/src/main/res/layout/fragment_profile.xml

@ -285,20 +285,6 @@
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!--<androidx.swiperefreshlayout.widget.SwipeRefreshLayout-->
<!-- android:id="@+id/swipeRefreshLayout"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">-->
<!-- <androidx.recyclerview.widget.RecyclerView-->
<!-- android:id="@+id/mainPosts"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:clipToPadding="false"-->
<!-- tools:listitem="@layout/item_post" />-->
<!--</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>-->
<LinearLayout
android:id="@+id/privatePage"
android:layout_width="match_parent"

58
app/src/main/res/layout/fragment_topic_posts.xml

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/cover"
android:layout_width="match_parent"
android:layout_height="200dp"
app:actualImageScaleType="centerCrop"
tools:background="@mipmap/ic_launcher" />
<View
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<awais.instagrabber.customviews.PostsRecyclerView
android:id="@+id/posts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

45
app/src/main/res/layout/item_discover_topic.xml

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/cover"
android:layout_width="0dp"
android:layout_height="0dp"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundedCornerRadius="5dp"
app:viewAspectRatio="1"
tools:background="@mipmap/ic_launcher" />
<View
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.5"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="bottom"
android:padding="8dp"
android:textStyle="bold"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.3"
app:layout_constraintStart_toStartOf="parent"
tools:text="Title" />
</androidx.constraintlayout.widget.ConstraintLayout>

9
app/src/main/res/menu/topic_posts_menu.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/layout"
android:title="@string/layout"
app:showAsAction="never" />
</menu>

76
app/src/main/res/navigation/discover_nav_graph.xml

@ -5,9 +5,40 @@
android:id="@+id/discover_nav_graph"
app:startDestination="@id/discoverFragment">
<include app:graph="@navigation/post_view_nav_graph" />
<include app:graph="@navigation/hashtag_nav_graph" />
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/profile_nav_graph" />
<include app:graph="@navigation/comments_nav_graph" />
<action
android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph">
<argument
android:name="username"
app:argType="string"
app:nullable="true" />
</action>
<include app:graph="@navigation/location_nav_graph" />
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/post_view_nav_graph" />
<action
android:id="@+id/action_global_postViewFragment"
@ -23,9 +54,48 @@
app:argType="boolean" />
</action>
<include app:graph="@navigation/comments_nav_graph" />
<action
android:id="@+id/action_global_commentsViewerFragment"
app:destination="@id/comments_nav_graph">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/discoverFragment"
android:name="awais.instagrabber.fragments.main.DiscoverFragment"
android:label="@string/title_discover"
tools:layout="@layout/fragment_discover" />
tools:layout="@layout/fragment_discover">
<action
android:id="@+id/action_discoverFragment_to_topicPostsFragment"
app:destination="@id/topicPostsFragment" />
</fragment>
<fragment
android:id="@+id/topicPostsFragment"
android:name="awais.instagrabber.fragments.TopicPostsFragment"
tools:layout="@layout/fragment_topic_posts">
<argument
android:name="topicCluster"
app:argType="awais.instagrabber.models.TopicCluster" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
</fragment>
</navigation>
Loading…
Cancel
Save