Austin Huang
4 years ago
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
4 changed files with 273 additions and 562 deletions
-
393app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
-
343app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java
-
25app/src/main/res/layout/fragment_discover.xml
-
60app/src/main/res/navigation/discover_nav_graph.xml
@ -1,393 +0,0 @@ |
|||
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.util.Log; |
|||
import android.view.ActionMode; |
|||
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.activity.OnBackPressedCallback; |
|||
import androidx.activity.OnBackPressedDispatcher; |
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.coordinatorlayout.widget.CoordinatorLayout; |
|||
import androidx.core.graphics.ColorUtils; |
|||
import androidx.fragment.app.Fragment; |
|||
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 com.google.common.collect.ImmutableList; |
|||
|
|||
import java.util.Set; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.MainActivity; |
|||
import awais.instagrabber.adapters.FeedAdapterV2; |
|||
import awais.instagrabber.asyncs.DiscoverPostFetchService; |
|||
import awais.instagrabber.customviews.PrimaryActionModeCallback; |
|||
import awais.instagrabber.databinding.FragmentTopicPostsBinding; |
|||
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.repositories.responses.Location; |
|||
import awais.instagrabber.repositories.responses.Media; |
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.repositories.responses.discover.TopicCluster; |
|||
import awais.instagrabber.utils.AppExecutors; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DownloadUtils; |
|||
import awais.instagrabber.utils.ResponseBodyUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.webservices.DiscoverService; |
|||
|
|||
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { |
|||
private static final String TAG = TopicPostsFragment.class.getSimpleName(); |
|||
|
|||
private MainActivity fragmentActivity; |
|||
private FragmentTopicPostsBinding binding; |
|||
private CoordinatorLayout root; |
|||
private boolean shouldRefresh = true; |
|||
private TopicCluster topicCluster; |
|||
private ActionMode actionMode; |
|||
private Set<Media> selectedFeedModels; |
|||
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_TOPIC_POSTS_LAYOUT); |
|||
|
|||
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { |
|||
@Override |
|||
public void handleOnBackPressed() { |
|||
binding.posts.endSelection(); |
|||
} |
|||
}; |
|||
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( |
|||
R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { |
|||
@Override |
|||
public void onDestroy(final ActionMode mode) { |
|||
binding.posts.endSelection(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onActionItemClicked(final ActionMode mode, |
|||
final MenuItem item) { |
|||
if (item.getItemId() == R.id.action_download) { |
|||
if (TopicPostsFragment.this.selectedFeedModels == null) return false; |
|||
final Context context = getContext(); |
|||
if (context == null) return false; |
|||
DownloadUtils.download(context, ImmutableList.copyOf(TopicPostsFragment.this.selectedFeedModels)); |
|||
binding.posts.endSelection(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { |
|||
@Override |
|||
public void onPostClick(final Media feedModel) { |
|||
openPostDialog(feedModel, -1); |
|||
} |
|||
|
|||
@Override |
|||
public void onSliderClick(final Media feedModel, final int position) { |
|||
openPostDialog(feedModel, position); |
|||
} |
|||
|
|||
@Override |
|||
public void onCommentsClick(final Media feedModel) { |
|||
final User user = feedModel.getUser(); |
|||
if (user == null) return; |
|||
final NavDirections commentsAction = TopicPostsFragmentDirections.actionToComments( |
|||
feedModel.getCode(), |
|||
feedModel.getPk(), |
|||
user.getPk() |
|||
); |
|||
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(commentsAction); |
|||
} |
|||
|
|||
@Override |
|||
public void onDownloadClick(final Media feedModel, final int childPosition, final View popupLocation) { |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
DownloadUtils.showDownloadDialog(context, feedModel, childPosition, popupLocation); |
|||
} |
|||
|
|||
@Override |
|||
public void onHashtagClick(final String hashtag) { |
|||
final NavDirections action = TopicPostsFragmentDirections.actionToHashtag(hashtag); |
|||
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); |
|||
} |
|||
|
|||
@Override |
|||
public void onLocationClick(final Media feedModel) { |
|||
final Location location = feedModel.getLocation(); |
|||
if (location == null) return; |
|||
final NavDirections action = TopicPostsFragmentDirections.actionToLocation(location.getPk()); |
|||
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); |
|||
} |
|||
|
|||
@Override |
|||
public void onMentionClick(final String mention) { |
|||
navigateToProfile(mention.trim()); |
|||
} |
|||
|
|||
@Override |
|||
public void onNameClick(final Media feedModel) { |
|||
navigateToProfile("@" + feedModel.getUser().getUsername()); |
|||
} |
|||
|
|||
@Override |
|||
public void onProfilePicClick(final Media feedModel) { |
|||
final User user = feedModel.getUser(); |
|||
if (user == null) return; |
|||
navigateToProfile("@" + user.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 Media feedModel, final int position) { |
|||
try { |
|||
final NavDirections action = TopicPostsFragmentDirections.actionToPost(feedModel, position); |
|||
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "openPostDialog: ", e); |
|||
} |
|||
} |
|||
}; |
|||
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { |
|||
|
|||
@Override |
|||
public void onSelectionStart() { |
|||
if (!onBackPressedCallback.isEnabled()) { |
|||
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); |
|||
onBackPressedCallback.setEnabled(true); |
|||
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); |
|||
} |
|||
if (actionMode == null) { |
|||
actionMode = fragmentActivity.startActionMode(multiSelectAction); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onSelectionChange(final Set<Media> selectedFeedModels) { |
|||
final String title = getString(R.string.number_selected, selectedFeedModels.size()); |
|||
if (actionMode != null) { |
|||
actionMode.setTitle(title); |
|||
} |
|||
TopicPostsFragment.this.selectedFeedModels = selectedFeedModels; |
|||
} |
|||
|
|||
@Override |
|||
public void onSelectionEnd() { |
|||
if (onBackPressedCallback.isEnabled()) { |
|||
onBackPressedCallback.setEnabled(false); |
|||
onBackPressedCallback.remove(); |
|||
} |
|||
if (actionMode != null) { |
|||
actionMode.finish(); |
|||
actionMode = null; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
fragmentActivity = (MainActivity) requireActivity(); |
|||
final Context context = getContext(); |
|||
if (context != null) { |
|||
final TransitionSet transitionSet = new TransitionSet(); |
|||
transitionSet.addTransition(new ChangeBounds()) |
|||
.addTransition(TransitionInflater.from(context).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 onResume() { |
|||
super.onResume(); |
|||
fragmentActivity.setToolbar(binding.toolbar, this); |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
binding.posts.refresh(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStop() { |
|||
super.onStop(); |
|||
fragmentActivity.resetToolbar(this); |
|||
} |
|||
|
|||
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, this); |
|||
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 = ResponseBodyUtils.getImageUrl(topicCluster.getCoverMedia()); |
|||
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(layoutPreferences) |
|||
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) |
|||
.setFeedItemCallback(feedItemCallback) |
|||
.setSelectionModeCallback(selectionModeCallback) |
|||
.init(); |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
private void updateSwipeRefreshState() { |
|||
AppExecutors.INSTANCE.getMainThread().execute(() -> binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching()) |
|||
); |
|||
} |
|||
|
|||
private void navigateToProfile(final String username) { |
|||
try { |
|||
final NavDirections action = TopicPostsFragmentDirections.actionToProfile().setUsername(username); |
|||
NavHostFragment.findNavController(this).navigate(action); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "navigateToProfile: ", e); |
|||
} |
|||
} |
|||
|
|||
private void showPostsLayoutPreferences() { |
|||
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment( |
|||
Constants.PREF_TOPIC_POSTS_LAYOUT, |
|||
preferences -> { |
|||
layoutPreferences = preferences; |
|||
new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200); |
|||
}); |
|||
fragment.show(getChildFragmentManager(), "posts_layout_preferences"); |
|||
} |
|||
} |
@ -1,27 +1,16 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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"> |
|||
|
|||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
|||
android:id="@+id/swipe_refresh_layout" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:clipToPadding="false" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior" |
|||
tools:context=".fragments.DiscoverFragment"> |
|||
|
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/topics_recycler_view" |
|||
<awais.instagrabber.customviews.PostsRecyclerView |
|||
android:id="@+id/posts" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:clipToPadding="false" |
|||
android:paddingBottom="?actionBarSize" |
|||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" |
|||
app:spanCount="2" |
|||
tools:itemCount="10" |
|||
tools:listitem="@layout/item_discover_topic" /> |
|||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
|||
android:clipToPadding="false" /> |
|||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue