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);
|
2020-09-02 17:02:31 +00:00
|
|
|
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;
|
2020-08-30 06:47:04 +00:00
|
|
|
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;
|
2020-09-02 17:02:31 +00:00
|
|
|
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;
|
2020-09-02 17:02:31 +00:00
|
|
|
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;
|
2020-09-02 17:02:31 +00:00
|
|
|
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) {
|
2020-09-02 17:02:31 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2020-08-30 14:24:28 +00:00
|
|
|
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
|
2020-08-29 08:01:42 +00:00
|
|
|
if (isHashtag) {
|
2020-08-30 14:24:28 +00:00
|
|
|
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) {
|
2020-09-02 17:02:31 +00:00
|
|
|
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(text);
|
2020-08-30 12:51:47 +00:00
|
|
|
NavHostFragment.findNavController(this).navigate(action);
|
|
|
|
return;
|
|
|
|
}
|
2020-08-30 14:24:28 +00:00
|
|
|
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) {
|
2020-09-02 17:02:31 +00:00
|
|
|
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:
|
2020-09-04 17:38:36 +00:00
|
|
|
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:
|
2020-09-02 17:02:31 +00:00
|
|
|
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();
|
|
|
|
|
2020-09-02 17:02:31 +00:00
|
|
|
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) {
|
2020-09-02 17:02:31 +00:00
|
|
|
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
|
2020-09-02 17:02:31 +00:00
|
|
|
if (breakWhenFoundSelected && postModels.size() == 0) {
|
2020-08-29 08:01:42 +00:00
|
|
|
postModels.add(sliderItems[0]);
|
2020-09-02 17:02:31 +00:00
|
|
|
}
|
|
|
|
if (postModels.size() > 0) {
|
|
|
|
Utils.batchDownload(requireContext(),
|
|
|
|
username,
|
|
|
|
DownloadMethod.DOWNLOAD_FEED,
|
|
|
|
postModels);
|
|
|
|
}
|
2020-08-29 08:01:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
new AlertDialog.Builder(requireContext())
|
2020-09-02 17:02:31 +00:00
|
|
|
.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();
|
2020-09-02 17:02:31 +00:00
|
|
|
if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
|
2020-08-29 08:01:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-30 14:24:28 +00:00
|
|
|
@Override
|
|
|
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
fragmentActivity = (MainActivity) requireActivity();
|
|
|
|
storiesService = StoriesService.getInstance();
|
2020-09-02 17:02:31 +00:00
|
|
|
// feedService = FeedService.getInstance();
|
2020-08-30 14:24:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@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);
|
2020-09-02 17:02:31 +00:00
|
|
|
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) -> {
|
2020-09-02 17:02:31 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|