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

366 lines
17 KiB
Java
Raw Normal View History

2020-08-29 08:01:42 +00:00
package awais.instagrabber.fragments.main;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.fragments.main.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.fragments.main.viewmodels.FeedViewModel;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
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.MediaItemType;
import awais.instagrabber.services.ServiceCallback;
import awais.instagrabber.services.StoriesService;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedFragment extends Fragment {
private static final String TAG = "FeedFragment";
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 final boolean SHOULD_AUTO_PLAY = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
2020-08-29 08:01:42 +00:00
private MainActivity fragmentActivity;
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;
2020-08-29 08:01:42 +00:00
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
@Override
public void doBefore() {
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
}
@Override
public void onResult(final FeedModel[] result) {
if (result == null) return;
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
for (final FeedModel feedModel : result) {
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
@Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
final Map<String, Object> extras = dataSource.getExtras();
if (extras == null) return;
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) {
2020-08-29 08:01:42 +00:00
// 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);
2020-08-29 08:01:42 +00:00
}
feedModel.setImageWidth(requiredWidth);
feedModel.setImageHeight(resultingHeight);
success++;
updateAdapter();
}
@Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
failed++;
updateAdapter();
}
public void updateAdapter() {
if (failed + success != result.length) return;
final List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
? new ArrayList<>()
: new ArrayList<>(currentFeedModelList);
2020-08-29 08:01:42 +00:00
finalList.addAll(Arrays.asList(result));
feedViewModel.getList().postValue(finalList);
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
binding.feedSwipeRefreshLayout.setRefreshing(false);
}
};
for (final FeedModel feedModel : result) {
final DataSource<Void> ds = Fresco.getImagePipeline()
.prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
2020-08-29 08:01:42 +00:00
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
2020-08-29 08:01:42 +00:00
if (isHashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(text);
2020-08-30 11:45:37 +00:00
NavHostFragment.findNavController(this).navigate(action);
2020-08-29 08:01:42 +00:00
return;
}
2020-08-30 12:51:47 +00:00
if (isLocation) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(text);
2020-08-30 12:51:47 +00:00
NavHostFragment.findNavController(this).navigate(action);
return;
}
final NavDirections action = FeedFragmentDirections.actionGlobalProfileFragment("@" + text);
2020-08-29 08:01:42 +00:00
NavHostFragment.findNavController(this).navigate(action);
};
private final View.OnClickListener postViewClickListener = v -> {
final Object tag = v.getTag();
if (!(tag instanceof FeedModel)) return;
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked()) feedModel.toggleCaption();
2020-08-29 08:01:42 +00:00
feedModel.setMentionClicked(false);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
feedModel.toggleCaption();
return;
}
final int id = v.getId();
switch (id) {
case R.id.btnComments:
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(this).navigate(commentsAction);
2020-08-29 08:01:42 +00:00
break;
case R.id.viewStoryPost:
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
if (feedModels == null || feedModels.size() == 0) return;
if (feedModels.get(0) == null) return;
final String postId = feedModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[feedModels.size()];
for (int i = 0; i < feedModels.size(); i++) {
idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
: feedModels.get(i).getShortCode();
}
final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
feedModel.getPosition(),
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
2020-08-29 08:01:42 +00:00
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(requireContext(),
username,
DownloadMethod.DOWNLOAD_FEED,
Collections.singletonList(feedModel));
2020-08-29 08:01:42 +00:00
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected) postModels.add(sliderItem);
2020-08-29 08:01:42 +00:00
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) {
2020-08-29 08:01:42 +00:00
postModels.add(sliderItems[0]);
}
if (postModels.size() > 0) {
Utils.batchDownload(requireContext(),
username,
DownloadMethod.DOWNLOAD_FEED,
postModels);
}
2020-08-29 08:01:42 +00:00
};
new AlertDialog.Builder(requireContext())
.setTitle(R.string.post_viewer_download_dialog_title).setPositiveButton(
R.string.post_viewer_download_current,
clickListener1)
2020-08-29 08:01:42 +00:00
.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, false);
2020-08-29 08:01:42 +00:00
break;
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance();
// feedService = FeedService.getInstance();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
shouldRefresh = false;
return root;
}
binding = FragmentFeedBinding.inflate(inflater, container, false);
root = binding.getRoot();
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
setupFeedStories();
setupFeed();
shouldRefresh = false;
}
@Override
public void onPause() {
super.onPause();
if (videoAwareRecyclerScroller != null) {
videoAwareRecyclerScroller.stopPlaying();
}
}
@Override
public void onResume() {
super.onResume();
if (videoAwareRecyclerScroller != null && SHOULD_AUTO_PLAY) {
videoAwareRecyclerScroller.startPlaying();
}
}
2020-08-29 08:01:42 +00:00
private void setupFeed() {
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
binding.feedRecyclerView.setLayoutManager(layoutManager);
binding.feedRecyclerView.setHasFixedSize(true);
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener);
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
binding.feedRecyclerView.setAdapter(feedAdapter);
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (feedHasNextPage) {
fetchFeed();
}
});
if (SHOULD_AUTO_PLAY) {
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
}
binding.feedRecyclerView.addOnScrollListener(lazyLoader);
fetchFeed();
}
private void fetchFeed() {
binding.feedSwipeRefreshLayout.setRefreshing(true);
new FeedFetcher(feedEndCursor, feedFetchListener)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
2020-08-29 08:01:42 +00:00
feedEndCursor = null;
}
private void setupFeedStories() {
final FeedStoriesViewModel feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter((model, position) -> {
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false);
2020-08-29 08:01:42 +00:00
NavHostFragment.findNavController(this).navigate(action);
});
binding.feedStoriesRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false));
binding.feedStoriesRecyclerView.setAdapter(feedStoriesAdapter);
feedStoriesViewModel.getList().observe(fragmentActivity, feedStoriesAdapter::submitList);
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
}
});
}
}