Browse Source
Merge remote-tracking branch 'origin/dm-notifications-enhancements' into dm-notifications-enhancements
renovate/org.robolectric-robolectric-4.x
Merge remote-tracking branch 'origin/dm-notifications-enhancements' into dm-notifications-enhancements
renovate/org.robolectric-robolectric-4.x
Ammar Githam
4 years ago
57 changed files with 1734 additions and 591 deletions
-
2app/src/main/java/awais/instagrabber/adapters/DiscoverTopicsAdapter.java
-
57app/src/main/java/awais/instagrabber/adapters/SavedCollectionsAdapter.java
-
72app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
-
10app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java
-
432app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
-
8app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
161app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java
-
2app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
-
4app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
12app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
-
1app/src/main/java/awais/instagrabber/models/enums/PostItemType.java
-
15app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java
-
50app/src/main/java/awais/instagrabber/repositories/responses/saved/CollectionsListResponse.java
-
46app/src/main/java/awais/instagrabber/repositories/responses/saved/SavedCollection.java
-
1app/src/main/java/awais/instagrabber/utils/Constants.java
-
3app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
19app/src/main/java/awais/instagrabber/viewmodels/SavedCollectionsViewModel.java
-
75app/src/main/java/awais/instagrabber/webservices/ProfileService.java
-
58app/src/main/res/layout/fragment_collection_posts.xml
-
24app/src/main/res/layout/fragment_saved_collections.xml
-
10app/src/main/res/menu/saved_collection_menu.xml
-
6app/src/main/res/navigation/profile_nav_graph.xml
-
101app/src/main/res/navigation/saved_nav_graph.xml
-
34app/src/main/res/values-ca/arrays.xml
-
2app/src/main/res/values-ca/strings.xml
-
34app/src/main/res/values-cs/arrays.xml
-
38app/src/main/res/values-de/arrays.xml
-
34app/src/main/res/values-es/arrays.xml
-
4app/src/main/res/values-es/strings.xml
-
34app/src/main/res/values-fa/arrays.xml
-
34app/src/main/res/values-fr/arrays.xml
-
34app/src/main/res/values-hi/arrays.xml
-
2app/src/main/res/values-hi/strings.xml
-
40app/src/main/res/values-in/arrays.xml
-
64app/src/main/res/values-in/strings.xml
-
34app/src/main/res/values-it/arrays.xml
-
38app/src/main/res/values-ja/arrays.xml
-
26app/src/main/res/values-ja/strings.xml
-
34app/src/main/res/values-kn/arrays.xml
-
34app/src/main/res/values-mk/arrays.xml
-
102app/src/main/res/values-mk/strings.xml
-
40app/src/main/res/values-nl/arrays.xml
-
8app/src/main/res/values-nl/strings.xml
-
34app/src/main/res/values-or/arrays.xml
-
38app/src/main/res/values-or/strings.xml
-
34app/src/main/res/values-pl/arrays.xml
-
34app/src/main/res/values-pt/arrays.xml
-
40app/src/main/res/values-ru/arrays.xml
-
84app/src/main/res/values-ru/strings.xml
-
40app/src/main/res/values-sk/arrays.xml
-
12app/src/main/res/values-sk/strings.xml
-
34app/src/main/res/values-tr/arrays.xml
-
34app/src/main/res/values-vi/arrays.xml
-
34app/src/main/res/values-zh-rCN/arrays.xml
-
40app/src/main/res/values-zh-rTW/arrays.xml
-
22app/src/main/res/values-zh-rTW/strings.xml
-
2app/src/main/res/values/strings.xml
@ -0,0 +1,57 @@ |
|||
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.repositories.responses.saved.SavedCollection; |
|||
import awais.instagrabber.utils.ResponseBodyUtils; |
|||
|
|||
public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicClusterViewHolder> { |
|||
private static final DiffUtil.ItemCallback<SavedCollection> DIFF_CALLBACK = new DiffUtil.ItemCallback<SavedCollection>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) { |
|||
return oldItem.getId().equals(newItem.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) { |
|||
if (oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) { |
|||
if (oldItem.getCoverMedias().size() == 0) return true; |
|||
return oldItem.getCoverMedias().get(0).getId().equals(newItem.getCoverMedias().get(0).getId()); |
|||
} |
|||
return false; |
|||
} |
|||
}; |
|||
|
|||
private final OnCollectionClickListener onCollectionClickListener; |
|||
|
|||
public SavedCollectionsAdapter(final OnCollectionClickListener onCollectionClickListener) { |
|||
super(DIFF_CALLBACK); |
|||
this.onCollectionClickListener = onCollectionClickListener; |
|||
} |
|||
|
|||
@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, null, onCollectionClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final TopicClusterViewHolder holder, final int position) { |
|||
final SavedCollection topicCluster = getItem(position); |
|||
holder.bind(topicCluster); |
|||
} |
|||
|
|||
public interface OnCollectionClickListener { |
|||
void onCollectionClick(SavedCollection savedCollection, View root, View cover, View title, int titleColor, int backgroundColor); |
|||
} |
|||
} |
@ -0,0 +1,432 @@ |
|||
package awais.instagrabber.fragments; |
|||
|
|||
import android.animation.ArgbEvaluator; |
|||
import android.content.Context; |
|||
import android.content.pm.PackageManager; |
|||
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.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.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 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.SavedPostFetchService; |
|||
import awais.instagrabber.customviews.PrimaryActionModeCallback; |
|||
import awais.instagrabber.databinding.FragmentCollectionPostsBinding; |
|||
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; |
|||
import awais.instagrabber.fragments.CollectionPostsFragmentDirections; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.models.enums.PostItemType; |
|||
import awais.instagrabber.repositories.responses.Media; |
|||
import awais.instagrabber.repositories.responses.saved.SavedCollection; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DownloadUtils; |
|||
import awais.instagrabber.utils.ResponseBodyUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static androidx.core.content.PermissionChecker.checkSelfPermission; |
|||
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; |
|||
|
|||
public class CollectionPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { |
|||
private static final int STORAGE_PERM_REQUEST_CODE = 8020; |
|||
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; |
|||
|
|||
private MainActivity fragmentActivity; |
|||
private FragmentCollectionPostsBinding binding; |
|||
private CoordinatorLayout root; |
|||
private boolean shouldRefresh = true; |
|||
private SavedCollection savedCollection; |
|||
private ActionMode actionMode; |
|||
private Set<Media> selectedFeedModels; |
|||
private Media downloadFeedModel; |
|||
private int downloadChildPosition = -1; |
|||
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_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 (CollectionPostsFragment.this.selectedFeedModels == null) return false; |
|||
final Context context = getContext(); |
|||
if (context == null) return false; |
|||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { |
|||
DownloadUtils.download(context, ImmutableList.copyOf(CollectionPostsFragment.this.selectedFeedModels)); |
|||
binding.posts.endSelection(); |
|||
return true; |
|||
} |
|||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { |
|||
@Override |
|||
public void onPostClick(final Media feedModel, final View profilePicView, final View mainPostImage) { |
|||
openPostDialog(feedModel, profilePicView, mainPostImage, -1); |
|||
} |
|||
|
|||
@Override |
|||
public void onSliderClick(final Media feedModel, final int position) { |
|||
openPostDialog(feedModel, null, null, position); |
|||
} |
|||
|
|||
@Override |
|||
public void onCommentsClick(final Media feedModel) { |
|||
final NavDirections commentsAction = CollectionPostsFragmentDirections.actionGlobalCommentsViewerFragment( |
|||
feedModel.getCode(), |
|||
feedModel.getPk(), |
|||
feedModel.getUser().getPk() |
|||
); |
|||
NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(commentsAction); |
|||
} |
|||
|
|||
@Override |
|||
public void onDownloadClick(final Media feedModel, final int childPosition) { |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { |
|||
DownloadUtils.showDownloadDialog(context, feedModel, childPosition); |
|||
return; |
|||
} |
|||
downloadFeedModel = feedModel; |
|||
downloadChildPosition = -1; |
|||
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); |
|||
} |
|||
|
|||
@Override |
|||
public void onHashtagClick(final String hashtag) { |
|||
final NavDirections action = CollectionPostsFragmentDirections.actionGlobalHashTagFragment(hashtag); |
|||
NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); |
|||
} |
|||
|
|||
@Override |
|||
public void onLocationClick(final Media feedModel) { |
|||
final NavDirections action = CollectionPostsFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); |
|||
NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); |
|||
} |
|||
|
|||
@Override |
|||
public void onMentionClick(final String mention) { |
|||
navigateToProfile(mention.trim()); |
|||
} |
|||
|
|||
@Override |
|||
public void onNameClick(final Media feedModel, final View profilePicView) { |
|||
navigateToProfile("@" + feedModel.getUser().getUsername()); |
|||
} |
|||
|
|||
@Override |
|||
public void onProfilePicClick(final Media feedModel, final View profilePicView) { |
|||
navigateToProfile("@" + feedModel.getUser().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 View profilePicView, |
|||
final View mainPostImage, |
|||
final int position) { |
|||
final PostViewV2Fragment.Builder builder = PostViewV2Fragment |
|||
.builder(feedModel); |
|||
if (position >= 0) { |
|||
builder.setPosition(position); |
|||
} |
|||
if (!layoutPreferences.isAnimationDisabled()) { |
|||
builder.setSharedProfilePicElement(profilePicView) |
|||
.setSharedMainPostElement(mainPostImage); |
|||
} |
|||
builder.build().show(getChildFragmentManager(), "post_view"); |
|||
} |
|||
}; |
|||
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); |
|||
} |
|||
CollectionPostsFragment.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 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 = FragmentCollectionPostsBinding.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); |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
binding.posts.refresh(); |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
resetToolbar(); |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroyView() { |
|||
super.onDestroyView(); |
|||
resetToolbar(); |
|||
} |
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |
|||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
|||
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { |
|||
if (downloadFeedModel == null) return; |
|||
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition); |
|||
downloadFeedModel = null; |
|||
downloadChildPosition = -1; |
|||
return; |
|||
} |
|||
if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { |
|||
DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); |
|||
binding.posts.endSelection(); |
|||
} |
|||
} |
|||
|
|||
private void resetToolbar() { |
|||
fragmentActivity.resetToolbar(); |
|||
} |
|||
|
|||
private void init() { |
|||
if (getArguments() == null) return; |
|||
final CollectionPostsFragmentArgs fragmentArgs = CollectionPostsFragmentArgs.fromBundle(getArguments()); |
|||
savedCollection = fragmentArgs.getSavedCollection(); |
|||
setupToolbar(fragmentArgs.getTitleColor(), fragmentArgs.getBackgroundColor()); |
|||
setupPosts(); |
|||
} |
|||
|
|||
private void setupToolbar(final int titleColor, final int backgroundColor) { |
|||
if (savedCollection == null) { |
|||
return; |
|||
} |
|||
binding.cover.setTransitionName("collection-" + savedCollection.getId()); |
|||
fragmentActivity.setToolbar(binding.toolbar); |
|||
binding.collapsingToolbarLayout.setTitle(savedCollection.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(savedCollection.getCoverMedias() == null |
|||
? null |
|||
: savedCollection.getCoverMedias().get(0)); |
|||
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() { |
|||
binding.posts.setViewModelStoreOwner(this) |
|||
.setLifeCycleOwner(this) |
|||
.setPostFetchService(new SavedPostFetchService(0, PostItemType.COLLECTION, true, savedCollection.getId())) |
|||
.setLayoutPreferences(layoutPreferences) |
|||
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) |
|||
.setFeedItemCallback(feedItemCallback) |
|||
.setSelectionModeCallback(selectionModeCallback) |
|||
.init(); |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
private void updateSwipeRefreshState() { |
|||
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching()); |
|||
} |
|||
|
|||
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 -> { |
|||
layoutPreferences = preferences; |
|||
new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200); |
|||
}); |
|||
fragment.show(getChildFragmentManager(), "posts_layout_preferences"); |
|||
} |
|||
} |
@ -0,0 +1,161 @@ |
|||
package awais.instagrabber.fragments; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.util.Log; |
|||
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 android.widget.EditText; |
|||
import android.widget.Toast; |
|||
|
|||
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.fragment.FragmentNavigator; |
|||
import androidx.navigation.fragment.NavHostFragment; |
|||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.MainActivity; |
|||
import awais.instagrabber.adapters.SavedCollectionsAdapter; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.databinding.FragmentSavedCollectionsBinding; |
|||
import awais.instagrabber.repositories.responses.StoryStickerResponse; |
|||
import awais.instagrabber.repositories.responses.saved.CollectionsListResponse; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.viewmodels.SavedCollectionsViewModel; |
|||
import awais.instagrabber.webservices.ProfileService; |
|||
import awais.instagrabber.webservices.ServiceCallback; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { |
|||
private static final String TAG = "SavedCollectionsFragment"; |
|||
|
|||
private MainActivity fragmentActivity; |
|||
private CoordinatorLayout root; |
|||
private FragmentSavedCollectionsBinding binding; |
|||
private SavedCollectionsViewModel savedCollectionsViewModel; |
|||
private boolean shouldRefresh = true; |
|||
private ProfileService profileService; |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
fragmentActivity = (MainActivity) requireActivity(); |
|||
profileService = ProfileService.getInstance(); |
|||
setHasOptionsMenu(true); |
|||
} |
|||
|
|||
@Override |
|||
public View onCreateView(@NonNull final LayoutInflater inflater, |
|||
final ViewGroup container, |
|||
final Bundle savedInstanceState) { |
|||
if (root != null) { |
|||
shouldRefresh = false; |
|||
return root; |
|||
} |
|||
binding = FragmentSavedCollectionsBinding.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.saved_collection_menu, menu); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { |
|||
if (item.getItemId() == R.id.add) { |
|||
final Context context = getContext(); |
|||
final EditText input = new EditText(context); |
|||
new AlertDialog.Builder(context) |
|||
.setTitle(R.string.saved_create_collection) |
|||
.setView(input) |
|||
.setPositiveButton(R.string.confirm, (d, w) -> { |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
profileService.createCollection( |
|||
input.getText().toString(), |
|||
settingsHelper.getString(Constants.DEVICE_UUID), |
|||
CookieUtils.getUserIdFromCookie(cookie), |
|||
CookieUtils.getCsrfTokenFromCookie(cookie), |
|||
new ServiceCallback<String>() { |
|||
@Override |
|||
public void onSuccess(final String result) { |
|||
onRefresh(); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
Log.e(TAG, "Error creating collection", t); |
|||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); |
|||
} |
|||
}); |
|||
}) |
|||
.setNegativeButton(R.string.cancel, null) |
|||
.show(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
private void init() { |
|||
setupTopics(); |
|||
fetchTopics(null); |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
fetchTopics(null); |
|||
} |
|||
|
|||
public void setupTopics() { |
|||
savedCollectionsViewModel = new ViewModelProvider(fragmentActivity).get(SavedCollectionsViewModel.class); |
|||
binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2))); |
|||
final SavedCollectionsAdapter adapter = new SavedCollectionsAdapter((topicCluster, root, cover, title, titleColor, backgroundColor) -> { |
|||
final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() |
|||
.addSharedElement(cover, "collection-" + topicCluster.getId()); |
|||
final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections |
|||
.actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor); |
|||
NavHostFragment.findNavController(this).navigate(action, builder.build()); |
|||
}); |
|||
binding.topicsRecyclerView.setAdapter(adapter); |
|||
savedCollectionsViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); |
|||
} |
|||
|
|||
private void fetchTopics(final String maxId) { |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
profileService.fetchCollections(maxId, new ServiceCallback<CollectionsListResponse>() { |
|||
@Override |
|||
public void onSuccess(final CollectionsListResponse result) { |
|||
if (result == null) return; |
|||
savedCollectionsViewModel.getList().postValue(result.getItems()); |
|||
binding.swipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
Log.e(TAG, "onFailure", t); |
|||
binding.swipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
package awais.instagrabber.repositories.responses.saved; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class CollectionsListResponse { |
|||
private final boolean moreAvailable; |
|||
private final String nextMaxId; |
|||
private final String maxId; |
|||
private final String status; |
|||
// private final int numResults; |
|||
private final List<SavedCollection> items; |
|||
|
|||
public CollectionsListResponse(final boolean moreAvailable, |
|||
final String nextMaxId, |
|||
final String maxId, |
|||
final String status, |
|||
// final int numResults, |
|||
final List<SavedCollection> items) { |
|||
this.moreAvailable = moreAvailable; |
|||
this.nextMaxId = nextMaxId; |
|||
this.maxId = maxId; |
|||
this.status = status; |
|||
// this.numResults = numResults; |
|||
this.items = items; |
|||
} |
|||
|
|||
public boolean isMoreAvailable() { |
|||
return moreAvailable; |
|||
} |
|||
|
|||
public String getNextMaxId() { |
|||
return nextMaxId; |
|||
} |
|||
|
|||
public String getMaxId() { |
|||
return maxId; |
|||
} |
|||
|
|||
public String getStatus() { |
|||
return status; |
|||
} |
|||
|
|||
// public int getNumResults() { |
|||
// return numResults; |
|||
// } |
|||
|
|||
public List<SavedCollection> getItems() { |
|||
return items; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package awais.instagrabber.repositories.responses.saved; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.repositories.responses.Media; |
|||
|
|||
public class SavedCollection implements Serializable { |
|||
private final String collectionId; |
|||
private final String collectionName; |
|||
private final String collectionType; |
|||
private final int collectionMediacount; |
|||
private final List<Media> coverMediaList; |
|||
|
|||
public SavedCollection(final String collectionId, |
|||
final String collectionName, |
|||
final String collectionType, |
|||
final int collectionMediacount, |
|||
final List<Media> coverMediaList) { |
|||
this.collectionId = collectionId; |
|||
this.collectionName = collectionName; |
|||
this.collectionType = collectionType; |
|||
this.collectionMediacount = collectionMediacount; |
|||
this.coverMediaList = coverMediaList; |
|||
} |
|||
|
|||
public String getId() { |
|||
return collectionId; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return collectionName; |
|||
} |
|||
|
|||
public String getType() { |
|||
return collectionType; |
|||
} |
|||
|
|||
public int getMediaCount() { |
|||
return collectionMediacount; |
|||
} |
|||
|
|||
public List<Media> getCoverMedias() { |
|||
return coverMediaList; |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
package awais.instagrabber.viewmodels; |
|||
|
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.ViewModel; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.repositories.responses.saved.SavedCollection; |
|||
|
|||
public class SavedCollectionsViewModel extends ViewModel { |
|||
private MutableLiveData<List<SavedCollection>> list; |
|||
|
|||
public MutableLiveData<List<SavedCollection>> getList() { |
|||
if (list == null) { |
|||
list = new MutableLiveData<>(); |
|||
} |
|||
return list; |
|||
} |
|||
} |
@ -0,0 +1,58 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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> |
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
|||
|
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/topics_recycler_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
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> |
@ -0,0 +1,10 @@ |
|||
<?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/add" |
|||
android:icon="@drawable/ic_add" |
|||
android:title="@string/saved_create_collection" |
|||
app:showAsAction="always" /> |
|||
</menu> |
@ -0,0 +1,101 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<navigation 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:id="@+id/saved_nav_graph" |
|||
app:startDestination="@id/savedCollectionsFragment"> |
|||
|
|||
<action |
|||
android:id="@+id/action_global_hashTagFragment" |
|||
app:destination="@id/hashtag_nav_graph"> |
|||
<argument |
|||
android:name="hashtag" |
|||
app:argType="string" |
|||
app:nullable="false" /> |
|||
</action> |
|||
|
|||
<action |
|||
android:id="@+id/action_global_profileFragment" |
|||
app:destination="@id/profile_nav_graph"> |
|||
<argument |
|||
android:name="username" |
|||
app:argType="string" |
|||
app:nullable="true" /> |
|||
</action> |
|||
|
|||
<action |
|||
android:id="@+id/action_global_locationFragment" |
|||
app:destination="@id/location_nav_graph"> |
|||
<argument |
|||
android:name="locationId" |
|||
app:argType="long" /> |
|||
</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="long" /> |
|||
</action> |
|||
|
|||
<include app:graph="@navigation/likes_nav_graph" /> |
|||
|
|||
<action |
|||
android:id="@+id/action_global_likesViewerFragment" |
|||
app:destination="@id/likes_nav_graph"> |
|||
<argument |
|||
android:name="postId" |
|||
app:argType="string" |
|||
app:nullable="false" /> |
|||
<argument |
|||
android:name="isComment" |
|||
app:argType="boolean" |
|||
app:nullable="false" /> |
|||
</action> |
|||
|
|||
<action |
|||
android:id="@+id/action_global_notificationsViewerFragment" |
|||
app:destination="@id/notification_viewer_nav_graph"> |
|||
<argument |
|||
android:name="type" |
|||
app:argType="string" |
|||
app:nullable="false" /> |
|||
</action> |
|||
|
|||
<fragment |
|||
android:id="@+id/savedCollectionsFragment" |
|||
android:name="awais.instagrabber.fragments.SavedCollectionsFragment" |
|||
android:label="@string/saved" |
|||
tools:layout="@layout/fragment_saved_collections" > |
|||
<action |
|||
android:id="@+id/action_savedCollectionsFragment_to_collectionPostsFragment" |
|||
app:destination="@id/collectionPostsFragment" /> |
|||
</fragment> |
|||
<fragment |
|||
android:id="@+id/collectionPostsFragment" |
|||
android:name="awais.instagrabber.fragments.CollectionPostsFragment" |
|||
tools:layout="@layout/fragment_collection_posts"> |
|||
<argument |
|||
android:name="savedCollection" |
|||
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" /> |
|||
|
|||
<argument |
|||
android:name="titleColor" |
|||
app:argType="integer" /> |
|||
|
|||
<argument |
|||
android:name="backgroundColor" |
|||
app:argType="integer" /> |
|||
</fragment> |
|||
</navigation> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue