132 changed files with 9028 additions and 2010 deletions
-
24app/build.gradle
-
4app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
-
12app/src/main/java/awais/instagrabber/activities/Login.java
-
26app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
2app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
-
169app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java
-
4app/src/main/java/awais/instagrabber/adapters/MultiSelectListAdapter.java
-
152app/src/main/java/awais/instagrabber/adapters/PostViewAdapter.java
-
9app/src/main/java/awais/instagrabber/adapters/PostViewerChildAdapter.java
-
15app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java
-
153app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java
-
147app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java
-
2app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
-
479app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewerViewHolder.java
-
21app/src/main/java/awais/instagrabber/adapters/viewholder/SliderItemViewHolder.java
-
122app/src/main/java/awais/instagrabber/adapters/viewholder/SliderPhotoViewHolder.java
-
171app/src/main/java/awais/instagrabber/adapters/viewholder/SliderVideoViewHolder.java
-
36app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java
-
87app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedPhotoViewHolder.java
-
298app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java
-
162app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedVideoViewHolder.java
-
45app/src/main/java/awais/instagrabber/animations/ResizeAnimation.java
-
7app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
-
310app/src/main/java/awais/instagrabber/asyncs/DownloadV2AsyncTask.java
-
181app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
-
54app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
-
129app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
-
4app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
-
5app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java
-
124app/src/main/java/awais/instagrabber/asyncs/i/iPostFetcher.java
-
87app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
-
259app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
-
141app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java
-
21app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
-
302app/src/main/java/awais/instagrabber/customviews/SharedElementTransitionDialogFragment.java
-
203app/src/main/java/awais/instagrabber/customviews/VerticalDragHelper.java
-
18app/src/main/java/awais/instagrabber/customviews/VideoPlayerCallbackAdapter.java
-
373app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java
-
20app/src/main/java/awais/instagrabber/customviews/drawee/DefaultZoomableController.java
-
98app/src/main/java/awais/instagrabber/customviews/drawee/DraggableZoomableDraweeView.java
-
7app/src/main/java/awais/instagrabber/customviews/drawee/MultiZoomableControllerListener.java
-
2app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableController.java
-
44app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java
-
19app/src/main/java/awais/instagrabber/customviews/helpers/GridSpacingItemDecoration.java
-
50app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java
-
58app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoader.java
-
51app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoaderAtBottom.java
-
206app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java
-
219app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java
-
19app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
-
27app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
-
4app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
682app/src/main/java/awais/instagrabber/fragments/PostViewFragment.java
-
1245app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
24app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
-
512app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
68app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
5app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
-
7app/src/main/java/awais/instagrabber/models/BasePostModel.java
-
235app/src/main/java/awais/instagrabber/models/FeedModel.java
-
121app/src/main/java/awais/instagrabber/models/PostChild.java
-
14app/src/main/java/awais/instagrabber/models/PostModel.java
-
204app/src/main/java/awais/instagrabber/models/PostsLayoutPreferences.java
-
165app/src/main/java/awais/instagrabber/models/ViewerPostModel.java
-
10app/src/main/java/awais/instagrabber/models/ViewerPostModelWrapper.java
-
12app/src/main/java/awais/instagrabber/repositories/FeedRepository.java
-
29app/src/main/java/awais/instagrabber/repositories/responses/FeedFetchResponse.java
-
5app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
-
6app/src/main/java/awais/instagrabber/utils/Constants.java
-
265app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
-
1app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java
-
4app/src/main/java/awais/instagrabber/utils/NumberUtils.java
-
3app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
20app/src/main/java/awais/instagrabber/utils/TextUtils.java
-
12app/src/main/java/awais/instagrabber/utils/Utils.java
-
307app/src/main/java/awais/instagrabber/webservices/FeedService.java
-
115app/src/main/java/awais/instagrabber/webservices/StoriesService.java
-
382app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
-
18app/src/main/res/anim/dialog_anim_in.xml
-
19app/src/main/res/anim/dialog_anim_out.xml
-
3app/src/main/res/drawable/background_grey_ripple.xml
-
10app/src/main/res/drawable/ic_border_style_flipped_24.xml
-
11app/src/main/res/drawable/ic_checkbox_multiple_blank.xml
-
13app/src/main/res/drawable/ic_checkbox_multiple_blank_stroke.xml
-
10app/src/main/res/drawable/ic_class_24.xml
-
10app/src/main/res/drawable/ic_dashboard_24.xml
-
13app/src/main/res/drawable/ic_forward_5_24.xml
-
10app/src/main/res/drawable/ic_notes_24.xml
-
10app/src/main/res/drawable/ic_pause_24.xml
-
10app/src/main/res/drawable/ic_play_arrow_24.xml
-
10app/src/main/res/drawable/ic_play_circle_outline_24.xml
-
0app/src/main/res/drawable/ic_profile_24.xml
-
10app/src/main/res/drawable/ic_profile_40.xml
-
10app/src/main/res/drawable/ic_profile_48.xml
-
13app/src/main/res/drawable/ic_replay_5_24.xml
-
10app/src/main/res/drawable/ic_rounded_corner_24.xml
-
10app/src/main/res/drawable/ic_view_agenda_24.xml
-
11app/src/main/res/drawable/ic_view_grid_24.xml
-
8app/src/main/res/drawable/popup_background_exoplayer.xml
-
5app/src/main/res/drawable/rounder_corner_bg.xml
@ -0,0 +1,169 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
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 androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.adapters.viewholder.FeedGridItemViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
import awais.instagrabber.databinding.ItemFeedGridBinding; |
|||
import awais.instagrabber.databinding.ItemFeedPhotoBinding; |
|||
import awais.instagrabber.databinding.ItemFeedSliderBinding; |
|||
import awais.instagrabber.databinding.ItemFeedVideoBinding; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.ViewHolder> { |
|||
private static final String TAG = "FeedAdapterV2"; |
|||
|
|||
private PostsLayoutPreferences layoutPreferences; |
|||
private OnPostClickListener postClickListener; |
|||
private int lastAnimatedPosition; |
|||
|
|||
private final View.OnClickListener clickListener; |
|||
private final MentionClickListener mentionClickListener; |
|||
private final View.OnLongClickListener longClickListener = v -> { |
|||
final Object tag; |
|||
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) |
|||
Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption()); |
|||
return true; |
|||
}; |
|||
|
|||
|
|||
private static final DiffUtil.ItemCallback<FeedModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<FeedModel>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { |
|||
return oldItem.getPostId().equals(newItem.getPostId()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { |
|||
return oldItem.getPostId().equals(newItem.getPostId()); |
|||
} |
|||
}; |
|||
|
|||
public FeedAdapterV2(@NonNull final PostsLayoutPreferences layoutPreferences, |
|||
final View.OnClickListener clickListener, |
|||
final MentionClickListener mentionClickListener, |
|||
final OnPostClickListener postClickListener) { |
|||
super(DIFF_CALLBACK); |
|||
this.layoutPreferences = layoutPreferences; |
|||
this.clickListener = clickListener; |
|||
this.mentionClickListener = mentionClickListener; |
|||
this.postClickListener = postClickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final Context context = parent.getContext(); |
|||
final LayoutInflater layoutInflater = LayoutInflater.from(context); |
|||
switch (layoutPreferences.getType()) { |
|||
case LINEAR: |
|||
return getLinearViewHolder(parent, layoutInflater, viewType); |
|||
case GRID: |
|||
case STAGGERED_GRID: |
|||
default: |
|||
final ItemFeedGridBinding binding = ItemFeedGridBinding.inflate(layoutInflater, parent, false); |
|||
return new FeedGridItemViewHolder(binding); |
|||
} |
|||
} |
|||
|
|||
@NonNull |
|||
private RecyclerView.ViewHolder getLinearViewHolder(@NonNull final ViewGroup parent, |
|||
final LayoutInflater layoutInflater, |
|||
final int viewType) { |
|||
switch (MediaItemType.valueOf(viewType)) { |
|||
case MEDIA_TYPE_VIDEO: { |
|||
final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false); |
|||
return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener); |
|||
} |
|||
case MEDIA_TYPE_SLIDER: { |
|||
final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false); |
|||
return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener); |
|||
} |
|||
case MEDIA_TYPE_IMAGE: |
|||
default: { |
|||
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false); |
|||
return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, final int position) { |
|||
final FeedModel feedModel = getItem(position); |
|||
if (feedModel == null) return; |
|||
feedModel.setPosition(position); |
|||
switch (layoutPreferences.getType()) { |
|||
case LINEAR: |
|||
((FeedItemViewHolder) viewHolder).bind(feedModel, postClickListener); |
|||
break; |
|||
case GRID: |
|||
case STAGGERED_GRID: |
|||
default: |
|||
final boolean animate = position > lastAnimatedPosition; |
|||
((FeedGridItemViewHolder) viewHolder).bind(feedModel, layoutPreferences, postClickListener, false); |
|||
} |
|||
lastAnimatedPosition = position; |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
return getItem(position).getItemType().getId(); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewDetachedFromWindow(@NonNull final RecyclerView.ViewHolder viewHolder) { |
|||
switch (layoutPreferences.getType()) { |
|||
case LINEAR: |
|||
((FeedItemViewHolder) viewHolder).clearAnimation(); |
|||
break; |
|||
case GRID: |
|||
case STAGGERED_GRID: |
|||
default: |
|||
((FeedGridItemViewHolder) viewHolder).clearAnimation(); |
|||
} |
|||
} |
|||
|
|||
public void setLayoutPreferences(@NonNull final PostsLayoutPreferences layoutPreferences) { |
|||
this.layoutPreferences = layoutPreferences; |
|||
} |
|||
|
|||
// @Override |
|||
// public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) { |
|||
// super.onViewAttachedToWindow(holder); |
|||
// // Log.d(TAG, "attached holder: " + holder); |
|||
// if (!(holder instanceof FeedSliderViewHolder)) return; |
|||
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; |
|||
// feedSliderViewHolder.startPlayingVideo(); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) { |
|||
// super.onViewDetachedFromWindow(holder); |
|||
// // Log.d(TAG, "detached holder: " + holder); |
|||
// if (!(holder instanceof FeedSliderViewHolder)) return; |
|||
// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; |
|||
// feedSliderViewHolder.stopPlayingVideo(); |
|||
// } |
|||
|
|||
public interface OnPostClickListener { |
|||
void onPostClick(final FeedModel feedModel, |
|||
final View profilePicView, |
|||
final View mainPostImage); |
|||
} |
|||
} |
@ -1,77 +1,75 @@ |
|||
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 java.util.Arrays; |
|||
|
|||
import awais.instagrabber.adapters.viewholder.PostViewerViewHolder; |
|||
import awais.instagrabber.databinding.ItemFullPostViewBinding; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
|
|||
public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> { |
|||
private final OnPostViewChildViewClickListener clickListener; |
|||
private final OnPostCaptionLongClickListener longClickListener; |
|||
private final MentionClickListener mentionClickListener; |
|||
|
|||
private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem, |
|||
@NonNull final ViewerPostModelWrapper newItem) { |
|||
return oldItem.getPosition() == newItem.getPosition(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem, |
|||
@NonNull final ViewerPostModelWrapper newItem) { |
|||
return Arrays.equals(oldItem.getViewerPostModels(), newItem.getViewerPostModels()); |
|||
} |
|||
}; |
|||
|
|||
public PostViewAdapter(final OnPostViewChildViewClickListener clickListener, |
|||
final OnPostCaptionLongClickListener longClickListener, |
|||
final MentionClickListener mentionClickListener) { |
|||
super(diffCallback); |
|||
this.clickListener = clickListener; |
|||
this.longClickListener = longClickListener; |
|||
this.mentionClickListener = mentionClickListener; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) { |
|||
holder.stopPlayingVideo(); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, |
|||
final int viewType) { |
|||
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
final ItemFullPostViewBinding binding = ItemFullPostViewBinding |
|||
.inflate(layoutInflater, parent, false); |
|||
return new PostViewerViewHolder(binding); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) { |
|||
final ViewerPostModelWrapper item = getItem(position); |
|||
holder.bind(item, position, clickListener, longClickListener, mentionClickListener); |
|||
} |
|||
|
|||
public interface OnPostViewChildViewClickListener { |
|||
void onClick(View v, |
|||
ViewerPostModelWrapper viewerPostModelWrapper, |
|||
int postPosition, |
|||
int childPosition); |
|||
} |
|||
|
|||
public interface OnPostCaptionLongClickListener { |
|||
void onLongClick(String text); |
|||
} |
|||
} |
|||
// 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.PostViewerViewHolder; |
|||
// import awais.instagrabber.databinding.ItemFullPostViewBinding; |
|||
// import awais.instagrabber.interfaces.MentionClickListener; |
|||
// import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
// |
|||
// public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> { |
|||
// private final OnPostViewChildViewClickListener clickListener; |
|||
// private final OnPostCaptionLongClickListener longClickListener; |
|||
// private final MentionClickListener mentionClickListener; |
|||
// |
|||
// private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() { |
|||
// @Override |
|||
// public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem, |
|||
// @NonNull final ViewerPostModelWrapper newItem) { |
|||
// return oldItem.getPosition() == newItem.getPosition(); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem, |
|||
// @NonNull final ViewerPostModelWrapper newItem) { |
|||
// return oldItem.getViewerPostModels().equals(newItem.getViewerPostModels()); |
|||
// } |
|||
// }; |
|||
// |
|||
// public PostViewAdapter(final OnPostViewChildViewClickListener clickListener, |
|||
// final OnPostCaptionLongClickListener longClickListener, |
|||
// final MentionClickListener mentionClickListener) { |
|||
// super(diffCallback); |
|||
// this.clickListener = clickListener; |
|||
// this.longClickListener = longClickListener; |
|||
// this.mentionClickListener = mentionClickListener; |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) { |
|||
// holder.stopPlayingVideo(); |
|||
// } |
|||
// |
|||
// @NonNull |
|||
// @Override |
|||
// public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, |
|||
// final int viewType) { |
|||
// final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
// final ItemFullPostViewBinding binding = ItemFullPostViewBinding |
|||
// .inflate(layoutInflater, parent, false); |
|||
// return new PostViewerViewHolder(binding); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) { |
|||
// final ViewerPostModelWrapper item = getItem(position); |
|||
// holder.bind(item, position, clickListener, longClickListener, mentionClickListener); |
|||
// } |
|||
// |
|||
// public interface OnPostViewChildViewClickListener { |
|||
// void onClick(View v, |
|||
// ViewerPostModelWrapper viewerPostModelWrapper, |
|||
// int postPosition, |
|||
// int childPosition); |
|||
// } |
|||
// |
|||
// public interface OnPostCaptionLongClickListener { |
|||
// void onLongClick(String text); |
|||
// } |
|||
// } |
@ -0,0 +1,15 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback { |
|||
@Override |
|||
public void onThumbnailLoaded(final int position) {} |
|||
|
|||
@Override |
|||
public void onItemClicked(final int position) {} |
|||
|
|||
@Override |
|||
public void onPlayerPlay(final int position) {} |
|||
|
|||
@Override |
|||
public void onPlayerPause(final int position) {} |
|||
} |
@ -0,0 +1,153 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.view.LayoutInflater; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.DiffUtil; |
|||
import androidx.recyclerview.widget.ListAdapter; |
|||
|
|||
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder; |
|||
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder; |
|||
import awais.instagrabber.customviews.VerticalDragHelper; |
|||
import awais.instagrabber.databinding.ItemSliderPhotoBinding; |
|||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding; |
|||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; |
|||
import awais.instagrabber.models.PostChild; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
|
|||
public final class SliderItemsAdapter extends ListAdapter<PostChild, SliderItemViewHolder> { |
|||
|
|||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener; |
|||
private final SliderCallback sliderCallback; |
|||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding; |
|||
|
|||
private static final DiffUtil.ItemCallback<PostChild> DIFF_CALLBACK = new DiffUtil.ItemCallback<PostChild>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) { |
|||
return oldItem.getPostId().equals(newItem.getPostId()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) { |
|||
return oldItem.getPostId().equals(newItem.getPostId()); |
|||
} |
|||
}; |
|||
|
|||
public SliderItemsAdapter() { |
|||
this(null, null, null); |
|||
} |
|||
|
|||
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener, |
|||
final LayoutExoCustomControlsBinding controlsBinding, |
|||
final SliderCallback sliderCallback) { |
|||
super(DIFF_CALLBACK); |
|||
this.onVerticalDragListener = onVerticalDragListener; |
|||
this.sliderCallback = sliderCallback; |
|||
this.controlsBinding = controlsBinding; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public SliderItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); |
|||
final MediaItemType mediaItemType = MediaItemType.valueOf(viewType); |
|||
switch (mediaItemType) { |
|||
case MEDIA_TYPE_VIDEO: { |
|||
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false); |
|||
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding); |
|||
} |
|||
case MEDIA_TYPE_IMAGE: |
|||
default: |
|||
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false); |
|||
return new SliderPhotoViewHolder(binding, onVerticalDragListener); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final SliderItemViewHolder holder, final int position) { |
|||
final PostChild model = getItem(position); |
|||
holder.bind(model, position, sliderCallback); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
final PostChild viewerPostModel = getItem(position); |
|||
return viewerPostModel.getItemType().getId(); |
|||
} |
|||
|
|||
// @NonNull |
|||
// @Override |
|||
// public Object instantiateItem(@NonNull final ViewGroup container, final int position) { |
|||
// final Context context = container.getContext(); |
|||
// final ViewerPostModel sliderItem = sliderItems.get(position); |
|||
// |
|||
// if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { |
|||
// final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getThumbnailUrl(), sliderItem.getDisplayUrl()); |
|||
// container.addView(viewSwitcher); |
|||
// return viewSwitcher; |
|||
// } |
|||
// final GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(container.getResources()) |
|||
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) |
|||
// .build(); |
|||
// final SimpleDraweeView photoView = new SimpleDraweeView(context, hierarchy); |
|||
// photoView.setLayoutParams(layoutParams); |
|||
// final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(sliderItem.getDisplayUrl())) |
|||
// .setLocalThumbnailPreviewsEnabled(true) |
|||
// .setProgressiveRenderingEnabled(true) |
|||
// .build(); |
|||
// photoView.setImageRequest(imageRequest); |
|||
// container.addView(photoView); |
|||
// return photoView; |
|||
// } |
|||
|
|||
// @NonNull |
|||
// private ViewSwitcher createViewSwitcher(final Context context, |
|||
// final int position, |
|||
// final String thumbnailUrl, |
|||
// final String displayUrl) { |
|||
// |
|||
// final ViewSwitcher viewSwitcher = new ViewSwitcher(context); |
|||
// viewSwitcher.setLayoutParams(layoutParams); |
|||
// |
|||
// final FrameLayout frameLayout = new FrameLayout(context); |
|||
// frameLayout.setLayoutParams(layoutParams); |
|||
// |
|||
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources()) |
|||
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) |
|||
// .build(); |
|||
// final SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context, hierarchy); |
|||
// simpleDraweeView.setLayoutParams(layoutParams); |
|||
// simpleDraweeView.setImageURI(thumbnailUrl); |
|||
// frameLayout.addView(simpleDraweeView); |
|||
// |
|||
// final AppCompatImageView imageView = new AppCompatImageView(context); |
|||
// final int px = Utils.convertDpToPx(50); |
|||
// final FrameLayout.LayoutParams playButtonLayoutParams = new FrameLayout.LayoutParams(px, px); |
|||
// playButtonLayoutParams.gravity = Gravity.CENTER; |
|||
// imageView.setLayoutParams(playButtonLayoutParams); |
|||
// imageView.setImageResource(R.drawable.exo_icon_play); |
|||
// frameLayout.addView(imageView); |
|||
// |
|||
// viewSwitcher.addView(frameLayout); |
|||
// |
|||
// final PlayerView playerView = new PlayerView(context); |
|||
// viewSwitcher.addView(playerView); |
|||
// if (shouldAutoPlay && position == 0) { |
|||
// loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener); |
|||
// } else |
|||
// frameLayout.setOnClickListener(v -> loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener)); |
|||
// return viewSwitcher; |
|||
// } |
|||
|
|||
public interface SliderCallback { |
|||
void onThumbnailLoaded(int position); |
|||
|
|||
void onItemClicked(int position); |
|||
|
|||
void onPlayerPlay(int position); |
|||
|
|||
void onPlayerPause(int position); |
|||
} |
|||
} |
@ -0,0 +1,147 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.graphics.drawable.Animatable; |
|||
import android.net.Uri; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.view.animation.Animation; |
|||
import android.view.animation.AnimationUtils; |
|||
|
|||
import androidx.annotation.DimenRes; |
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.facebook.drawee.backends.pipeline.Fresco; |
|||
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; |
|||
import com.facebook.drawee.controller.BaseControllerListener; |
|||
import com.facebook.imagepipeline.image.ImageInfo; |
|||
import com.facebook.imagepipeline.request.ImageRequest; |
|||
import com.facebook.imagepipeline.request.ImageRequestBuilder; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.FeedAdapterV2; |
|||
import awais.instagrabber.databinding.ItemFeedGridBinding; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.PostChild; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
|
|||
import static awais.instagrabber.models.PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID; |
|||
|
|||
public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { |
|||
private final ItemFeedGridBinding binding; |
|||
|
|||
public FeedGridItemViewHolder(@NonNull final ItemFeedGridBinding binding) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
// for rounded borders (clip view to background shape) |
|||
// |
|||
} |
|||
|
|||
public void bind(@NonNull final FeedModel feedModel, |
|||
@NonNull final PostsLayoutPreferences layoutPreferences, |
|||
final FeedAdapterV2.OnPostClickListener postClickListener, |
|||
final boolean animate) { |
|||
if (postClickListener != null) { |
|||
itemView.setOnClickListener(v -> postClickListener.onPostClick(feedModel, binding.profilePic, binding.postImage)); |
|||
} |
|||
itemView.setClipToOutline(layoutPreferences.getHasRoundedCorners()); |
|||
if (layoutPreferences.getType() == STAGGERED_GRID) { |
|||
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight(); |
|||
binding.postImage.setAspectRatio(aspectRatio); |
|||
} else { |
|||
binding.postImage.setAspectRatio(1); |
|||
} |
|||
if (layoutPreferences.isAvatarVisible()) { |
|||
binding.profilePic.setVisibility(View.VISIBLE); |
|||
binding.profilePic.setImageURI(feedModel.getProfileModel().getSdProfilePic()); |
|||
final ViewGroup.LayoutParams layoutParams = binding.profilePic.getLayoutParams(); |
|||
@DimenRes final int dimenRes; |
|||
switch (layoutPreferences.getProfilePicSize()) { |
|||
case SMALL: |
|||
dimenRes = R.dimen.profile_pic_size_small; |
|||
break; |
|||
case TINY: |
|||
dimenRes = R.dimen.profile_pic_size_tiny; |
|||
break; |
|||
default: |
|||
case REGULAR: |
|||
dimenRes = R.dimen.profile_pic_size_regular; |
|||
break; |
|||
} |
|||
final int dimensionPixelSize = itemView.getResources().getDimensionPixelSize(dimenRes); |
|||
layoutParams.width = dimensionPixelSize; |
|||
layoutParams.height = dimensionPixelSize; |
|||
binding.profilePic.requestLayout(); |
|||
} else { |
|||
binding.profilePic.setVisibility(View.GONE); |
|||
} |
|||
if (layoutPreferences.isNameVisible()) { |
|||
binding.name.setVisibility(View.VISIBLE); |
|||
binding.name.setText(feedModel.getProfileModel().getName()); |
|||
} else { |
|||
binding.name.setVisibility(View.GONE); |
|||
} |
|||
String thumbnailUrl = null; |
|||
final int typeIconRes; |
|||
switch (feedModel.getItemType()) { |
|||
case MEDIA_TYPE_IMAGE: |
|||
typeIconRes = -1; |
|||
thumbnailUrl = feedModel.getThumbnailUrl(); |
|||
break; |
|||
case MEDIA_TYPE_VIDEO: |
|||
thumbnailUrl = feedModel.getThumbnailUrl(); |
|||
typeIconRes = R.drawable.exo_icon_play; |
|||
break; |
|||
case MEDIA_TYPE_SLIDER: |
|||
final List<PostChild> sliderItems = feedModel.getSliderItems(); |
|||
if (sliderItems != null) { |
|||
thumbnailUrl = sliderItems.get(0).getThumbnailUrl(); |
|||
} |
|||
typeIconRes = R.drawable.ic_checkbox_multiple_blank_stroke; |
|||
break; |
|||
default: |
|||
typeIconRes = -1; |
|||
thumbnailUrl = null; |
|||
} |
|||
if (TextUtils.isEmpty(thumbnailUrl)) { |
|||
binding.postImage.setController(null); |
|||
return; |
|||
} |
|||
if (typeIconRes <= 0) { |
|||
binding.typeIcon.setVisibility(View.GONE); |
|||
} else { |
|||
binding.typeIcon.setVisibility(View.VISIBLE); |
|||
binding.typeIcon.setImageResource(typeIconRes); |
|||
} |
|||
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)) |
|||
.setLocalThumbnailPreviewsEnabled(true) |
|||
.setProgressiveRenderingEnabled(true) |
|||
.build(); |
|||
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder() |
|||
.setImageRequest(requestBuilder) |
|||
.setOldController(binding.postImage.getController()); |
|||
if (animate) { |
|||
final BaseControllerListener<ImageInfo> imageListener = new BaseControllerListener<ImageInfo>() { |
|||
@Override |
|||
public void onFinalImageSet(final String id, final ImageInfo imageInfo, final Animatable animatable) { |
|||
setAnimation(binding.getRoot()); |
|||
} |
|||
}; |
|||
builder.setControllerListener(imageListener); |
|||
} |
|||
binding.postImage.setController(builder.build()); |
|||
} |
|||
|
|||
private void setAnimation(View viewToAnimate) { |
|||
final Animation animation = AnimationUtils.loadAnimation(viewToAnimate.getContext(), android.R.anim.fade_in); |
|||
animation.setDuration(300); |
|||
viewToAnimate.startAnimation(animation); |
|||
} |
|||
|
|||
public void clearAnimation() { |
|||
binding.getRoot().clearAnimation(); |
|||
} |
|||
} |
@ -1,239 +1,240 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.content.res.ColorStateList; |
|||
import android.content.res.Resources; |
|||
import android.util.Log; |
|||
import android.view.View; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.core.view.ViewCompat; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
import androidx.viewpager2.widget.ViewPager2; |
|||
|
|||
import com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.ui.PlayerView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener; |
|||
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; |
|||
import awais.instagrabber.adapters.PostViewerChildAdapter; |
|||
import awais.instagrabber.databinding.ItemFullPostViewBinding; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public class PostViewerViewHolder extends RecyclerView.ViewHolder { |
|||
private static final String TAG = "PostViewerViewHolder"; |
|||
|
|||
private final ItemFullPostViewBinding binding; |
|||
private int currentChildPosition; |
|||
|
|||
public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
binding.topPanel.viewStoryPost.setVisibility(View.GONE); |
|||
} |
|||
|
|||
public void bind(final ViewerPostModelWrapper wrapper, |
|||
final int position, |
|||
final OnPostViewChildViewClickListener clickListener, |
|||
final OnPostCaptionLongClickListener longClickListener, |
|||
final MentionClickListener mentionClickListener) { |
|||
if (wrapper == null) return; |
|||
final ViewerPostModel[] items = wrapper.getViewerPostModels(); |
|||
if (items == null || items.length <= 0) return; |
|||
if (items[0] == null) return; |
|||
final PostViewerChildAdapter adapter = new PostViewerChildAdapter(); |
|||
binding.mediaViewPager.setAdapter(adapter); |
|||
final ViewerPostModel firstPost = items[0]; |
|||
setPostInfo(firstPost, mentionClickListener); |
|||
setMediaItems(items, adapter); |
|||
setupListeners(wrapper, |
|||
position, |
|||
clickListener, |
|||
longClickListener, |
|||
mentionClickListener, |
|||
firstPost.getLocation()); |
|||
} |
|||
|
|||
private void setPostInfo(final ViewerPostModel firstPost, |
|||
final MentionClickListener mentionClickListener) { |
|||
final ProfileModel profileModel = firstPost.getProfileModel(); |
|||
if (profileModel == null) return; |
|||
binding.topPanel.title.setText(profileModel.getUsername()); |
|||
final String locationName = firstPost.getLocationName(); |
|||
if (!TextUtils.isEmpty(locationName)) { |
|||
binding.topPanel.location.setVisibility(View.VISIBLE); |
|||
binding.topPanel.location.setText(locationName); |
|||
} else binding.topPanel.location.setVisibility(View.GONE); |
|||
binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic()); |
|||
binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount())); |
|||
final CharSequence postCaption = firstPost.getPostCaption(); |
|||
if (TextUtils.hasMentions(postCaption)) { |
|||
binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener); |
|||
binding.bottomPanel.viewerCaption |
|||
.setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE); |
|||
} else { |
|||
binding.bottomPanel.viewerCaption.setMentionClickListener(null); |
|||
binding.bottomPanel.viewerCaption.setText(postCaption); |
|||
} |
|||
binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate()); |
|||
setupLikes(firstPost); |
|||
setupSave(firstPost); |
|||
} |
|||
|
|||
private void setupLikes(final ViewerPostModel firstPost) { |
|||
final boolean liked = firstPost.getLike(); |
|||
final long likeCount = firstPost.getLikes(); |
|||
final Resources resources = itemView.getContext().getResources(); |
|||
if (liked) { |
|||
final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount)); |
|||
binding.btnLike.setText(unlikeString); |
|||
ViewCompat.setBackgroundTintList(binding.btnLike, |
|||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background))); |
|||
} else { |
|||
final String likeString = resources.getString(R.string.like, String.valueOf(likeCount)); |
|||
binding.btnLike.setText(likeString); |
|||
ViewCompat.setBackgroundTintList(binding.btnLike, |
|||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background))); |
|||
} |
|||
} |
|||
|
|||
private void setupSave(final ViewerPostModel firstPost) { |
|||
final boolean saved = firstPost.getBookmark(); |
|||
if (saved) { |
|||
binding.btnBookmark.setText(R.string.unbookmark); |
|||
ViewCompat.setBackgroundTintList(binding.btnBookmark, |
|||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background))); |
|||
} else { |
|||
binding.btnBookmark.setText(R.string.bookmark); |
|||
ViewCompat.setBackgroundTintList( |
|||
binding.btnBookmark, |
|||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background))); |
|||
} |
|||
} |
|||
|
|||
private void setupListeners(final ViewerPostModelWrapper wrapper, |
|||
final int position, |
|||
final OnPostViewChildViewClickListener clickListener, |
|||
final OnPostCaptionLongClickListener longClickListener, |
|||
final MentionClickListener mentionClickListener, |
|||
final String location) { |
|||
final View.OnClickListener onClickListener = v -> clickListener |
|||
.onClick(v, wrapper, position, currentChildPosition); |
|||
binding.bottomPanel.btnComments.setOnClickListener(onClickListener); |
|||
binding.topPanel.title.setOnClickListener(onClickListener); |
|||
binding.topPanel.ivProfilePic.setOnClickListener(onClickListener); |
|||
binding.bottomPanel.btnDownload.setOnClickListener(onClickListener); |
|||
binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener); |
|||
binding.btnLike.setOnClickListener(onClickListener); |
|||
binding.btnBookmark.setOnClickListener(onClickListener); |
|||
binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> { |
|||
longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString()); |
|||
return true; |
|||
}); |
|||
if (!TextUtils.isEmpty(location)) { |
|||
binding.topPanel.location.setOnClickListener(v -> mentionClickListener |
|||
.onClick(binding.topPanel.location, location, false, true)); |
|||
} |
|||
} |
|||
|
|||
private void setMediaItems(final ViewerPostModel[] items, |
|||
final PostViewerChildAdapter adapter) { |
|||
final List<ViewerPostModel> filteredList = new ArrayList<>(); |
|||
for (final ViewerPostModel model : items) { |
|||
final MediaItemType itemType = model.getItemType(); |
|||
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) { |
|||
filteredList.add(model); |
|||
} |
|||
} |
|||
binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE); |
|||
final String counter = "1/" + filteredList.size(); |
|||
binding.mediaCounter.setText(counter); |
|||
adapter.submitList(filteredList); |
|||
binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { |
|||
@Override |
|||
public void onPageSelected(final int position) { |
|||
if (filteredList.size() <= 0 || position >= filteredList.size()) return; |
|||
currentChildPosition = position; |
|||
final String counter = (position + 1) + "/" + filteredList.size(); |
|||
binding.mediaCounter.setText(counter); |
|||
final ViewerPostModel viewerPostModel = filteredList.get(position); |
|||
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { |
|||
setVideoDetails(viewerPostModel); |
|||
setVolumeListener(position); |
|||
return; |
|||
} |
|||
setImageDetails(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void setVolumeListener(final int position) { |
|||
binding.bottomPanel.btnMute.setOnClickListener(v -> { |
|||
try { |
|||
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager |
|||
.getChildAt(0)).findViewHolderForAdapterPosition(position); |
|||
if (viewHolder != null) { |
|||
final View itemView = viewHolder.itemView; |
|||
if (itemView instanceof PlayerView) { |
|||
final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView) |
|||
.getPlayer(); |
|||
if (player == null) return; |
|||
final float vol = player.getVolume() == 0f ? 1f : 0f; |
|||
player.setVolume(vol); |
|||
binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 |
|||
: R.drawable.ic_volume_off_24); |
|||
Utils.sessionVolumeFull = vol == 1f; |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error", e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void setImageDetails() { |
|||
binding.bottomPanel.btnMute.setVisibility(View.GONE); |
|||
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); |
|||
} |
|||
|
|||
private void setVideoDetails(final ViewerPostModel viewerPostModel) { |
|||
binding.bottomPanel.btnMute.setVisibility(View.VISIBLE); |
|||
final long videoViews = viewerPostModel.getVideoViews(); |
|||
if (videoViews < 0) { |
|||
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); |
|||
return; |
|||
} |
|||
binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews)); |
|||
binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
public void stopPlayingVideo() { |
|||
try { |
|||
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager |
|||
.getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition); |
|||
if (viewHolder != null) { |
|||
final View itemView = viewHolder.itemView; |
|||
if (itemView instanceof PlayerView) { |
|||
final Player player = ((PlayerView) itemView).getPlayer(); |
|||
if (player != null) { |
|||
player.setPlayWhenReady(false); |
|||
} |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "Error", e); |
|||
} |
|||
} |
|||
} |
|||
// package awais.instagrabber.adapters.viewholder; |
|||
// |
|||
// import android.content.res.ColorStateList; |
|||
// import android.content.res.Resources; |
|||
// import android.util.Log; |
|||
// import android.view.View; |
|||
// import android.widget.TextView; |
|||
// |
|||
// import androidx.annotation.NonNull; |
|||
// import androidx.core.content.ContextCompat; |
|||
// import androidx.core.view.ViewCompat; |
|||
// import androidx.recyclerview.widget.RecyclerView; |
|||
// import androidx.viewpager2.widget.ViewPager2; |
|||
// |
|||
// import com.google.android.exoplayer2.Player; |
|||
// import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
// import com.google.android.exoplayer2.ui.PlayerView; |
|||
// |
|||
// import java.util.ArrayList; |
|||
// import java.util.List; |
|||
// |
|||
// import awais.instagrabber.R; |
|||
// import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener; |
|||
// import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; |
|||
// import awais.instagrabber.adapters.PostViewerChildAdapter; |
|||
// import awais.instagrabber.databinding.ItemFullPostViewBinding; |
|||
// import awais.instagrabber.interfaces.MentionClickListener; |
|||
// import awais.instagrabber.models.PostChild; |
|||
// import awais.instagrabber.models.ProfileModel; |
|||
// import awais.instagrabber.models.ViewerPostModel; |
|||
// import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
// import awais.instagrabber.models.enums.MediaItemType; |
|||
// import awais.instagrabber.utils.TextUtils; |
|||
// import awais.instagrabber.utils.Utils; |
|||
// |
|||
// public class PostViewerViewHolder extends RecyclerView.ViewHolder { |
|||
// private static final String TAG = "PostViewerViewHolder"; |
|||
// |
|||
// private final ItemFullPostViewBinding binding; |
|||
// private int currentChildPosition; |
|||
// |
|||
// public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) { |
|||
// super(binding.getRoot()); |
|||
// this.binding = binding; |
|||
// binding.topPanel.viewStoryPost.setVisibility(View.GONE); |
|||
// } |
|||
// |
|||
// public void bind(final ViewerPostModelWrapper wrapper, |
|||
// final int position, |
|||
// final OnPostViewChildViewClickListener clickListener, |
|||
// final OnPostCaptionLongClickListener longClickListener, |
|||
// final MentionClickListener mentionClickListener) { |
|||
// if (wrapper == null) return; |
|||
// final List<PostChild> items = wrapper.getViewerPostModels(); |
|||
// if (items == null || items.size() == 0) return; |
|||
// if (items.get(0) == null) return; |
|||
// final PostViewerChildAdapter adapter = new PostViewerChildAdapter(); |
|||
// binding.mediaViewPager.setAdapter(adapter); |
|||
// final PostChild firstPost = items.get(0); |
|||
// setPostInfo(firstPost, mentionClickListener); |
|||
// setMediaItems(items, adapter); |
|||
// setupListeners(wrapper, |
|||
// position, |
|||
// clickListener, |
|||
// longClickListener, |
|||
// mentionClickListener, |
|||
// firstPost.getLocation()); |
|||
// } |
|||
// |
|||
// private void setPostInfo(final PostChild firstPost, |
|||
// final MentionClickListener mentionClickListener) { |
|||
// final ProfileModel profileModel = firstPost.getProfileModel(); |
|||
// if (profileModel == null) return; |
|||
// binding.topPanel.title.setText(profileModel.getUsername()); |
|||
// final String locationName = firstPost.getLocationName(); |
|||
// if (!TextUtils.isEmpty(locationName)) { |
|||
// binding.topPanel.location.setVisibility(View.VISIBLE); |
|||
// binding.topPanel.location.setText(locationName); |
|||
// } else binding.topPanel.location.setVisibility(View.GONE); |
|||
// binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic()); |
|||
// binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount())); |
|||
// final CharSequence postCaption = firstPost.getPostCaption(); |
|||
// if (TextUtils.hasMentions(postCaption)) { |
|||
// binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener); |
|||
// binding.bottomPanel.viewerCaption |
|||
// .setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE); |
|||
// } else { |
|||
// binding.bottomPanel.viewerCaption.setMentionClickListener(null); |
|||
// binding.bottomPanel.viewerCaption.setText(postCaption); |
|||
// } |
|||
// binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate()); |
|||
// setupLikes(firstPost); |
|||
// setupSave(firstPost); |
|||
// } |
|||
// |
|||
// private void setupLikes(final ViewerPostModel firstPost) { |
|||
// final boolean liked = firstPost.getLike(); |
|||
// final long likeCount = firstPost.getLikes(); |
|||
// final Resources resources = itemView.getContext().getResources(); |
|||
// if (liked) { |
|||
// final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount)); |
|||
// binding.btnLike.setText(unlikeString); |
|||
// ViewCompat.setBackgroundTintList(binding.btnLike, |
|||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background))); |
|||
// } else { |
|||
// final String likeString = resources.getString(R.string.like, String.valueOf(likeCount)); |
|||
// binding.btnLike.setText(likeString); |
|||
// ViewCompat.setBackgroundTintList(binding.btnLike, |
|||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background))); |
|||
// } |
|||
// } |
|||
// |
|||
// private void setupSave(final ViewerPostModel firstPost) { |
|||
// final boolean saved = firstPost.isSaved(); |
|||
// if (saved) { |
|||
// binding.btnBookmark.setText(R.string.unbookmark); |
|||
// ViewCompat.setBackgroundTintList(binding.btnBookmark, |
|||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background))); |
|||
// } else { |
|||
// binding.btnBookmark.setText(R.string.bookmark); |
|||
// ViewCompat.setBackgroundTintList( |
|||
// binding.btnBookmark, |
|||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background))); |
|||
// } |
|||
// } |
|||
// |
|||
// private void setupListeners(final ViewerPostModelWrapper wrapper, |
|||
// final int position, |
|||
// final OnPostViewChildViewClickListener clickListener, |
|||
// final OnPostCaptionLongClickListener longClickListener, |
|||
// final MentionClickListener mentionClickListener, |
|||
// final String location) { |
|||
// final View.OnClickListener onClickListener = v -> clickListener |
|||
// .onClick(v, wrapper, position, currentChildPosition); |
|||
// binding.bottomPanel.btnComments.setOnClickListener(onClickListener); |
|||
// binding.topPanel.title.setOnClickListener(onClickListener); |
|||
// binding.topPanel.ivProfilePic.setOnClickListener(onClickListener); |
|||
// binding.bottomPanel.btnDownload.setOnClickListener(onClickListener); |
|||
// binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener); |
|||
// binding.btnLike.setOnClickListener(onClickListener); |
|||
// binding.btnBookmark.setOnClickListener(onClickListener); |
|||
// binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> { |
|||
// longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString()); |
|||
// return true; |
|||
// }); |
|||
// if (!TextUtils.isEmpty(location)) { |
|||
// binding.topPanel.location.setOnClickListener(v -> mentionClickListener |
|||
// .onClick(binding.topPanel.location, location, false, true)); |
|||
// } |
|||
// } |
|||
// |
|||
// private void setMediaItems(final List<ViewerPostModel> items, |
|||
// final PostViewerChildAdapter adapter) { |
|||
// final List<ViewerPostModel> filteredList = new ArrayList<>(); |
|||
// for (final ViewerPostModel model : items) { |
|||
// final MediaItemType itemType = model.getItemType(); |
|||
// if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) { |
|||
// filteredList.add(model); |
|||
// } |
|||
// } |
|||
// binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE); |
|||
// final String counter = "1/" + filteredList.size(); |
|||
// binding.mediaCounter.setText(counter); |
|||
// adapter.submitList(filteredList); |
|||
// binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { |
|||
// @Override |
|||
// public void onPageSelected(final int position) { |
|||
// if (filteredList.size() <= 0 || position >= filteredList.size()) return; |
|||
// currentChildPosition = position; |
|||
// final String counter = (position + 1) + "/" + filteredList.size(); |
|||
// binding.mediaCounter.setText(counter); |
|||
// final ViewerPostModel viewerPostModel = filteredList.get(position); |
|||
// if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { |
|||
// setVideoDetails(viewerPostModel); |
|||
// setVolumeListener(position); |
|||
// return; |
|||
// } |
|||
// setImageDetails(); |
|||
// } |
|||
// }); |
|||
// } |
|||
// |
|||
// private void setVolumeListener(final int position) { |
|||
// binding.bottomPanel.btnMute.setOnClickListener(v -> { |
|||
// try { |
|||
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager |
|||
// .getChildAt(0)).findViewHolderForAdapterPosition(position); |
|||
// if (viewHolder != null) { |
|||
// final View itemView = viewHolder.itemView; |
|||
// if (itemView instanceof PlayerView) { |
|||
// final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView) |
|||
// .getPlayer(); |
|||
// if (player == null) return; |
|||
// final float vol = player.getVolume() == 0f ? 1f : 0f; |
|||
// player.setVolume(vol); |
|||
// binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 |
|||
// : R.drawable.ic_volume_off_24); |
|||
// Utils.sessionVolumeFull = vol == 1f; |
|||
// } |
|||
// } |
|||
// } catch (Exception e) { |
|||
// Log.e(TAG, "Error", e); |
|||
// } |
|||
// }); |
|||
// } |
|||
// |
|||
// private void setImageDetails() { |
|||
// binding.bottomPanel.btnMute.setVisibility(View.GONE); |
|||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); |
|||
// } |
|||
// |
|||
// private void setVideoDetails(final ViewerPostModel viewerPostModel) { |
|||
// binding.bottomPanel.btnMute.setVisibility(View.VISIBLE); |
|||
// final long videoViews = viewerPostModel.getVideoViews(); |
|||
// if (videoViews < 0) { |
|||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); |
|||
// return; |
|||
// } |
|||
// binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews)); |
|||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE); |
|||
// } |
|||
// |
|||
// public void stopPlayingVideo() { |
|||
// try { |
|||
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager |
|||
// .getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition); |
|||
// if (viewHolder != null) { |
|||
// final View itemView = viewHolder.itemView; |
|||
// if (itemView instanceof PlayerView) { |
|||
// final Player player = ((PlayerView) itemView).getPlayer(); |
|||
// if (player != null) { |
|||
// player.setPlayWhenReady(false); |
|||
// } |
|||
// } |
|||
// } |
|||
// } catch (Exception e) { |
|||
// Log.e(TAG, "Error", e); |
|||
// } |
|||
// } |
|||
// } |
@ -0,0 +1,21 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.adapters.SliderItemsAdapter; |
|||
import awais.instagrabber.models.PostChild; |
|||
|
|||
public abstract class SliderItemViewHolder extends RecyclerView.ViewHolder { |
|||
private static final String TAG = "FeedSliderItemViewHolder"; |
|||
|
|||
public SliderItemViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
} |
|||
|
|||
public abstract void bind(final PostChild model, |
|||
final int position, |
|||
final SliderItemsAdapter.SliderCallback sliderCallback); |
|||
} |
@ -0,0 +1,122 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.graphics.drawable.Animatable; |
|||
import android.net.Uri; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import com.facebook.drawee.backends.pipeline.Fresco; |
|||
import com.facebook.drawee.controller.BaseControllerListener; |
|||
import com.facebook.imagepipeline.image.ImageInfo; |
|||
import com.facebook.imagepipeline.request.ImageRequest; |
|||
import com.facebook.imagepipeline.request.ImageRequestBuilder; |
|||
|
|||
import awais.instagrabber.adapters.SliderItemsAdapter; |
|||
import awais.instagrabber.customviews.VerticalDragHelper; |
|||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController; |
|||
import awais.instagrabber.databinding.ItemSliderPhotoBinding; |
|||
import awais.instagrabber.models.PostChild; |
|||
|
|||
public class SliderPhotoViewHolder extends SliderItemViewHolder { |
|||
private static final String TAG = "FeedSliderPhotoViewHolder"; |
|||
|
|||
private final ItemSliderPhotoBinding binding; |
|||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener; |
|||
|
|||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding, |
|||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
this.onVerticalDragListener = onVerticalDragListener; |
|||
} |
|||
|
|||
public void bind(@NonNull final PostChild model, |
|||
final int position, |
|||
final SliderItemsAdapter.SliderCallback sliderCallback) { |
|||
final ImageRequest requestBuilder = ImageRequestBuilder |
|||
.newBuilderWithSource(Uri.parse(model.getDisplayUrl())) |
|||
.setLocalThumbnailPreviewsEnabled(true) |
|||
.build(); |
|||
binding.getRoot() |
|||
.setController(Fresco.newDraweeControllerBuilder() |
|||
.setImageRequest(requestBuilder) |
|||
.setControllerListener(new BaseControllerListener<ImageInfo>() { |
|||
@Override |
|||
public void onFailure(final String id, final Throwable throwable) { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onThumbnailLoaded(position); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFinalImageSet(final String id, |
|||
final ImageInfo imageInfo, |
|||
final Animatable animatable) { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onThumbnailLoaded(position); |
|||
} |
|||
} |
|||
}) |
|||
.setLowResImageRequest(ImageRequest.fromUri(model.getThumbnailUrl())) |
|||
.build()); |
|||
binding.getRoot().setOnClickListener(v -> { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onItemClicked(position); |
|||
} |
|||
}); |
|||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance(); |
|||
zoomableController.setMaxScaleFactor(3f); |
|||
binding.getRoot().setZoomableController(zoomableController); |
|||
if (onVerticalDragListener != null) { |
|||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener); |
|||
} |
|||
} |
|||
|
|||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) { |
|||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams(); |
|||
// final int deviceWidth = Utils.displayMetrics.widthPixels; |
|||
// final int spanWidth = deviceWidth / spanCount; |
|||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth()); |
|||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth; |
|||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight; |
|||
// if (animate) { |
|||
// Animation animation = AnimationUtils.expand( |
|||
// binding.imageViewer, |
|||
// layoutParams.width, |
|||
// layoutParams.height, |
|||
// width, |
|||
// height, |
|||
// new Animation.AnimationListener() { |
|||
// @Override |
|||
// public void onAnimationStart(final Animation animation) { |
|||
// showOrHideDetails(spanCount); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onAnimationEnd(final Animation animation) { |
|||
// // showOrHideDetails(spanCount); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onAnimationRepeat(final Animation animation) { |
|||
// |
|||
// } |
|||
// }); |
|||
// binding.imageViewer.startAnimation(animation); |
|||
// } else { |
|||
// layoutParams.width = width; |
|||
// layoutParams.height = height; |
|||
// binding.imageViewer.requestLayout(); |
|||
// } |
|||
// } |
|||
// |
|||
// private void showOrHideDetails(final int spanCount) { |
|||
// if (spanCount == 1) { |
|||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE); |
|||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE); |
|||
// } else { |
|||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE); |
|||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE); |
|||
// } |
|||
// } |
|||
} |
@ -0,0 +1,171 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import awais.instagrabber.adapters.SliderItemsAdapter; |
|||
import awais.instagrabber.customviews.VerticalDragHelper; |
|||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter; |
|||
import awais.instagrabber.customviews.VideoPlayerViewHelper; |
|||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding; |
|||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; |
|||
import awais.instagrabber.models.PostChild; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.NumberUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class SliderVideoViewHolder extends SliderItemViewHolder { |
|||
private static final String TAG = "SliderVideoViewHolder"; |
|||
|
|||
private final LayoutVideoPlayerWithThumbnailBinding binding; |
|||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding; |
|||
private VideoPlayerViewHelper videoPlayerViewHelper; |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding, |
|||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener, |
|||
final LayoutExoCustomControlsBinding controlsBinding) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
this.controlsBinding = controlsBinding; |
|||
final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent); |
|||
final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView); |
|||
thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener); |
|||
playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener); |
|||
binding.thumbnailParent.setOnTouchListener((v, event) -> { |
|||
final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event); |
|||
if (onDragTouch) { |
|||
return true; |
|||
} |
|||
return thumbnailVerticalDragHelper.onGestureTouchEvent(event); |
|||
}); |
|||
binding.playerView.setOnTouchListener((v, event) -> { |
|||
final boolean onDragTouch = playerVerticalDragHelper.onDragTouch(event); |
|||
if (onDragTouch) { |
|||
return true; |
|||
} |
|||
return playerVerticalDragHelper.onGestureTouchEvent(event); |
|||
}); |
|||
} |
|||
|
|||
public void bind(@NonNull final PostChild model, |
|||
final int position, |
|||
final SliderItemsAdapter.SliderCallback sliderCallback) { |
|||
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; |
|||
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() { |
|||
@Override |
|||
public void onThumbnailLoaded() { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onThumbnailLoaded(position); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPlayerViewLoaded() { |
|||
// binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE); |
|||
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams(); |
|||
final int requiredWidth = Utils.displayMetrics.widthPixels; |
|||
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, model.getHeight(), model.getWidth()); |
|||
layoutParams.width = requiredWidth; |
|||
layoutParams.height = resultingHeight; |
|||
binding.playerView.requestLayout(); |
|||
// setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol); |
|||
} |
|||
|
|||
@Override |
|||
public void onPlay() { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onPlayerPlay(position); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onPlayerPause(position); |
|||
} |
|||
} |
|||
}; |
|||
final float aspectRatio = (float) model.getWidth() / model.getHeight(); |
|||
videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(), |
|||
binding, |
|||
model.getDisplayUrl(), |
|||
vol, |
|||
aspectRatio, |
|||
model.getThumbnailUrl(), |
|||
controlsBinding, |
|||
videoPlayerCallback); |
|||
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> { |
|||
// final float newVol = videoPlayerViewHelper.toggleMute(); |
|||
// setMuteIcon(newVol); |
|||
// Utils.sessionVolumeFull = newVol == 1f; |
|||
// }); |
|||
binding.playerView.setOnClickListener(v -> { |
|||
if (sliderCallback != null) { |
|||
sliderCallback.onItemClicked(position); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void pause() { |
|||
if (videoPlayerViewHelper == null) return; |
|||
videoPlayerViewHelper.pause(); |
|||
} |
|||
|
|||
public void releasePlayer() { |
|||
if (videoPlayerViewHelper == null) return; |
|||
videoPlayerViewHelper.releasePlayer(); |
|||
} |
|||
|
|||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) { |
|||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams(); |
|||
// final int deviceWidth = Utils.displayMetrics.widthPixels; |
|||
// final int spanWidth = deviceWidth / spanCount; |
|||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth()); |
|||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth; |
|||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight; |
|||
// if (animate) { |
|||
// Animation animation = AnimationUtils.expand( |
|||
// binding.imageViewer, |
|||
// layoutParams.width, |
|||
// layoutParams.height, |
|||
// width, |
|||
// height, |
|||
// new Animation.AnimationListener() { |
|||
// @Override |
|||
// public void onAnimationStart(final Animation animation) { |
|||
// showOrHideDetails(spanCount); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onAnimationEnd(final Animation animation) { |
|||
// // showOrHideDetails(spanCount); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onAnimationRepeat(final Animation animation) { |
|||
// |
|||
// } |
|||
// }); |
|||
// binding.imageViewer.startAnimation(animation); |
|||
// } else { |
|||
// layoutParams.width = width; |
|||
// layoutParams.height = height; |
|||
// binding.imageViewer.requestLayout(); |
|||
// } |
|||
// } |
|||
// |
|||
// private void showOrHideDetails(final int spanCount) { |
|||
// if (spanCount == 1) { |
|||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE); |
|||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE); |
|||
// } else { |
|||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE); |
|||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE); |
|||
// } |
|||
// } |
|||
} |
@ -0,0 +1,45 @@ |
|||
package awais.instagrabber.animations; |
|||
|
|||
import android.view.View; |
|||
import android.view.animation.Animation; |
|||
import android.view.animation.Transformation; |
|||
|
|||
public class ResizeAnimation extends Animation { |
|||
private static final String TAG = "ResizeAnimation"; |
|||
|
|||
final View view; |
|||
final int startHeight; |
|||
final int targetHeight; |
|||
final int startWidth; |
|||
final int targetWidth; |
|||
|
|||
public ResizeAnimation(final View view, |
|||
final int startHeight, |
|||
final int startWidth, |
|||
final int targetHeight, |
|||
final int targetWidth) { |
|||
this.view = view; |
|||
this.startHeight = startHeight; |
|||
this.targetHeight = targetHeight; |
|||
this.startWidth = startWidth; |
|||
this.targetWidth = targetWidth; |
|||
} |
|||
|
|||
@Override |
|||
protected void applyTransformation(final float interpolatedTime, final Transformation t) { |
|||
// Log.d(TAG, "applyTransformation: interpolatedTime: " + interpolatedTime); |
|||
view.getLayoutParams().height = (int) (startHeight + (targetHeight - startHeight) * interpolatedTime); |
|||
view.getLayoutParams().width = (int) (startWidth + (targetWidth - startWidth) * interpolatedTime); |
|||
view.requestLayout(); |
|||
} |
|||
|
|||
@Override |
|||
public void initialize(final int width, final int height, final int parentWidth, final int parentHeight) { |
|||
super.initialize(width, height, parentWidth, parentHeight); |
|||
} |
|||
|
|||
@Override |
|||
public boolean willChangeBounds() { |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,310 @@ |
|||
// package awais.instagrabber.asyncs; |
|||
// |
|||
// import android.content.ContentResolver; |
|||
// import android.content.Context; |
|||
// import android.content.Intent; |
|||
// import android.graphics.Bitmap; |
|||
// import android.graphics.BitmapFactory; |
|||
// import android.media.MediaMetadataRetriever; |
|||
// import android.media.MediaScannerConnection; |
|||
// import android.net.Uri; |
|||
// import android.os.AsyncTask; |
|||
// import android.os.Build; |
|||
// import android.util.Log; |
|||
// |
|||
// import androidx.annotation.NonNull; |
|||
// import androidx.annotation.Nullable; |
|||
// import androidx.core.content.FileProvider; |
|||
// |
|||
// import java.io.BufferedInputStream; |
|||
// import java.io.File; |
|||
// import java.io.FileOutputStream; |
|||
// import java.io.InputStream; |
|||
// import java.net.URL; |
|||
// import java.net.URLConnection; |
|||
// import java.util.HashMap; |
|||
// import java.util.Map; |
|||
// |
|||
// import awais.instagrabber.BuildConfig; |
|||
// import awais.instagrabber.R; |
|||
// import awais.instagrabber.utils.Utils; |
|||
// |
|||
// import static awais.instagrabber.utils.Utils.logCollector; |
|||
// import static awaisomereport.LogCollector.LogFile; |
|||
// |
|||
// public final class DownloadV2AsyncTask extends AsyncTask<Void, Float, File> { |
|||
// private static final String TAG = "DownloadAsync"; |
|||
// |
|||
// // private final int currentNotificationId; |
|||
// // private final int initialNotificationId; |
|||
// // private final File outFile; |
|||
// // private final String url; |
|||
// // private final FetchListener<File> fetchListener; |
|||
// // private final NotificationCompat.Builder downloadNotif; |
|||
// private String shortCode, username; |
|||
// // private final NotificationManagerCompat notificationManager; |
|||
// |
|||
// public DownloadV2AsyncTask(@NonNull final Context context) { |
|||
// // this.shortCode = this.username = resources.getString(R.string.downloader_started); |
|||
// |
|||
// // @StringRes final int titleRes = R.string.downloader_downloading_post; |
|||
// // downloadNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID) |
|||
// // .setCategory(NotificationCompat.CATEGORY_STATUS) |
|||
// // .setSmallIcon(R.mipmap.ic_launcher) |
|||
// // .setContentText(shortCode == null ? username : shortCode) |
|||
// // .setOngoing(true) |
|||
// // .setProgress(100, 0, false) |
|||
// // .setAutoCancel(false) |
|||
// // .setOnlyAlertOnce(true) |
|||
// // .setContentTitle(resources.getString(titleRes)); |
|||
// // |
|||
// // notificationManager = NotificationManagerCompat.from(context.getApplicationContext()); |
|||
// // notificationManager.notify(currentNotificationId, downloadNotif.build()); |
|||
// } |
|||
// |
|||
// // public DownloadV2AsyncTask setItems(final String shortCode, final String username) { |
|||
// // this.shortCode = shortCode; |
|||
// // this.username = username; |
|||
// // if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode); |
|||
// // return this; |
|||
// // } |
|||
// |
|||
// @Nullable |
|||
// @Override |
|||
// protected File doInBackground(final Void... voids) { |
|||
// try { |
|||
// final URLConnection urlConnection = new URL(url).openConnection(); |
|||
// final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : |
|||
// urlConnection.getContentLength(); |
|||
// float totalRead = 0; |
|||
// |
|||
// try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); |
|||
// final FileOutputStream fos = new FileOutputStream(outFile)) { |
|||
// final byte[] buffer = new byte[0x2000]; |
|||
// |
|||
// int count; |
|||
// boolean deletedIPTC = false; |
|||
// while ((count = bis.read(buffer, 0, 0x2000)) != -1) { |
|||
// totalRead = totalRead + count; |
|||
// |
|||
// if (!deletedIPTC) { |
|||
// int iptcStart = -1; |
|||
// int fbmdStart = -1; |
|||
// int fbmdBytesLen = -1; |
|||
// |
|||
// for (int i = 0; i < buffer.length; ++i) { |
|||
// if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED) |
|||
// iptcStart = i; |
|||
// else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B' |
|||
// && buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') { |
|||
// fbmdStart = i; |
|||
// fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 | |
|||
// (buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) | |
|||
// (buffer[i - 6] & 0xFF); |
|||
// break; |
|||
// } |
|||
// } |
|||
// |
|||
// if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) { |
|||
// final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4; |
|||
// |
|||
// fos.write(buffer, 0, iptcStart); |
|||
// fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart); |
|||
// |
|||
// publishProgress(totalRead * 100f / fileSize); |
|||
// |
|||
// deletedIPTC = true; |
|||
// continue; |
|||
// } |
|||
// } |
|||
// |
|||
// fos.write(buffer, 0, count); |
|||
// publishProgress(totalRead * 100f / fileSize); |
|||
// } |
|||
// fos.flush(); |
|||
// } |
|||
// |
|||
// return outFile; |
|||
// } catch (final Exception e) { |
|||
// // if (logCollector != null) |
|||
// // logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground", |
|||
// // new Pair<>("context", context.get()), |
|||
// // new Pair<>("resources", resources), |
|||
// // new Pair<>("lastNotifId", initialNotificationId), |
|||
// // new Pair<>("downloadNotif", downloadNotif), |
|||
// // new Pair<>("currentNotifId", currentNotificationId), |
|||
// // new Pair<>("notificationManager", notificationManager)); |
|||
// if (BuildConfig.DEBUG) Log.e(TAG, "", e); |
|||
// } |
|||
// return null; |
|||
// } |
|||
// |
|||
// @Override |
|||
// protected void onPreExecute() { |
|||
// // if (fetchListener != null) fetchListener.doBefore(); |
|||
// } |
|||
// |
|||
// @Override |
|||
// protected void onProgressUpdate(@NonNull final Float... values) { |
|||
// // if (downloadNotif != null) { |
|||
// // downloadNotif.setProgress(100, values[0].intValue(), false); |
|||
// // notificationManager.notify(currentNotificationId, downloadNotif.build()); |
|||
// // } |
|||
// } |
|||
// |
|||
// @Override |
|||
// protected void onPostExecute(final File result) { |
|||
// if (result != null) { |
|||
// // final Context context = this.context.get(); |
|||
// // context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile()))); |
|||
// // MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null); |
|||
// // |
|||
// // // if (notificationManager != null) { |
|||
// // final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", result); |
|||
// // |
|||
// // final ContentResolver contentResolver = context.getContentResolver(); |
|||
// // Bitmap bitmap = null; |
|||
// // if (Utils.isImage(uri, contentResolver)) { |
|||
// // try (final InputStream inputStream = contentResolver.openInputStream(uri)) { |
|||
// // bitmap = BitmapFactory.decodeStream(inputStream); |
|||
// // } catch (final Exception e) { |
|||
// // if (logCollector != null) |
|||
// // logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); |
|||
// // if (BuildConfig.DEBUG) Log.e(TAG, "", e); |
|||
// // } |
|||
// // } |
|||
// // |
|||
// // if (bitmap == null) { |
|||
// // final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
|||
// // try { |
|||
// // try { |
|||
// // retriever.setDataSource(context, uri); |
|||
// // } catch (final Exception e) { |
|||
// // retriever.setDataSource(result.getAbsolutePath()); |
|||
// // } |
|||
// // bitmap = retriever.getFrameAtTime(); |
|||
// // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) |
|||
// // try { |
|||
// // retriever.close(); |
|||
// // } catch (final Exception e) { |
|||
// // if (logCollector != null) |
|||
// // logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); |
|||
// // } |
|||
// // } catch (final Exception e) { |
|||
// // if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
// // if (logCollector != null) |
|||
// // logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); |
|||
// // } |
|||
// // } |
|||
// |
|||
// // final String downloadComplete = resources.getString(R.string.downloader_complete); |
|||
// |
|||
// // downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false) |
|||
// // .setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true) |
|||
// // .setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent( |
|||
// // PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri) |
|||
// // .addFlags( |
|||
// // Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|||
// // .putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT)); |
|||
// // |
|||
// // if (bitmap != null) |
|||
// // downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap)) |
|||
// // .setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); |
|||
// // |
|||
// // notificationManager.cancel(currentNotificationId); |
|||
// // notificationManager.notify(currentNotificationId + 1, downloadNotif.build()); |
|||
// // } |
|||
// } |
|||
// |
|||
// // if (fetchListener != null) fetchListener.onResult(result); |
|||
// } |
|||
// |
|||
// public static class DownloadOptions { |
|||
// private final Map<String, File> urlToFileMap; |
|||
// private final Map<String, DownloadCallback> callbackMap; |
|||
// private final boolean showNotification; |
|||
// |
|||
// public static class Builder { |
|||
// private Map<String, File> urlToFileMap; |
|||
// private Map<String, DownloadCallback> callbackMap; |
|||
// private boolean showNotification; |
|||
// |
|||
// public Builder setUrlToFileMap(@NonNull final Map<String, File> urlToFileMap) { |
|||
// this.urlToFileMap = urlToFileMap; |
|||
// return this; |
|||
// } |
|||
// |
|||
// public Builder setCallbackMap(@NonNull final Map<String, DownloadCallback> callbackMap) { |
|||
// this.callbackMap = callbackMap; |
|||
// return this; |
|||
// } |
|||
// |
|||
// public Builder addUrl(@NonNull final String url, @NonNull final File file) { |
|||
// if (urlToFileMap == null) { |
|||
// urlToFileMap = new HashMap<>(); |
|||
// } |
|||
// urlToFileMap.put(url, file); |
|||
// return this; |
|||
// } |
|||
// |
|||
// public Builder addUrl(@NonNull final String url, |
|||
// @NonNull final File file, |
|||
// @NonNull final DownloadCallback callback) { |
|||
// if (urlToFileMap == null) { |
|||
// urlToFileMap = new HashMap<>(); |
|||
// } |
|||
// if (callbackMap == null) { |
|||
// callbackMap = new HashMap<>(); |
|||
// } |
|||
// urlToFileMap.put(url, file); |
|||
// callbackMap.put(url, callback); |
|||
// return this; |
|||
// } |
|||
// |
|||
// public Builder setShowNotification(final boolean showNotification) { |
|||
// this.showNotification = showNotification; |
|||
// return this; |
|||
// } |
|||
// |
|||
// public DownloadOptions build() { |
|||
// return new DownloadOptions( |
|||
// urlToFileMap, |
|||
// callbackMap, |
|||
// showNotification |
|||
// ); |
|||
// } |
|||
// } |
|||
// |
|||
// public Builder builder() { |
|||
// return new Builder(); |
|||
// } |
|||
// |
|||
// private DownloadOptions(final Map<String, File> urlToFileMap, |
|||
// final Map<String, DownloadCallback> callbackMap, |
|||
// final boolean showNotification) { |
|||
// this.urlToFileMap = urlToFileMap; |
|||
// this.callbackMap = callbackMap; |
|||
// this.showNotification = showNotification; |
|||
// } |
|||
// |
|||
// public Map<String, File> getUrlToFileMap() { |
|||
// return urlToFileMap; |
|||
// } |
|||
// |
|||
// public Map<String, DownloadCallback> getCallbackMap() { |
|||
// return callbackMap; |
|||
// } |
|||
// |
|||
// public boolean isShowNotification() { |
|||
// return showNotification; |
|||
// } |
|||
// } |
|||
// |
|||
// public interface DownloadCallback { |
|||
// void onDownloadStart(); |
|||
// |
|||
// void onDownloadProgress(); |
|||
// |
|||
// void onDownloadComplete(); |
|||
// } |
|||
// } |
@ -0,0 +1,54 @@ |
|||
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.repositories.responses.FeedFetchResponse; |
|||
import awais.instagrabber.webservices.FeedService; |
|||
import awais.instagrabber.webservices.ServiceCallback; |
|||
|
|||
public class FeedPostFetchService implements PostFetcher.PostFetchService { |
|||
private static final String TAG = "FeedPostFetchService"; |
|||
private final FeedService feedService; |
|||
private String nextCursor; |
|||
private boolean hasNextPage; |
|||
|
|||
public FeedPostFetchService() { |
|||
feedService = FeedService.getInstance(); |
|||
} |
|||
|
|||
@Override |
|||
public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) { |
|||
feedService.fetch(25, cursor, new ServiceCallback<FeedFetchResponse>() { |
|||
@Override |
|||
public void onSuccess(final FeedFetchResponse result) { |
|||
if (result == null) return; |
|||
nextCursor = result.getNextCursor(); |
|||
hasNextPage = result.hasNextPage(); |
|||
if (fetchListener != null) { |
|||
fetchListener.onResult(result.getFeedModels()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
// Log.e(TAG, "onFailure: ", t); |
|||
if (fetchListener != null) { |
|||
fetchListener.onFailure(t); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public String getNextCursor() { |
|||
return nextCursor; |
|||
} |
|||
|
|||
@Override |
|||
public boolean hasNextPage() { |
|||
return hasNextPage; |
|||
} |
|||
} |
@ -0,0 +1,259 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.util.AttributeSet; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.lifecycle.LifecycleOwner; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
import androidx.lifecycle.ViewModelStoreOwner; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
import androidx.recyclerview.widget.StaggeredGridLayoutManager; |
|||
import androidx.transition.ChangeBounds; |
|||
import androidx.transition.Transition; |
|||
import androidx.transition.TransitionManager; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.adapters.FeedAdapterV2; |
|||
import awais.instagrabber.adapters.FeedAdapterV2.OnPostClickListener; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.customviews.helpers.PostFetcher; |
|||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.viewmodels.FeedViewModel; |
|||
|
|||
public class PostsRecyclerView extends RecyclerView { |
|||
private static final String TAG = "PostsRecyclerView"; |
|||
|
|||
private StaggeredGridLayoutManager gridLayoutManager; |
|||
private PostsLayoutPreferences layoutPreferences; |
|||
private PostFetcher.PostFetchService postFetchService; |
|||
private Transition transition; |
|||
private OnClickListener postViewClickListener; |
|||
private MentionClickListener mentionClickListener; |
|||
private PostFetcher postFetcher; |
|||
private ViewModelStoreOwner viewModelStoreOwner; |
|||
private FeedAdapterV2 feedAdapter; |
|||
private LifecycleOwner lifeCycleOwner; |
|||
private FeedViewModel feedViewModel; |
|||
private boolean initCalled = false; |
|||
private GridSpacingItemDecoration gridSpacingItemDecoration; |
|||
private RecyclerLazyLoaderAtBottom lazyLoader; |
|||
private OnPostClickListener onPostClickListener; |
|||
|
|||
private final FetchListener<List<FeedModel>> fetchListener = new FetchListener<List<FeedModel>>() { |
|||
@Override |
|||
public void onResult(final List<FeedModel> result) { |
|||
final int currentPage = lazyLoader.getCurrentPage(); |
|||
if (currentPage == 0) { |
|||
feedViewModel.getList().postValue(result); |
|||
dispatchFetchStatus(); |
|||
return; |
|||
} |
|||
final List<FeedModel> models = feedViewModel.getList().getValue(); |
|||
final List<FeedModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); |
|||
modelsCopy.addAll(result); |
|||
feedViewModel.getList().postValue(modelsCopy); |
|||
dispatchFetchStatus(); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
Log.e(TAG, "onFailure: ", t); |
|||
} |
|||
}; |
|||
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>(); |
|||
|
|||
public PostsRecyclerView(@NonNull final Context context) { |
|||
super(context); |
|||
} |
|||
|
|||
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
} |
|||
|
|||
public PostsRecyclerView setViewModelStoreOwner(final ViewModelStoreOwner owner) { |
|||
if (initCalled) { |
|||
throw new IllegalArgumentException("init already called!"); |
|||
} |
|||
this.viewModelStoreOwner = owner; |
|||
return this; |
|||
} |
|||
|
|||
public PostsRecyclerView setLifeCycleOwner(final LifecycleOwner lifeCycleOwner) { |
|||
if (initCalled) { |
|||
throw new IllegalArgumentException("init already called!"); |
|||
} |
|||
this.lifeCycleOwner = lifeCycleOwner; |
|||
return this; |
|||
} |
|||
|
|||
public PostsRecyclerView setPostFetchService(final PostFetcher.PostFetchService postFetchService) { |
|||
if (initCalled) { |
|||
throw new IllegalArgumentException("init already called!"); |
|||
} |
|||
this.postFetchService = postFetchService; |
|||
return this; |
|||
} |
|||
|
|||
public PostsRecyclerView setOnPostClickListener(@NonNull final OnPostClickListener onPostClickListener) { |
|||
this.onPostClickListener = onPostClickListener; |
|||
return this; |
|||
} |
|||
|
|||
public PostsRecyclerView setMentionClickListener(final MentionClickListener mentionClickListener) { |
|||
this.mentionClickListener = mentionClickListener; |
|||
return this; |
|||
} |
|||
|
|||
public PostsRecyclerView setLayoutPreferences(final PostsLayoutPreferences layoutPreferences) { |
|||
this.layoutPreferences = layoutPreferences; |
|||
if (initCalled) { |
|||
if (layoutPreferences == null) return this; |
|||
feedAdapter.setLayoutPreferences(layoutPreferences); |
|||
updateLayout(); |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
public void init() { |
|||
initCalled = true; |
|||
if (viewModelStoreOwner == null) { |
|||
throw new IllegalArgumentException("ViewModelStoreOwner cannot be null"); |
|||
} else if (lifeCycleOwner == null) { |
|||
throw new IllegalArgumentException("LifecycleOwner cannot be null"); |
|||
} else if (postFetchService == null) { |
|||
throw new IllegalArgumentException("PostFetchService cannot be null"); |
|||
} |
|||
if (layoutPreferences == null) { |
|||
layoutPreferences = PostsLayoutPreferences.builder() |
|||
.setType(PostsLayoutPreferences.PostsLayoutType.GRID) |
|||
.setColCount(3) |
|||
.setAvatarVisible(true) |
|||
.setNameVisible(false) |
|||
.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.TINY) |
|||
.setHasGap(true) |
|||
.setHasRoundedCorners(true) |
|||
.build(); |
|||
Utils.settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, layoutPreferences.getJson()); |
|||
} |
|||
gridSpacingItemDecoration = new GridSpacingItemDecoration(Utils.convertDpToPx(2)); |
|||
initTransition(); |
|||
initAdapter(); |
|||
initLayoutManager(); |
|||
initSelf(); |
|||
|
|||
} |
|||
|
|||
private void initTransition() { |
|||
transition = new ChangeBounds(); |
|||
// transition.addListener(new TransitionListenerAdapter(){ |
|||
// @Override |
|||
// public void onTransitionEnd(@NonNull final Transition transition) { |
|||
// super.onTransitionEnd(transition); |
|||
// } |
|||
// }); |
|||
transition.setDuration(300); |
|||
} |
|||
|
|||
private void initLayoutManager() { |
|||
gridLayoutManager = new StaggeredGridLayoutManager(layoutPreferences.getColCount(), StaggeredGridLayoutManager.VERTICAL); |
|||
setLayoutManager(gridLayoutManager); |
|||
} |
|||
|
|||
private void initAdapter() { |
|||
feedAdapter = new FeedAdapterV2( |
|||
layoutPreferences, |
|||
postViewClickListener, |
|||
mentionClickListener, |
|||
(feedModel, view, postImage) -> { |
|||
if (onPostClickListener != null) { |
|||
onPostClickListener.onPostClick(feedModel, view, postImage); |
|||
} |
|||
}); |
|||
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); |
|||
setAdapter(feedAdapter); |
|||
} |
|||
|
|||
private void initSelf() { |
|||
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class); |
|||
feedViewModel.getList().observe(lifeCycleOwner, feedAdapter::submitList); |
|||
postFetcher = new PostFetcher(postFetchService, fetchListener); |
|||
addItemDecoration(gridSpacingItemDecoration); |
|||
setHasFixedSize(true); |
|||
lazyLoader = new RecyclerLazyLoaderAtBottom(gridLayoutManager, (page) -> { |
|||
if (postFetcher.hasMore()) { |
|||
postFetcher.fetchNextPage(); |
|||
dispatchFetchStatus(); |
|||
} |
|||
}); |
|||
addOnScrollListener(lazyLoader); |
|||
postFetcher.fetch(); |
|||
dispatchFetchStatus(); |
|||
} |
|||
|
|||
private void updateLayout() { |
|||
post(() -> { |
|||
TransitionManager.beginDelayedTransition(this, transition); |
|||
feedAdapter.notifyDataSetChanged(); |
|||
if (!layoutPreferences.getHasGap()) { |
|||
removeItemDecoration(gridSpacingItemDecoration); |
|||
} |
|||
gridLayoutManager.setSpanCount(layoutPreferences.getColCount()); |
|||
}); |
|||
} |
|||
|
|||
public void refresh() { |
|||
lazyLoader.resetState(); |
|||
postFetcher.fetch(); |
|||
dispatchFetchStatus(); |
|||
} |
|||
|
|||
public boolean isFetching() { |
|||
return postFetcher != null && postFetcher.isFetching(); |
|||
} |
|||
|
|||
public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) { |
|||
if (fetchStatusChangeListener == null) return this; |
|||
fetchStatusChangeListeners.add(fetchStatusChangeListener); |
|||
return this; |
|||
} |
|||
|
|||
public void removeFetchStatusListener(final FetchStatusChangeListener fetchStatusChangeListener) { |
|||
if (fetchStatusChangeListener == null) return; |
|||
fetchStatusChangeListeners.remove(fetchStatusChangeListener); |
|||
} |
|||
|
|||
private void dispatchFetchStatus() { |
|||
for (final FetchStatusChangeListener listener : fetchStatusChangeListeners) { |
|||
listener.onFetchStatusChange(isFetching()); |
|||
} |
|||
} |
|||
|
|||
public PostsLayoutPreferences getLayoutPreferences() { |
|||
return layoutPreferences; |
|||
} |
|||
|
|||
public interface FetchStatusChangeListener { |
|||
void onFetchStatusChange(boolean fetching); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDetachedFromWindow() { |
|||
super.onDetachedFromWindow(); |
|||
lifeCycleOwner = null; |
|||
} |
|||
} |
@ -0,0 +1,141 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.content.res.TypedArray; |
|||
import android.graphics.Color; |
|||
import android.util.AttributeSet; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.DimenRes; |
|||
import androidx.annotation.NonNull; |
|||
|
|||
import com.facebook.drawee.generic.GenericDraweeHierarchy; |
|||
import com.facebook.drawee.generic.RoundingParams; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class ProfilePicView extends CircularImageView { |
|||
private static final String TAG = "ProfilePicView"; |
|||
|
|||
private Size size; |
|||
private int dimensionPixelSize; |
|||
|
|||
public ProfilePicView(Context context, GenericDraweeHierarchy hierarchy) { |
|||
super(context); |
|||
setHierarchy(hierarchy); |
|||
size = Size.REGULAR; |
|||
updateLayout(); |
|||
} |
|||
|
|||
public ProfilePicView(final Context context) { |
|||
super(context); |
|||
size = Size.REGULAR; |
|||
updateLayout(); |
|||
} |
|||
|
|||
public ProfilePicView(final Context context, final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
parseAttrs(context, attrs); |
|||
updateLayout(); |
|||
} |
|||
|
|||
public ProfilePicView(final Context context, |
|||
final AttributeSet attrs, |
|||
final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
parseAttrs(context, attrs); |
|||
} |
|||
|
|||
private void parseAttrs(final Context context, final AttributeSet attrs) { |
|||
final TypedArray a = context.getTheme().obtainStyledAttributes( |
|||
attrs, |
|||
R.styleable.ProfilePicView, |
|||
0, |
|||
0); |
|||
try { |
|||
final int sizeValue = a.getInt(R.styleable.ProfilePicView_size, Size.REGULAR.getValue()); |
|||
size = Size.valueOf(sizeValue); |
|||
} finally { |
|||
a.recycle(); |
|||
} |
|||
} |
|||
|
|||
private void updateLayout() { |
|||
@DimenRes final int dimenRes; |
|||
switch (size) { |
|||
case SMALL: |
|||
dimenRes = R.dimen.profile_pic_size_small; |
|||
break; |
|||
case TINY: |
|||
dimenRes = R.dimen.profile_pic_size_tiny; |
|||
break; |
|||
case LARGE: |
|||
dimenRes = R.dimen.profile_pic_size_large; |
|||
break; |
|||
default: |
|||
case REGULAR: |
|||
dimenRes = R.dimen.profile_pic_size_regular; |
|||
break; |
|||
} |
|||
ViewGroup.LayoutParams layoutParams = getLayoutParams(); |
|||
if (layoutParams == null) { |
|||
layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
|||
} |
|||
dimensionPixelSize = getResources().getDimensionPixelSize(dimenRes); |
|||
layoutParams.width = dimensionPixelSize; |
|||
layoutParams.height = dimensionPixelSize; |
|||
|
|||
// invalidate(); |
|||
// requestLayout(); |
|||
} |
|||
|
|||
public void setStoriesBorder() { |
|||
// private final int borderSize = 8; |
|||
final int color = Color.GREEN; |
|||
RoundingParams roundingParams = getHierarchy().getRoundingParams(); |
|||
if (roundingParams == null) { |
|||
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY); |
|||
} |
|||
roundingParams.setBorder(color, 5.0f); |
|||
getHierarchy().setRoundingParams(roundingParams); |
|||
} |
|||
|
|||
public enum Size { |
|||
TINY(0), |
|||
SMALL(1), |
|||
REGULAR(2), |
|||
LARGE(3); |
|||
|
|||
private final int value; |
|||
private static Map<Integer, Size> map = new HashMap<>(); |
|||
|
|||
static { |
|||
for (Size size : Size.values()) { |
|||
map.put(size.value, size); |
|||
} |
|||
} |
|||
|
|||
Size(final int value) { |
|||
this.value = value; |
|||
} |
|||
|
|||
@NonNull |
|||
public static Size valueOf(final int value) { |
|||
final Size size = map.get(value); |
|||
return size != null ? size : Size.REGULAR; |
|||
} |
|||
|
|||
public int getValue() { |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { |
|||
super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
|||
setMeasuredDimension(dimensionPixelSize, dimensionPixelSize); |
|||
} |
|||
} |
@ -0,0 +1,302 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.animation.Animator; |
|||
import android.graphics.Rect; |
|||
import android.os.Handler; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.view.ViewTreeObserver; |
|||
import android.view.animation.AccelerateDecelerateInterpolator; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.fragment.app.DialogFragment; |
|||
import androidx.transition.ChangeBounds; |
|||
import androidx.transition.ChangeTransform; |
|||
import androidx.transition.Transition; |
|||
import androidx.transition.TransitionListenerAdapter; |
|||
import androidx.transition.TransitionManager; |
|||
import androidx.transition.TransitionSet; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
|
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public abstract class SharedElementTransitionDialogFragment extends DialogFragment { |
|||
// private static final String TAG = "SETDialogFragment"; |
|||
private static final int DURATION = 200; |
|||
|
|||
private final Map<Integer, View> startViews = new HashMap<>(); |
|||
private final Map<Integer, View> destViews = new HashMap<>(); |
|||
private final Map<Integer, ViewBounds> viewBoundsMap = new HashMap<>(); |
|||
private final List<Animator> additionalAnimators = new ArrayList<>(); |
|||
private final Handler initialBoundsHandler = new Handler(); |
|||
|
|||
private boolean startCalled = false; |
|||
private boolean startInitiated = false; |
|||
private int boundsCalculatedCount = 0; |
|||
|
|||
protected int getAnimationDuration() { |
|||
return DURATION; |
|||
} |
|||
|
|||
public void addSharedElement(@NonNull final View startView, @NonNull final View destView) { |
|||
final int key = destView.hashCode(); |
|||
startViews.put(key, startView); |
|||
destViews.put(key, destView); |
|||
initialBoundsHandler.post(() -> setupInitialBounds(startView, destView)); |
|||
} |
|||
|
|||
public void startPostponedEnterTransition() { |
|||
startCalled = true; |
|||
if (startInitiated) return; |
|||
if (boundsCalculatedCount < startViews.size()) return; |
|||
startInitiated = true; |
|||
final Set<Integer> keySet = startViews.keySet(); |
|||
final View view = getView(); |
|||
if (!(view instanceof ViewGroup)) return; |
|||
final TransitionSet transitionSet = new TransitionSet() |
|||
.setDuration(DURATION) |
|||
.setInterpolator(new AccelerateDecelerateInterpolator()) |
|||
.addTransition(new ChangeBounds()) |
|||
.addTransition(new ChangeTransform()) |
|||
.addListener(new TransitionListenerAdapter() { |
|||
@Override |
|||
public void onTransitionStart(@NonNull final Transition transition) { |
|||
for (Animator animator : additionalAnimators) { |
|||
animator.start(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onTransitionEnd(@NonNull final Transition transition) { |
|||
for (final Integer key : keySet) { |
|||
final View startView = startViews.get(key); |
|||
final View destView = destViews.get(key); |
|||
final ViewBounds viewBounds = viewBoundsMap.get(key); |
|||
onEndSharedElementAnimation(startView, destView, viewBounds); |
|||
} |
|||
} |
|||
}); |
|||
view.post(() -> { |
|||
TransitionManager.beginDelayedTransition((ViewGroup) view, transitionSet); |
|||
for (final Integer key : keySet) { |
|||
final View startView = startViews.get(key); |
|||
final View destView = destViews.get(key); |
|||
final ViewBounds viewBounds = viewBoundsMap.get(key); |
|||
onBeforeSharedElementAnimation(startView, destView, viewBounds); |
|||
setDestBounds(key); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void setDestBounds(final int key) { |
|||
final View startView = startViews.get(key); |
|||
if (startView == null) return; |
|||
final View destView = destViews.get(key); |
|||
if (destView == null) return; |
|||
final ViewBounds viewBounds = viewBoundsMap.get(key); |
|||
if (viewBounds == null) return; |
|||
destView.setX((int) viewBounds.getDestX()); |
|||
destView.setY((int) viewBounds.getDestY()); |
|||
destView.setTranslationX(0); |
|||
destView.setTranslationY(0); |
|||
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams(); |
|||
layoutParams.height = viewBounds.getDestHeight(); |
|||
layoutParams.width = viewBounds.getDestWidth(); |
|||
destView.requestLayout(); |
|||
} |
|||
|
|||
protected void onBeforeSharedElementAnimation(@NonNull final View startView, |
|||
@NonNull final View destView, |
|||
@NonNull final ViewBounds viewBounds) {} |
|||
|
|||
protected void onEndSharedElementAnimation(@NonNull final View startView, |
|||
@NonNull final View destView, |
|||
@NonNull final ViewBounds viewBounds) {} |
|||
|
|||
private void setupInitialBounds(@NonNull final View startView, @NonNull final View destView) { |
|||
final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() { |
|||
private boolean firstPassDone; |
|||
|
|||
@Override |
|||
public boolean onPreDraw() { |
|||
destView.getViewTreeObserver().removeOnPreDrawListener(this); |
|||
if (!firstPassDone) { |
|||
getViewBounds(startView, destView, this); |
|||
firstPassDone = true; |
|||
return false; |
|||
} |
|||
final int[] location = new int[2]; |
|||
startView.getLocationOnScreen(location); |
|||
final int initX = location[0]; |
|||
final int initY = location[1]; |
|||
destView.setX(initX); |
|||
destView.setY(initY - Utils.getStatusBarHeight(getContext())); |
|||
destView.requestLayout(); |
|||
boundsCalculatedCount++; |
|||
if (startCalled) { |
|||
startPostponedEnterTransition(); |
|||
} |
|||
return true; |
|||
} |
|||
}; |
|||
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener); |
|||
} |
|||
|
|||
private void getViewBounds(@NonNull final View startView, |
|||
@NonNull final View destView, |
|||
@NonNull final ViewTreeObserver.OnPreDrawListener preDrawListener) { |
|||
final ViewBounds viewBounds = new ViewBounds(); |
|||
viewBounds.setDestWidth(destView.getWidth()); |
|||
viewBounds.setDestHeight(destView.getHeight()); |
|||
viewBounds.setDestX(destView.getX()); |
|||
viewBounds.setDestY(destView.getY()); |
|||
|
|||
final Rect destBounds = new Rect(); |
|||
destView.getDrawingRect(destBounds); |
|||
viewBounds.setDestBounds(destBounds); |
|||
|
|||
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams(); |
|||
|
|||
final Rect startBounds = new Rect(); |
|||
startView.getDrawingRect(startBounds); |
|||
viewBounds.setStartBounds(startBounds); |
|||
|
|||
final int key = destView.hashCode(); |
|||
viewBoundsMap.put(key, viewBounds); |
|||
|
|||
layoutParams.height = startView.getHeight(); |
|||
layoutParams.width = startView.getWidth(); |
|||
|
|||
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener); |
|||
destView.requestLayout(); |
|||
} |
|||
|
|||
// private void animateBounds(@NonNull final View startView, |
|||
// @NonNull final View destView, |
|||
// @NonNull final ViewBounds viewBounds) { |
|||
// final ValueAnimator heightAnimator = ObjectAnimator.ofInt(startView.getHeight(), viewBounds.getDestHeight()); |
|||
// final ValueAnimator widthAnimator = ObjectAnimator.ofInt(startView.getWidth(), viewBounds.getDestWidth()); |
|||
// heightAnimator.setDuration(DURATION); |
|||
// widthAnimator.setDuration(DURATION); |
|||
// additionalAnimators.add(heightAnimator); |
|||
// additionalAnimators.add(widthAnimator); |
|||
// heightAnimator.addUpdateListener(animation -> { |
|||
// ViewGroup.LayoutParams params = destView.getLayoutParams(); |
|||
// params.height = (int) animation.getAnimatedValue(); |
|||
// destView.requestLayout(); |
|||
// }); |
|||
// widthAnimator.addUpdateListener(animation -> { |
|||
// ViewGroup.LayoutParams params = destView.getLayoutParams(); |
|||
// params.width = (int) animation.getAnimatedValue(); |
|||
// destView.requestLayout(); |
|||
// }); |
|||
// onBeforeSharedElementAnimation(startView, destView, viewBounds); |
|||
// final float destX = viewBounds.getDestX(); |
|||
// final float destY = viewBounds.getDestY(); |
|||
// final AnimatorSet animatorSet = new AnimatorSet(); |
|||
// animatorSet.addListener(new AnimatorListenerAdapter() { |
|||
// @Override |
|||
// public void onAnimationEnd(final Animator animation) { |
|||
// animationEnded(startView, destView, viewBounds); |
|||
// } |
|||
// }); |
|||
// |
|||
// destView.animate() |
|||
// .x(destX) |
|||
// .y(destY) |
|||
// .setDuration(DURATION) |
|||
// .withStartAction(() -> { |
|||
// if (!additionalAnimatorsStarted && additionalAnimators.size() > 0) { |
|||
// additionalAnimatorsStarted = true; |
|||
// animatorSet.playTogether(additionalAnimators); |
|||
// animatorSet.start(); |
|||
// } |
|||
// }) |
|||
// .withEndAction(() -> animationEnded(startView, destView, viewBounds)) |
|||
// .start(); |
|||
// } |
|||
|
|||
// private int endCount = 0; |
|||
// private void animationEnded(final View startView, final View destView, final ViewBounds viewBounds) { |
|||
// ++endCount; |
|||
// if (endCount != startViews.size() + 1) return; |
|||
// onEndSharedElementAnimation(startView, destView, viewBounds); |
|||
// } |
|||
|
|||
protected void addAnimator(@NonNull final Animator animator) { |
|||
additionalAnimators.add(animator); |
|||
} |
|||
|
|||
protected static class ViewBounds { |
|||
private float destY; |
|||
private float destX; |
|||
private int destHeight; |
|||
private int destWidth; |
|||
private Rect startBounds; |
|||
private Rect destBounds; |
|||
|
|||
public ViewBounds() {} |
|||
|
|||
public float getDestY() { |
|||
return destY; |
|||
} |
|||
|
|||
public void setDestY(final float destY) { |
|||
this.destY = destY; |
|||
} |
|||
|
|||
public float getDestX() { |
|||
return destX; |
|||
} |
|||
|
|||
public void setDestX(final float destX) { |
|||
this.destX = destX; |
|||
} |
|||
|
|||
public int getDestHeight() { |
|||
return destHeight; |
|||
} |
|||
|
|||
public void setDestHeight(final int destHeight) { |
|||
this.destHeight = destHeight; |
|||
} |
|||
|
|||
public int getDestWidth() { |
|||
return destWidth; |
|||
} |
|||
|
|||
public void setDestWidth(final int destWidth) { |
|||
this.destWidth = destWidth; |
|||
} |
|||
|
|||
public Rect getStartBounds() { |
|||
return startBounds; |
|||
} |
|||
|
|||
public void setStartBounds(final Rect startBounds) { |
|||
this.startBounds = startBounds; |
|||
} |
|||
|
|||
public Rect getDestBounds() { |
|||
return destBounds; |
|||
} |
|||
|
|||
public void setDestBounds(final Rect destBounds) { |
|||
this.destBounds = destBounds; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroyView() { |
|||
super.onDestroyView(); |
|||
startViews.clear(); |
|||
destViews.clear(); |
|||
viewBoundsMap.clear(); |
|||
additionalAnimators.clear(); |
|||
} |
|||
} |
@ -0,0 +1,203 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.util.Log; |
|||
import android.view.GestureDetector; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.view.ViewConfiguration; |
|||
import android.view.ViewParent; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
public class VerticalDragHelper { |
|||
private static final String TAG = "VerticalDragHelper"; |
|||
private static final float PIXELS_PER_SECOND = 10; |
|||
|
|||
private final View view; |
|||
|
|||
private GestureDetector gestureDetector; |
|||
private Context context; |
|||
private float flingVelocity; |
|||
private OnVerticalDragListener onVerticalDragListener; |
|||
|
|||
private final GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() { |
|||
|
|||
@Override |
|||
public boolean onSingleTapConfirmed(final MotionEvent e) { |
|||
view.performClick(); |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { |
|||
float maxFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); |
|||
float velocityPercentY = velocityY / maxFlingVelocity; |
|||
float normalizedVelocityY = velocityPercentY * PIXELS_PER_SECOND; |
|||
if (Math.abs(normalizedVelocityY) > 4) { |
|||
flingVelocity = normalizedVelocityY; |
|||
} |
|||
return super.onFling(e1, e2, velocityX, velocityY); |
|||
} |
|||
|
|||
|
|||
}; |
|||
|
|||
private final GestureDetector.OnGestureListener dragPreventionGestureListener = new GestureDetector.SimpleOnGestureListener() { |
|||
float prevDistanceY = 0; |
|||
|
|||
@Override |
|||
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) { |
|||
Log.d(TAG, "onScroll: distanceX: " + distanceX + ", distanceY: " + distanceY); |
|||
return super.onScroll(e1, e2, distanceX, distanceY); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onSingleTapUp(final MotionEvent e) { |
|||
Log.d(TAG, "onSingleTapUp"); |
|||
return super.onSingleTapUp(e); |
|||
} |
|||
}; |
|||
|
|||
private float prevRawY; |
|||
private boolean isDragging; |
|||
private float prevRawX; |
|||
private float dX; |
|||
private float prevDY; |
|||
private GestureDetector dragPreventionGestureDetector; |
|||
|
|||
public VerticalDragHelper(@NonNull final View view) { |
|||
this.view = view; |
|||
final Context context = view.getContext(); |
|||
if (context == null) return; |
|||
this.context = context; |
|||
init(); |
|||
} |
|||
|
|||
public void setOnVerticalDragListener(@NonNull final OnVerticalDragListener onVerticalDragListener) { |
|||
this.onVerticalDragListener = onVerticalDragListener; |
|||
} |
|||
|
|||
protected void init() { |
|||
gestureDetector = new GestureDetector(context, gestureListener); |
|||
dragPreventionGestureDetector = new GestureDetector(context, dragPreventionGestureListener); |
|||
} |
|||
|
|||
public boolean onDragTouch(final MotionEvent event) { |
|||
if (onVerticalDragListener == null) { |
|||
return false; |
|||
} |
|||
// dragPreventionGestureDetector.onTouchEvent(event); |
|||
if (gestureDetector.onTouchEvent(event)) { |
|||
return true; |
|||
} |
|||
switch (event.getAction()) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
return true; |
|||
case MotionEvent.ACTION_MOVE: |
|||
boolean handled = false; |
|||
final float rawY = event.getRawY(); |
|||
final float dY = rawY - prevRawY; |
|||
if (!isDragging) { |
|||
final float rawX = event.getRawX(); |
|||
if (prevRawX != 0) { |
|||
dX = rawX - prevRawX; |
|||
} |
|||
prevRawX = rawX; |
|||
if (prevRawY != 0) { |
|||
final float dYAbs = Math.abs(dY - prevDY); |
|||
if (!isDragging && dYAbs < 50) { |
|||
final float abs = Math.abs(dY) - Math.abs(dX); |
|||
if (abs > 0) { |
|||
isDragging = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
if (isDragging) { |
|||
final ViewParent parent = view.getParent(); |
|||
parent.requestDisallowInterceptTouchEvent(true); |
|||
onVerticalDragListener.onDrag(dY); |
|||
handled = true; |
|||
} |
|||
prevDY = dY; |
|||
prevRawY = rawY; |
|||
return handled; |
|||
case MotionEvent.ACTION_UP: |
|||
// Log.d(TAG, "onDragTouch: reset prevRawY"); |
|||
prevRawY = 0; |
|||
if (flingVelocity != 0) { |
|||
onVerticalDragListener.onFling(flingVelocity); |
|||
flingVelocity = 0; |
|||
isDragging = false; |
|||
return true; |
|||
} |
|||
if (isDragging) { |
|||
onVerticalDragListener.onDragEnd(); |
|||
isDragging = false; |
|||
return true; |
|||
} |
|||
return false; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public boolean isDragging() { |
|||
return isDragging; |
|||
} |
|||
|
|||
public boolean onGestureTouchEvent(final MotionEvent event) { |
|||
return gestureDetector.onTouchEvent(event); |
|||
} |
|||
|
|||
private final static int DIRECTION_UP = 0; |
|||
private final static int DIRECTION_DOWN = 1; |
|||
float prevY = -1; |
|||
int edgeHitCount = 0; |
|||
float prevDirection = -1; |
|||
|
|||
|
|||
// private boolean shouldPreventDrag(final MotionEvent event) { |
|||
// switch (event.getAction()) { |
|||
// case MotionEvent.ACTION_DOWN: |
|||
// if (!firstDrag) { |
|||
// firstDrag = true; |
|||
// } |
|||
// return false; |
|||
// case MotionEvent.ACTION_MOVE: |
|||
// float y = event.getY(); |
|||
// int direction = -2; |
|||
// if (prevY != -1) { |
|||
// final float dy = y - prevY; |
|||
// // Log.d(TAG, "shouldPreventDrag: dy: " + dy); |
|||
// if (dy > 0) { |
|||
// direction = DIRECTION_DOWN; |
|||
// // move direction is down |
|||
// } else { |
|||
// direction = DIRECTION_UP; |
|||
// // move direction is up |
|||
// } |
|||
// } |
|||
// prevY = y; |
|||
// if (prevDirection == direction) { |
|||
// edgeHitCount++; |
|||
// } else { |
|||
// edgeHitCount = 1; |
|||
// } |
|||
// if (edgeHitCount >= 2) { |
|||
// return false; |
|||
// } |
|||
// return true; |
|||
// break; |
|||
// } |
|||
// } |
|||
|
|||
public interface OnVerticalDragListener { |
|||
void onDrag(final float dY); |
|||
|
|||
void onDragEnd(); |
|||
|
|||
void onFling(final float flingVelocity); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback { |
|||
@Override |
|||
public void onThumbnailLoaded() {} |
|||
|
|||
@Override |
|||
public void onThumbnailClick() {} |
|||
|
|||
@Override |
|||
public void onPlayerViewLoaded() {} |
|||
|
|||
@Override |
|||
public void onPlay() {} |
|||
|
|||
@Override |
|||
public void onPause() {} |
|||
} |
@ -0,0 +1,373 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.graphics.drawable.Animatable; |
|||
import android.net.Uri; |
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
import android.util.Log; |
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.appcompat.view.ContextThemeWrapper; |
|||
import androidx.appcompat.widget.PopupMenu; |
|||
|
|||
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.facebook.imagepipeline.request.ImageRequest; |
|||
import com.facebook.imagepipeline.request.ImageRequestBuilder; |
|||
import com.google.android.exoplayer2.ExoPlaybackException; |
|||
import com.google.android.exoplayer2.MediaItem; |
|||
import com.google.android.exoplayer2.PlaybackParameters; |
|||
import com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.audio.AudioListener; |
|||
import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
|||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding; |
|||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
|
|||
import static com.google.android.exoplayer2.C.TIME_UNSET; |
|||
import static com.google.android.exoplayer2.Player.STATE_ENDED; |
|||
import static com.google.android.exoplayer2.Player.STATE_IDLE; |
|||
import static com.google.android.exoplayer2.Player.STATE_READY; |
|||
|
|||
public class VideoPlayerViewHelper implements Player.EventListener { |
|||
private static final String TAG = "VideoPlayerViewHelper"; |
|||
|
|||
private final Context context; |
|||
private final awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding binding; |
|||
private final float initialVolume; |
|||
private final float thumbnailAspectRatio; |
|||
private final String thumbnailUrl; |
|||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding; |
|||
private final VideoPlayerCallback videoPlayerCallback; |
|||
private final String videoUrl; |
|||
private final DefaultDataSourceFactory dataSourceFactory; |
|||
private SimpleExoPlayer player; |
|||
private PopupMenu speedPopup; |
|||
|
|||
public VideoPlayerViewHelper(@NonNull final Context context, |
|||
@NonNull final LayoutVideoPlayerWithThumbnailBinding binding, |
|||
@NonNull final String videoUrl, |
|||
final float initialVolume, |
|||
final float thumbnailAspectRatio, |
|||
final String thumbnailUrl, |
|||
final LayoutExoCustomControlsBinding controlsBinding, |
|||
final VideoPlayerCallback videoPlayerCallback) { |
|||
this.context = context; |
|||
this.binding = binding; |
|||
this.initialVolume = initialVolume; |
|||
this.thumbnailAspectRatio = thumbnailAspectRatio; |
|||
this.thumbnailUrl = thumbnailUrl; |
|||
this.controlsBinding = controlsBinding; |
|||
this.videoPlayerCallback = videoPlayerCallback; |
|||
this.videoUrl = videoUrl; |
|||
this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram"); |
|||
bind(); |
|||
} |
|||
|
|||
private void bind() { |
|||
binding.thumbnailParent.setOnClickListener(v -> { |
|||
if (videoPlayerCallback != null) { |
|||
videoPlayerCallback.onThumbnailClick(); |
|||
} |
|||
loadPlayer(); |
|||
}); |
|||
setThumbnail(); |
|||
setupControls(); |
|||
} |
|||
|
|||
private void setThumbnail() { |
|||
binding.thumbnail.setAspectRatio(thumbnailAspectRatio); |
|||
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)) |
|||
.build(); |
|||
final DraweeController controller = Fresco.newDraweeControllerBuilder() |
|||
.setControllerListener(new BaseControllerListener<ImageInfo>() { |
|||
@Override |
|||
public void onFailure(final String id, final Throwable throwable) { |
|||
if (videoPlayerCallback != null) { |
|||
videoPlayerCallback.onThumbnailLoaded(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFinalImageSet(final String id, |
|||
final ImageInfo imageInfo, |
|||
final Animatable animatable) { |
|||
if (videoPlayerCallback != null) { |
|||
videoPlayerCallback.onThumbnailLoaded(); |
|||
} |
|||
} |
|||
}) |
|||
.setImageRequest(thumbnailRequest) |
|||
.build(); |
|||
binding.thumbnail.setController(controller); |
|||
} |
|||
|
|||
private void loadPlayer() { |
|||
if (videoUrl == null) return; |
|||
if (binding.root.getDisplayedChild() == 0) { |
|||
binding.root.showNext(); |
|||
} |
|||
if (videoPlayerCallback != null) { |
|||
videoPlayerCallback.onPlayerViewLoaded(); |
|||
} |
|||
player = (SimpleExoPlayer) binding.playerView.getPlayer(); |
|||
if (player != null) { |
|||
player.release(); |
|||
} |
|||
player = new SimpleExoPlayer.Builder(context) |
|||
.setLooper(Looper.getMainLooper()) |
|||
.build(); |
|||
player.addListener(this); |
|||
player.setVolume(initialVolume); |
|||
player.setPlayWhenReady(true); |
|||
player.setRepeatMode(Player.REPEAT_MODE_ALL); |
|||
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory); |
|||
final MediaItem mediaItem = MediaItem.fromUri(videoUrl); |
|||
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem); |
|||
player.setMediaSource(mediaSource); |
|||
setupControls(); |
|||
player.prepare(); |
|||
binding.playerView.setPlayer(player); |
|||
} |
|||
|
|||
private void setupControls() { |
|||
if (controlsBinding == null) return; |
|||
binding.playerView.setUseController(false); |
|||
if (player == null) { |
|||
enableControls(false); |
|||
controlsBinding.playPause.setEnabled(true); |
|||
controlsBinding.playPause.setOnClickListener(v -> binding.thumbnailParent.performClick()); |
|||
return; |
|||
} |
|||
enableControls(true); |
|||
final Handler handler = new Handler(); |
|||
final long initialDelay = 0; |
|||
final long recurringDelay = 60; |
|||
final Runnable positionChecker = new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
handler.removeCallbacks(this); |
|||
if (player == null) return; |
|||
final long currentPosition = player.getCurrentPosition(); |
|||
final long duration = player.getDuration(); |
|||
if (duration == TIME_UNSET) { |
|||
controlsBinding.timeline.setValueFrom(0); |
|||
controlsBinding.timeline.setValueTo(0); |
|||
controlsBinding.timeline.setEnabled(false); |
|||
return; |
|||
} |
|||
controlsBinding.timeline.setValue(Math.min(currentPosition, duration)); |
|||
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(currentPosition)); |
|||
handler.postDelayed(this, recurringDelay); |
|||
} |
|||
}; |
|||
updatePlayPauseDrawable(player.getPlayWhenReady()); |
|||
updateMuteIcon(player.getVolume()); |
|||
player.addListener(new Player.EventListener() { |
|||
@Override |
|||
public void onPlaybackStateChanged(final int state) { |
|||
switch (state) { |
|||
case Player.STATE_BUFFERING: |
|||
case STATE_IDLE: |
|||
case STATE_ENDED: |
|||
handler.removeCallbacks(positionChecker); |
|||
return; |
|||
case STATE_READY: |
|||
setupTimeline(); |
|||
handler.postDelayed(positionChecker, initialDelay); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) { |
|||
updatePlayPauseDrawable(playWhenReady); |
|||
} |
|||
}); |
|||
player.addAudioListener(new AudioListener() { |
|||
@Override |
|||
public void onVolumeChanged(final float volume) { |
|||
updateMuteIcon(volume); |
|||
} |
|||
}); |
|||
controlsBinding.timeline.addOnChangeListener((slider, value, fromUser) -> { |
|||
if (!fromUser) return; |
|||
long actualValue = (long) value; |
|||
if (actualValue < 0) { |
|||
actualValue = 0; |
|||
} else if (actualValue > player.getDuration()) { |
|||
actualValue = player.getDuration(); |
|||
} |
|||
player.seekTo(actualValue); |
|||
}); |
|||
controlsBinding.timeline.setLabelFormatter(value -> TextUtils.millisToTimeString((long) value)); |
|||
controlsBinding.playPause.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady())); |
|||
controlsBinding.mute.setOnClickListener(v -> toggleMute()); |
|||
controlsBinding.rewWithAmount.setOnClickListener(v -> { |
|||
final long positionMs = player.getCurrentPosition() - 5000; |
|||
player.seekTo(positionMs < 0 ? 0 : positionMs); |
|||
}); |
|||
controlsBinding.ffWithAmount.setOnClickListener(v -> { |
|||
long positionMs = player.getCurrentPosition() + 5000; |
|||
long duration = player.getDuration(); |
|||
if (duration == TIME_UNSET) { |
|||
duration = 0; |
|||
} |
|||
player.seekTo(Math.min(positionMs, duration)); |
|||
}); |
|||
controlsBinding.speed.setOnClickListener(this::showMenu); |
|||
} |
|||
|
|||
private void setupTimeline() { |
|||
final long duration = player.getDuration(); |
|||
controlsBinding.timeline.setEnabled(true); |
|||
controlsBinding.timeline.setValueFrom(0); |
|||
controlsBinding.timeline.setValueTo(duration); |
|||
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0)); |
|||
controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration)); |
|||
} |
|||
|
|||
private void enableControls(final boolean enable) { |
|||
controlsBinding.speed.setEnabled(enable); |
|||
controlsBinding.mute.setEnabled(enable); |
|||
controlsBinding.ffWithAmount.setEnabled(enable); |
|||
controlsBinding.rewWithAmount.setEnabled(enable); |
|||
controlsBinding.fromTime.setEnabled(enable); |
|||
controlsBinding.toTime.setEnabled(enable); |
|||
controlsBinding.playPause.setEnabled(enable); |
|||
} |
|||
|
|||
public void showMenu(View anchor) { |
|||
PopupMenu popup = getPopupMenu(anchor); |
|||
popup.show(); |
|||
} |
|||
|
|||
@NonNull |
|||
private PopupMenu getPopupMenu(final View anchor) { |
|||
if (speedPopup != null) { |
|||
return speedPopup; |
|||
} |
|||
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle); |
|||
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer); |
|||
speedPopup = new PopupMenu(themeWrapper, anchor); |
|||
speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu()); |
|||
speedPopup.setOnMenuItemClickListener(item -> { |
|||
float nextSpeed; |
|||
int textResId; |
|||
switch (item.getItemId()) { |
|||
case R.id.pt_two_five_x: |
|||
nextSpeed = 0.25f; |
|||
textResId = R.string.pt_two_five_x; |
|||
break; |
|||
case R.id.pt_five_x: |
|||
nextSpeed = 0.5f; |
|||
textResId = R.string.pt_five_x; |
|||
break; |
|||
case R.id.pt_seven_five_x: |
|||
nextSpeed = 0.75f; |
|||
textResId = R.string.pt_seven_five_x; |
|||
break; |
|||
case R.id.one_x: |
|||
nextSpeed = 1f; |
|||
textResId = R.string.one_x; |
|||
break; |
|||
case R.id.one_pt_two_five_x: |
|||
nextSpeed = 1.25f; |
|||
textResId = R.string.one_pt_two_five_x; |
|||
break; |
|||
case R.id.one_pt_five_x: |
|||
nextSpeed = 1.5f; |
|||
textResId = R.string.one_pt_five_x; |
|||
break; |
|||
case R.id.two_x: |
|||
nextSpeed = 2f; |
|||
textResId = R.string.two_x; |
|||
break; |
|||
default: |
|||
nextSpeed = 1; |
|||
textResId = R.string.one_x; |
|||
} |
|||
player.setPlaybackParameters(new PlaybackParameters(nextSpeed)); |
|||
controlsBinding.speed.setText(textResId); |
|||
return true; |
|||
}); |
|||
return speedPopup; |
|||
} |
|||
|
|||
private void updateMuteIcon(final float volume) { |
|||
if (volume == 0) { |
|||
controlsBinding.mute.setIconResource(R.drawable.ic_volume_off_24); |
|||
return; |
|||
} |
|||
controlsBinding.mute.setIconResource(R.drawable.ic_volume_up_24); |
|||
} |
|||
|
|||
private void updatePlayPauseDrawable(final boolean playWhenReady) { |
|||
if (playWhenReady) { |
|||
controlsBinding.playPause.setIconResource(R.drawable.ic_pause_24); |
|||
return; |
|||
} |
|||
controlsBinding.playPause.setIconResource(R.drawable.ic_play_arrow_24); |
|||
} |
|||
|
|||
@Override |
|||
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) { |
|||
if (videoPlayerCallback == null) return; |
|||
if (playWhenReady) { |
|||
videoPlayerCallback.onPlay(); |
|||
return; |
|||
} |
|||
videoPlayerCallback.onPause(); |
|||
} |
|||
|
|||
@Override |
|||
public void onPlayerError(final ExoPlaybackException error) { |
|||
Log.e(TAG, "onPlayerError", error); |
|||
} |
|||
|
|||
public float toggleMute() { |
|||
if (player == null) return 0; |
|||
final float vol = player.getVolume() == 0f ? 1f : 0f; |
|||
player.setVolume(vol); |
|||
return vol; |
|||
} |
|||
|
|||
public void togglePlayback() { |
|||
if (player == null) return; |
|||
final int playbackState = player.getPlaybackState(); |
|||
if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) return; |
|||
final boolean playWhenReady = player.getPlayWhenReady(); |
|||
player.setPlayWhenReady(!playWhenReady); |
|||
} |
|||
|
|||
public void releasePlayer() { |
|||
if (player == null) return; |
|||
player.release(); |
|||
player = null; |
|||
} |
|||
|
|||
public void pause() { |
|||
if (player == null) return; |
|||
player.pause(); |
|||
} |
|||
|
|||
public interface VideoPlayerCallback { |
|||
void onThumbnailLoaded(); |
|||
|
|||
void onThumbnailClick(); |
|||
|
|||
void onPlayerViewLoaded(); |
|||
|
|||
void onPlay(); |
|||
|
|||
void onPause(); |
|||
} |
|||
} |
@ -0,0 +1,98 @@ |
|||
/* |
|||
* Copyright (c) Facebook, Inc. and its affiliates. |
|||
* |
|||
* This source code is licensed under the MIT license found in the |
|||
* LICENSE file in the root directory of this source tree. |
|||
*/ |
|||
|
|||
package awais.instagrabber.customviews.drawee; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.graphics.Matrix; |
|||
import android.util.AttributeSet; |
|||
import android.view.MotionEvent; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import com.facebook.drawee.generic.GenericDraweeHierarchy; |
|||
|
|||
import awais.instagrabber.customviews.VerticalDragHelper; |
|||
import awais.instagrabber.customviews.VerticalDragHelper.OnVerticalDragListener; |
|||
|
|||
public class DraggableZoomableDraweeView extends ZoomableDraweeView { |
|||
private static final String TAG = "DraggableZoomableDV"; |
|||
|
|||
private VerticalDragHelper verticalDragHelper; |
|||
|
|||
public DraggableZoomableDraweeView(final Context context, final GenericDraweeHierarchy hierarchy) { |
|||
super(context, hierarchy); |
|||
verticalDragHelper = new VerticalDragHelper(this); |
|||
} |
|||
|
|||
public DraggableZoomableDraweeView(final Context context) { |
|||
super(context); |
|||
verticalDragHelper = new VerticalDragHelper(this); |
|||
} |
|||
|
|||
public DraggableZoomableDraweeView(final Context context, final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
verticalDragHelper = new VerticalDragHelper(this); |
|||
} |
|||
|
|||
public DraggableZoomableDraweeView(final Context context, final AttributeSet attrs, final int defStyle) { |
|||
super(context, attrs, defStyle); |
|||
verticalDragHelper = new VerticalDragHelper(this); |
|||
} |
|||
|
|||
public void setOnVerticalDragListener(@NonNull final OnVerticalDragListener onVerticalDragListener) { |
|||
verticalDragHelper.setOnVerticalDragListener(onVerticalDragListener); |
|||
} |
|||
|
|||
private int lastPointerCount; |
|||
private int lastNewPointerCount; |
|||
private boolean wasTransformCorrected; |
|||
|
|||
@Override |
|||
protected void onTransformEnd(final Matrix transform) { |
|||
super.onTransformEnd(transform); |
|||
final AnimatedZoomableController zoomableController = (AnimatedZoomableController) getZoomableController(); |
|||
final TransformGestureDetector detector = zoomableController.getDetector(); |
|||
lastNewPointerCount = detector.getNewPointerCount(); |
|||
lastPointerCount = detector.getPointerCount(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onTranslationLimited(final float offsetLeft, final float offsetTop) { |
|||
super.onTranslationLimited(offsetLeft, offsetTop); |
|||
wasTransformCorrected = offsetTop != 0; |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
@Override |
|||
public boolean onTouchEvent(final MotionEvent event) { |
|||
boolean superResult = false; |
|||
if (verticalDragHelper.isDragging()) { |
|||
final boolean onDragTouch = verticalDragHelper.onDragTouch(event); |
|||
if (onDragTouch) { |
|||
return true; |
|||
} |
|||
} |
|||
if (!verticalDragHelper.isDragging()) { |
|||
superResult = super.onTouchEvent(event); |
|||
if (wasTransformCorrected |
|||
&& (lastPointerCount == 1 || lastPointerCount == 0) |
|||
&& (lastNewPointerCount == 1 || lastNewPointerCount == 0)) { |
|||
final boolean onDragTouch = verticalDragHelper.onDragTouch(event); |
|||
if (onDragTouch) { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
final boolean gestureListenerResult = verticalDragHelper.onGestureTouchEvent(event); |
|||
if (gestureListenerResult) { |
|||
return true; |
|||
} |
|||
return superResult; |
|||
} |
|||
} |
@ -0,0 +1,50 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.FeedModel; |
|||
|
|||
public class PostFetcher { |
|||
private final PostFetchService postFetchService; |
|||
private final FetchListener<List<FeedModel>> fetchListener; |
|||
private boolean fetching; |
|||
|
|||
public PostFetcher(final PostFetchService postFetchService, |
|||
final FetchListener<List<FeedModel>> fetchListener) { |
|||
this.postFetchService = postFetchService; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
public void fetch() { |
|||
fetch(null); |
|||
} |
|||
|
|||
public void fetchNextPage() { |
|||
fetch(postFetchService.getNextCursor()); |
|||
} |
|||
|
|||
public void fetch(final String cursor) { |
|||
fetching = true; |
|||
postFetchService.fetch(cursor, result -> { |
|||
fetching = false; |
|||
fetchListener.onResult(result); |
|||
}); |
|||
} |
|||
|
|||
public boolean isFetching() { |
|||
return fetching; |
|||
} |
|||
|
|||
public boolean hasMore() { |
|||
return postFetchService.hasNextPage(); |
|||
} |
|||
|
|||
public interface PostFetchService { |
|||
void fetch(String cursor, FetchListener<List<FeedModel>> fetchListener); |
|||
|
|||
String getNextCursor(); |
|||
|
|||
boolean hasNextPage(); |
|||
} |
|||
} |
@ -0,0 +1,51 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import android.os.Handler; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
public final class RecyclerLazyLoaderAtBottom extends RecyclerView.OnScrollListener { |
|||
|
|||
@NonNull |
|||
private final RecyclerView.LayoutManager layoutManager; |
|||
private final LazyLoadListener lazyLoadListener; |
|||
private int currentPage; |
|||
private int previousItemCount; |
|||
private boolean loading; |
|||
|
|||
public RecyclerLazyLoaderAtBottom(@NonNull final RecyclerView.LayoutManager layoutManager, |
|||
final LazyLoadListener lazyLoadListener) { |
|||
this.layoutManager = layoutManager; |
|||
this.lazyLoadListener = lazyLoadListener; |
|||
} |
|||
|
|||
@Override |
|||
public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) { |
|||
super.onScrollStateChanged(recyclerView, newState); |
|||
final int itemCount = layoutManager.getItemCount(); |
|||
if (itemCount > previousItemCount) { |
|||
loading = false; |
|||
} |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public int getCurrentPage() { |
|||
return currentPage; |
|||
} |
|||
|
|||
public void resetState() { |
|||
currentPage = 0; |
|||
previousItemCount = 0; |
|||
loading = true; |
|||
} |
|||
|
|||
public interface LazyLoadListener { |
|||
void onLoadMore(final int page); |
|||
} |
|||
} |
@ -0,0 +1,219 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.Window; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.DialogFragment; |
|||
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding; |
|||
import awais.instagrabber.models.PostsLayoutPreferences; |
|||
import awais.instagrabber.utils.Constants; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class PostsLayoutPreferencesDialogFragment extends DialogFragment { |
|||
|
|||
private final PostsLayoutPreferences.Builder preferencesBuilder; |
|||
@NonNull |
|||
private final OnApplyListener onApplyListener; |
|||
private DialogPostLayoutPreferencesBinding binding; |
|||
private Context context; |
|||
|
|||
public PostsLayoutPreferencesDialogFragment(@NonNull final OnApplyListener onApplyListener) { |
|||
final PostsLayoutPreferences preferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT)); |
|||
this.preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences); |
|||
this.onApplyListener = onApplyListener; |
|||
} |
|||
|
|||
@Override |
|||
public void onAttach(@NonNull final Context context) { |
|||
super.onAttach(context); |
|||
this.context = context; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
|||
binding = DialogPostLayoutPreferencesBinding.inflate(LayoutInflater.from(context), null, false); |
|||
init(); |
|||
return new MaterialAlertDialogBuilder(context) |
|||
.setView(binding.getRoot()) |
|||
.setPositiveButton(R.string.apply, (dialog, which) -> { |
|||
final PostsLayoutPreferences preferences = preferencesBuilder.build(); |
|||
final String json = preferences.getJson(); |
|||
settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, json); |
|||
onApplyListener.onApply(preferences); |
|||
}) |
|||
.create(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStart() { |
|||
super.onStart(); |
|||
final Dialog dialog = getDialog(); |
|||
if (dialog == null) return; |
|||
final Window window = dialog.getWindow(); |
|||
if (window == null) return; |
|||
window.setWindowAnimations(R.style.dialog_window_animation); |
|||
} |
|||
|
|||
private void init() { |
|||
initLayoutToggle(); |
|||
if (preferencesBuilder.getType() != PostsLayoutPreferences.PostsLayoutType.LINEAR) { |
|||
initStaggeredOrGridOptions(); |
|||
} |
|||
} |
|||
|
|||
private void initStaggeredOrGridOptions() { |
|||
initColCountToggle(); |
|||
initNamesToggle(); |
|||
initAvatarsToggle(); |
|||
initCornersToggle(); |
|||
initGapToggle(); |
|||
} |
|||
|
|||
private void initLayoutToggle() { |
|||
binding.layoutToggle.check(getSelectedLayoutId()); |
|||
// binding.staggeredOrGridOptions.setVisibility(getSelectedLayoutId() != R.id.layout_linear ? View.VISIBLE : View.GONE); |
|||
binding.layoutToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { |
|||
if (isChecked) { |
|||
switch (checkedId) { |
|||
case R.id.layout_linear: |
|||
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.LINEAR); |
|||
binding.staggeredOrGridOptions.setVisibility(View.GONE); |
|||
break; |
|||
case R.id.layout_staggered: |
|||
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID); |
|||
binding.staggeredOrGridOptions.setVisibility(View.VISIBLE); |
|||
initStaggeredOrGridOptions(); |
|||
break; |
|||
case R.id.layout_grid: |
|||
default: |
|||
preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.GRID); |
|||
binding.staggeredOrGridOptions.setVisibility(View.VISIBLE); |
|||
initStaggeredOrGridOptions(); |
|||
break; |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void initColCountToggle() { |
|||
binding.colCountToggle.check(getSelectedColCountId()); |
|||
binding.colCountToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { |
|||
if (!isChecked) return; |
|||
switch (checkedId) { |
|||
case R.id.col_count_two: |
|||
preferencesBuilder.setColCount(2); |
|||
break; |
|||
case R.id.col_count_three: |
|||
default: |
|||
preferencesBuilder.setColCount(3); |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void initAvatarsToggle() { |
|||
binding.showAvatarToggle.setChecked(preferencesBuilder.isAvatarVisible()); |
|||
binding.avatarSizeToggle.check(getSelectedAvatarSizeId()); |
|||
binding.showAvatarToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { |
|||
preferencesBuilder.setAvatarVisible(isChecked); |
|||
binding.labelAvatarSize.setVisibility(isChecked ? View.VISIBLE : View.GONE); |
|||
binding.avatarSizeToggle.setVisibility(isChecked ? View.VISIBLE : View.GONE); |
|||
}); |
|||
binding.labelAvatarSize.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE); |
|||
binding.avatarSizeToggle.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE); |
|||
binding.avatarSizeToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { |
|||
if (!isChecked) return; |
|||
switch (checkedId) { |
|||
case R.id.avatar_size_tiny: |
|||
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.TINY); |
|||
break; |
|||
case R.id.avatar_size_small: |
|||
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.SMALL); |
|||
break; |
|||
case R.id.avatar_size_regular: |
|||
default: |
|||
preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.REGULAR); |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void initNamesToggle() { |
|||
binding.showNamesToggle.setChecked(preferencesBuilder.isNameVisible()); |
|||
binding.showNamesToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setNameVisible(isChecked)); |
|||
} |
|||
|
|||
private void initCornersToggle() { |
|||
binding.cornersToggle.check(getSelectedCornersId()); |
|||
binding.cornersToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { |
|||
if (!isChecked) return; |
|||
if (checkedId == R.id.corners_round) { |
|||
preferencesBuilder.setHasRoundedCorners(true); |
|||
return; |
|||
} |
|||
preferencesBuilder.setHasRoundedCorners(false); |
|||
}); |
|||
} |
|||
|
|||
private void initGapToggle() { |
|||
binding.showGapToggle.setChecked(preferencesBuilder.getHasGap()); |
|||
binding.showGapToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setHasGap(isChecked)); |
|||
} |
|||
|
|||
private int getSelectedLayoutId() { |
|||
switch (preferencesBuilder.getType()) { |
|||
case STAGGERED_GRID: |
|||
return R.id.layout_staggered; |
|||
case LINEAR: |
|||
return R.id.layout_linear; |
|||
default: |
|||
case GRID: |
|||
return R.id.layout_grid; |
|||
} |
|||
} |
|||
|
|||
private int getSelectedColCountId() { |
|||
switch (preferencesBuilder.getColCount()) { |
|||
case 2: |
|||
return R.id.col_count_two; |
|||
case 3: |
|||
default: |
|||
return R.id.col_count_three; |
|||
} |
|||
} |
|||
|
|||
private int getSelectedCornersId() { |
|||
if (preferencesBuilder.getHasRoundedCorners()) { |
|||
return R.id.corners_round; |
|||
} |
|||
return R.id.corners_square; |
|||
} |
|||
|
|||
private int getSelectedAvatarSizeId() { |
|||
switch (preferencesBuilder.getProfilePicSize()) { |
|||
case TINY: |
|||
return R.id.avatar_size_tiny; |
|||
case SMALL: |
|||
return R.id.avatar_size_small; |
|||
case REGULAR: |
|||
default: |
|||
return R.id.avatar_size_regular; |
|||
} |
|||
} |
|||
|
|||
public interface OnApplyListener { |
|||
void onApply(final PostsLayoutPreferences preferences); |
|||
} |
|||
} |
@ -1,342 +1,340 @@ |
|||
package awais.instagrabber.fragments; |
|||
|
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.content.pm.PackageManager; |
|||
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.fragment.app.Fragment; |
|||
import androidx.fragment.app.FragmentActivity; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
import androidx.navigation.NavDirections; |
|||
import androidx.navigation.fragment.NavHostFragment; |
|||
import androidx.viewpager2.widget.ViewPager2; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.PostViewAdapter; |
|||
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; |
|||
import awais.instagrabber.asyncs.PostFetcher; |
|||
import awais.instagrabber.asyncs.i.iPostFetcher; |
|||
import awais.instagrabber.databinding.FragmentPostViewBinding; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.DownloadUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.viewmodels.ViewerPostViewModel; |
|||
import awais.instagrabber.webservices.MediaService; |
|||
import awais.instagrabber.webservices.ServiceCallback; |
|||
|
|||
import static androidx.core.content.ContextCompat.checkSelfPermission; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class PostViewFragment extends Fragment { |
|||
private static final String TAG = "PostViewFragment"; |
|||
private static final String COOKIE = settingsHelper.getString(Constants.COOKIE); |
|||
|
|||
private FragmentActivity fragmentActivity; |
|||
private FragmentPostViewBinding binding; |
|||
private ViewPager2 root; |
|||
private boolean shouldRefresh = true; |
|||
private ViewerPostViewModel viewerPostViewModel; |
|||
private boolean isId; |
|||
private int currentPostIndex; |
|||
private List<String> idOrCodeList; |
|||
private boolean hasInitialResult = false; |
|||
private PostViewAdapter adapter; |
|||
private boolean session; |
|||
private MediaService mediaService; |
|||
|
|||
private FetchListener<ViewerPostModel[]> pfl = result -> { |
|||
if (result == null) return; |
|||
if (result.length <= 0) return; |
|||
final List<ViewerPostModelWrapper> viewerPostModels = viewerPostViewModel.getList().getValue(); |
|||
final List<ViewerPostModelWrapper> temp = viewerPostModels == null ? new ArrayList<>(idOrCodeList.size()) |
|||
: new ArrayList<>(viewerPostModels); |
|||
final ViewerPostModel firstPost = result[0]; |
|||
if (firstPost == null) return; |
|||
String idOrCode = isId ? firstPost.getPostId() : firstPost.getShortCode(); |
|||
if (idOrCode == null) return; |
|||
if (isId) { |
|||
// the post id is appended with `_` in the result |
|||
idOrCode = idOrCode.substring(0, idOrCode.indexOf('_')); |
|||
} |
|||
final int index = idOrCodeList.indexOf(idOrCode); |
|||
if (index < 0) return; |
|||
final ViewerPostModelWrapper viewerPostModelWrapper = temp.get(index); |
|||
viewerPostModelWrapper.setViewerPostModels(result); |
|||
temp.set(index, viewerPostModelWrapper); |
|||
viewerPostViewModel.getList().setValue(temp); |
|||
adapter.notifyItemChanged(index); |
|||
if (!hasInitialResult) { |
|||
Log.d(TAG, "setting delayed position to: " + currentPostIndex); |
|||
binding.getRoot() |
|||
.postDelayed(() -> binding.getRoot().setCurrentItem(currentPostIndex), 200); |
|||
} |
|||
hasInitialResult = true; |
|||
}; |
|||
private MentionClickListener mentionListener = (view, text, isHashtag, isLocation) -> { |
|||
if (isHashtag) { |
|||
final NavDirections action = PostViewFragmentDirections |
|||
.actionGlobalHashTagFragment(text); |
|||
NavHostFragment.findNavController(this).navigate(action); |
|||
return; |
|||
} |
|||
if (isLocation) { |
|||
final NavDirections action = PostViewFragmentDirections |
|||
.actionGlobalLocationFragment(text); |
|||
NavHostFragment.findNavController(this).navigate(action); |
|||
return; |
|||
} |
|||
final NavDirections action = PostViewFragmentDirections |
|||
.actionGlobalProfileFragment("@" + text); |
|||
NavHostFragment.findNavController(this).navigate(action); |
|||
}; |
|||
private OnPostViewChildViewClickListener clickListener = (v, wrapper, postPosition, childPosition) -> { |
|||
final ViewerPostModel postModel = wrapper.getViewerPostModels()[0]; |
|||
final String username = postModel.getProfileModel().getUsername(); |
|||
final int id = v.getId(); |
|||
switch (id) { |
|||
case R.id.viewerCaption: |
|||
break; |
|||
case R.id.btnComments: |
|||
String postId = postModel.getPostId(); |
|||
if (postId.contains("_")) postId = postId.substring(0, postId.indexOf("_")); |
|||
final NavDirections commentsAction = PostViewFragmentDirections.actionGlobalCommentsViewerFragment( |
|||
postModel.getShortCode(), |
|||
postId, |
|||
postModel.getProfileModel().getId() |
|||
); |
|||
NavHostFragment.findNavController(this).navigate(commentsAction); |
|||
break; |
|||
case R.id.btnDownload: |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
if (checkSelfPermission(context, |
|||
DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { |
|||
showDownloadDialog(Arrays.asList(wrapper.getViewerPostModels()), |
|||
childPosition, |
|||
username); |
|||
return; |
|||
} |
|||
requestPermissions(DownloadUtils.PERMS, 8020); |
|||
break; |
|||
case R.id.ivProfilePic: |
|||
case R.id.title: |
|||
mentionListener.onClick(null, username, false, false); |
|||
break; |
|||
case R.id.btnLike: |
|||
if (mediaService != null) { |
|||
final String userId = CookieUtils.getUserIdFromCookie(COOKIE); |
|||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE); |
|||
v.setEnabled(false); |
|||
final ServiceCallback<Boolean> likeCallback = new ServiceCallback<Boolean>() { |
|||
@Override |
|||
public void onSuccess(final Boolean result) { |
|||
v.setEnabled(true); |
|||
if (result) { |
|||
postModel.setManualLike(!postModel.getLike()); |
|||
adapter.notifyItemChanged(postPosition); |
|||
return; |
|||
} |
|||
Log.e(TAG, "like/unlike unsuccessful!"); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
v.setEnabled(true); |
|||
Log.e(TAG, "Error during like/unlike", t); |
|||
} |
|||
}; |
|||
if (!postModel.getLike()) { |
|||
mediaService.like(postModel.getPostId(), userId, csrfToken, likeCallback); |
|||
} else { |
|||
mediaService.unlike(postModel.getPostId(), userId, csrfToken, likeCallback); |
|||
} |
|||
} |
|||
break; |
|||
case R.id.btnBookmark: |
|||
if (mediaService != null) { |
|||
final String userId = CookieUtils.getUserIdFromCookie(COOKIE); |
|||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE); |
|||
v.setEnabled(false); |
|||
final ServiceCallback<Boolean> saveCallback = new ServiceCallback<Boolean>() { |
|||
@Override |
|||
public void onSuccess(final Boolean result) { |
|||
v.setEnabled(true); |
|||
if (result) { |
|||
postModel.setBookmarked(!postModel.getBookmark()); |
|||
adapter.notifyItemChanged(postPosition); |
|||
return; |
|||
} |
|||
Log.e(TAG, "save/unsave unsuccessful!"); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
v.setEnabled(true); |
|||
Log.e(TAG, "Error during save/unsave", t); |
|||
} |
|||
}; |
|||
if (!postModel.getBookmark()) { |
|||
mediaService.save(postModel.getPostId(), userId, csrfToken, saveCallback); |
|||
} else { |
|||
mediaService.unsave(postModel.getPostId(), userId, csrfToken, saveCallback); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
}; |
|||
private PostViewAdapter.OnPostCaptionLongClickListener captionLongClickListener = text -> { |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
Utils.copyText(context, text); |
|||
}; |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
fragmentActivity = getActivity(); |
|||
mediaService = MediaService.getInstance(); |
|||
} |
|||
|
|||
@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 = FragmentPostViewBinding.inflate(inflater, container, false); |
|||
root = binding.getRoot(); |
|||
setupViewPager(); |
|||
return root; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
if (!shouldRefresh) return; |
|||
init(); |
|||
shouldRefresh = false; |
|||
} |
|||
|
|||
private void setupViewPager() { |
|||
viewerPostViewModel = new ViewModelProvider(fragmentActivity) |
|||
.get(ViewerPostViewModel.class); |
|||
adapter = new PostViewAdapter(clickListener, captionLongClickListener, mentionListener); |
|||
root.setAdapter(adapter); |
|||
root.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { |
|||
|
|||
@Override |
|||
public void onPageSelected(final int position) { |
|||
// Log.d(TAG, "onPageSelected: " + position + ", hasInitialResult: " + hasInitialResult); |
|||
if (!hasInitialResult) { |
|||
return; |
|||
} |
|||
currentPostIndex = position; |
|||
fetchPost(); |
|||
} |
|||
}); |
|||
viewerPostViewModel.getList().observe(fragmentActivity, list -> adapter.submitList(list)); |
|||
} |
|||
|
|||
private void init() { |
|||
if (getArguments() == null) return; |
|||
final PostViewFragmentArgs fragmentArgs = PostViewFragmentArgs.fromBundle(getArguments()); |
|||
final String[] idOrCodeArray = fragmentArgs.getIdOrCodeArray(); |
|||
if (idOrCodeArray.length == 0) return; |
|||
currentPostIndex = fragmentArgs.getIndex(); |
|||
if (currentPostIndex < 0) return; |
|||
if (currentPostIndex >= idOrCodeArray.length) return; |
|||
idOrCodeList = Arrays.asList(idOrCodeArray); |
|||
viewerPostViewModel.getList().setValue(createPlaceholderModels(idOrCodeArray.length)); |
|||
isId = fragmentArgs.getIsId(); |
|||
fetchPost(); |
|||
} |
|||
|
|||
private List<ViewerPostModelWrapper> createPlaceholderModels(final int size) { |
|||
final List<ViewerPostModelWrapper> viewerPostModels = new ArrayList<>(size); |
|||
for (int i = 0; i < size; i++) { |
|||
// viewerPostModels.add(new ViewerPostModel[]{ViewerPostModel.getDefaultModel(-i, "")}); |
|||
viewerPostModels.add(new ViewerPostModelWrapper(i, null)); |
|||
} |
|||
return viewerPostModels; |
|||
} |
|||
|
|||
private void fetchPost() { |
|||
// Log.d(TAG, "fetchPost, currentPostIndex: " + currentPostIndex); |
|||
final List<ViewerPostModelWrapper> list = viewerPostViewModel.getList().getValue(); |
|||
if (list != null) { |
|||
final ViewerPostModelWrapper viewerPostModels = list.get(currentPostIndex); |
|||
if (viewerPostModels != null && viewerPostModels |
|||
.getViewerPostModels() != null && viewerPostModels |
|||
.getViewerPostModels().length > 0) { |
|||
Log.d(TAG, "returning without fetching"); |
|||
return; |
|||
} |
|||
} |
|||
if (currentPostIndex >= idOrCodeList.size() || currentPostIndex < 0) return; |
|||
final String idOrShortCode = idOrCodeList.get(currentPostIndex); |
|||
if (isId) { |
|||
new iPostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
return; |
|||
} |
|||
new PostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void showDownloadDialog(final List<ViewerPostModel> postModels, |
|||
final int childPosition, |
|||
final String username) { |
|||
final List<ViewerPostModel> postModelsToDownload = new ArrayList<>(); |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
if (!session && postModels.size() > 1) { |
|||
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))); |
|||
} |
|||
} |
|||
} |
|||
// package awais.instagrabber.fragments; |
|||
// |
|||
// import android.content.Context; |
|||
// import android.content.DialogInterface; |
|||
// import android.content.pm.PackageManager; |
|||
// 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.fragment.app.Fragment; |
|||
// import androidx.fragment.app.FragmentActivity; |
|||
// import androidx.lifecycle.ViewModelProvider; |
|||
// import androidx.navigation.NavDirections; |
|||
// import androidx.navigation.fragment.NavHostFragment; |
|||
// import androidx.viewpager2.widget.ViewPager2; |
|||
// |
|||
// import java.util.ArrayList; |
|||
// import java.util.Arrays; |
|||
// import java.util.Collections; |
|||
// import java.util.List; |
|||
// |
|||
// import awais.instagrabber.R; |
|||
// import awais.instagrabber.adapters.PostViewAdapter; |
|||
// import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; |
|||
// import awais.instagrabber.asyncs.PostFetcher; |
|||
// import awais.instagrabber.asyncs.i.iPostFetcher; |
|||
// import awais.instagrabber.databinding.FragmentPostViewBinding; |
|||
// import awais.instagrabber.interfaces.FetchListener; |
|||
// import awais.instagrabber.interfaces.MentionClickListener; |
|||
// import awais.instagrabber.models.FeedModel; |
|||
// import awais.instagrabber.models.ViewerPostModel; |
|||
// import awais.instagrabber.models.ViewerPostModelWrapper; |
|||
// import awais.instagrabber.models.enums.DownloadMethod; |
|||
// import awais.instagrabber.utils.Constants; |
|||
// import awais.instagrabber.utils.CookieUtils; |
|||
// import awais.instagrabber.utils.DownloadUtils; |
|||
// import awais.instagrabber.utils.Utils; |
|||
// import awais.instagrabber.viewmodels.ViewerPostViewModel; |
|||
// import awais.instagrabber.webservices.MediaService; |
|||
// import awais.instagrabber.webservices.ServiceCallback; |
|||
// |
|||
// import static androidx.core.content.ContextCompat.checkSelfPermission; |
|||
// import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
// |
|||
// public class PostViewFragment extends Fragment { |
|||
// private static final String TAG = "PostViewFragment"; |
|||
// private static final String COOKIE = settingsHelper.getString(Constants.COOKIE); |
|||
// |
|||
// private FragmentActivity fragmentActivity; |
|||
// private FragmentPostViewBinding binding; |
|||
// private ViewPager2 root; |
|||
// private boolean shouldRefresh = true; |
|||
// private ViewerPostViewModel viewerPostViewModel; |
|||
// private boolean isId; |
|||
// private int currentPostIndex; |
|||
// private List<String> idOrCodeList; |
|||
// private boolean hasInitialResult = false; |
|||
// private PostViewAdapter adapter; |
|||
// private boolean session; |
|||
// private MediaService mediaService; |
|||
// |
|||
// private FetchListener<FeedModel> pfl = result -> { |
|||
// if (result == null) return; |
|||
// final List<ViewerPostModelWrapper> viewerPostModels = viewerPostViewModel.getList().getValue(); |
|||
// final List<ViewerPostModelWrapper> temp = viewerPostModels == null ? new ArrayList<>(idOrCodeList.size()) |
|||
// : new ArrayList<>(viewerPostModels); |
|||
// String idOrCode = isId ? result.getPostId() : result.getShortCode(); |
|||
// if (idOrCode == null) return; |
|||
// if (isId) { |
|||
// // the post id is appended with `_` in the result |
|||
// idOrCode = idOrCode.substring(0, idOrCode.indexOf('_')); |
|||
// } |
|||
// final int index = idOrCodeList.indexOf(idOrCode); |
|||
// if (index < 0) return; |
|||
// final ViewerPostModelWrapper viewerPostModelWrapper = temp.get(index); |
|||
// viewerPostModelWrapper.setViewerPostModels(result.getSliderItems() == null ? Collections.emptyList() : result.getSliderItems()); |
|||
// temp.set(index, viewerPostModelWrapper); |
|||
// viewerPostViewModel.getList().setValue(temp); |
|||
// adapter.notifyItemChanged(index); |
|||
// if (!hasInitialResult) { |
|||
// Log.d(TAG, "setting delayed position to: " + currentPostIndex); |
|||
// binding.getRoot() |
|||
// .postDelayed(() -> binding.getRoot().setCurrentItem(currentPostIndex), 200); |
|||
// } |
|||
// hasInitialResult = true; |
|||
// }; |
|||
// private MentionClickListener mentionListener = (view, text, isHashtag, isLocation) -> { |
|||
// if (isHashtag) { |
|||
// final NavDirections action = PostViewFragmentDirections |
|||
// .actionGlobalHashTagFragment(text); |
|||
// NavHostFragment.findNavController(this).navigate(action); |
|||
// return; |
|||
// } |
|||
// if (isLocation) { |
|||
// final NavDirections action = PostViewFragmentDirections |
|||
// .actionGlobalLocationFragment(text); |
|||
// NavHostFragment.findNavController(this).navigate(action); |
|||
// return; |
|||
// } |
|||
// final NavDirections action = PostViewFragmentDirections |
|||
// .actionGlobalProfileFragment("@" + text); |
|||
// NavHostFragment.findNavController(this).navigate(action); |
|||
// }; |
|||
// private OnPostViewChildViewClickListener clickListener = (v, wrapper, postPosition, childPosition) -> { |
|||
// final ViewerPostModel postModel = wrapper.getViewerPostModels().get(0); |
|||
// final String username = postModel.getProfileModel().getUsername(); |
|||
// final int id = v.getId(); |
|||
// switch (id) { |
|||
// case R.id.viewerCaption: |
|||
// break; |
|||
// case R.id.btnComments: |
|||
// String postId = postModel.getPostId(); |
|||
// if (postId.contains("_")) postId = postId.substring(0, postId.indexOf("_")); |
|||
// final NavDirections commentsAction = PostViewFragmentDirections.actionGlobalCommentsViewerFragment( |
|||
// postModel.getShortCode(), |
|||
// postId, |
|||
// postModel.getProfileModel().getId() |
|||
// ); |
|||
// NavHostFragment.findNavController(this).navigate(commentsAction); |
|||
// break; |
|||
// case R.id.btnDownload: |
|||
// final Context context = getContext(); |
|||
// if (context == null) return; |
|||
// if (checkSelfPermission(context, |
|||
// DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { |
|||
// showDownloadDialog(wrapper.getViewerPostModels(), |
|||
// childPosition, |
|||
// username); |
|||
// return; |
|||
// } |
|||
// requestPermissions(DownloadUtils.PERMS, 8020); |
|||
// break; |
|||
// case R.id.ivProfilePic: |
|||
// case R.id.title: |
|||
// mentionListener.onClick(null, username, false, false); |
|||
// break; |
|||
// case R.id.btnLike: |
|||
// if (mediaService != null) { |
|||
// final String userId = CookieUtils.getUserIdFromCookie(COOKIE); |
|||
// final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE); |
|||
// v.setEnabled(false); |
|||
// final ServiceCallback<Boolean> likeCallback = new ServiceCallback<Boolean>() { |
|||
// @Override |
|||
// public void onSuccess(final Boolean result) { |
|||
// v.setEnabled(true); |
|||
// if (result) { |
|||
// postModel.setManualLike(!postModel.getLike()); |
|||
// adapter.notifyItemChanged(postPosition); |
|||
// return; |
|||
// } |
|||
// Log.e(TAG, "like/unlike unsuccessful!"); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onFailure(final Throwable t) { |
|||
// v.setEnabled(true); |
|||
// Log.e(TAG, "Error during like/unlike", t); |
|||
// } |
|||
// }; |
|||
// if (!postModel.getLike()) { |
|||
// mediaService.like(postModel.getPostId(), userId, csrfToken, likeCallback); |
|||
// } else { |
|||
// mediaService.unlike(postModel.getPostId(), userId, csrfToken, likeCallback); |
|||
// } |
|||
// } |
|||
// break; |
|||
// case R.id.btnBookmark: |
|||
// if (mediaService != null) { |
|||
// final String userId = CookieUtils.getUserIdFromCookie(COOKIE); |
|||
// final String csrfToken = CookieUtils.getCsrfTokenFromCookie(COOKIE); |
|||
// v.setEnabled(false); |
|||
// final ServiceCallback<Boolean> saveCallback = new ServiceCallback<Boolean>() { |
|||
// @Override |
|||
// public void onSuccess(final Boolean result) { |
|||
// v.setEnabled(true); |
|||
// if (result) { |
|||
// // postModel.setBookmarked(!postModel.isSaved()); |
|||
// adapter.notifyItemChanged(postPosition); |
|||
// return; |
|||
// } |
|||
// Log.e(TAG, "save/unsave unsuccessful!"); |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onFailure(final Throwable t) { |
|||
// v.setEnabled(true); |
|||
// Log.e(TAG, "Error during save/unsave", t); |
|||
// } |
|||
// }; |
|||
// if (!postModel.isSaved()) { |
|||
// mediaService.save(postModel.getPostId(), userId, csrfToken, saveCallback); |
|||
// } else { |
|||
// mediaService.unsave(postModel.getPostId(), userId, csrfToken, saveCallback); |
|||
// } |
|||
// } |
|||
// break; |
|||
// } |
|||
// }; |
|||
// private PostViewAdapter.OnPostCaptionLongClickListener captionLongClickListener = text -> { |
|||
// final Context context = getContext(); |
|||
// if (context == null) return; |
|||
// Utils.copyText(context, text); |
|||
// }; |
|||
// |
|||
// @Override |
|||
// public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
// super.onCreate(savedInstanceState); |
|||
// fragmentActivity = getActivity(); |
|||
// mediaService = MediaService.getInstance(); |
|||
// } |
|||
// |
|||
// @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 = FragmentPostViewBinding.inflate(inflater, container, false); |
|||
// root = binding.getRoot(); |
|||
// setupViewPager(); |
|||
// return root; |
|||
// } |
|||
// |
|||
// @Override |
|||
// public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
// if (!shouldRefresh) return; |
|||
// init(); |
|||
// shouldRefresh = false; |
|||
// } |
|||
// |
|||
// private void setupViewPager() { |
|||
// viewerPostViewModel = new ViewModelProvider(fragmentActivity) |
|||
// .get(ViewerPostViewModel.class); |
|||
// adapter = new PostViewAdapter(clickListener, captionLongClickListener, mentionListener); |
|||
// root.setAdapter(adapter); |
|||
// root.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { |
|||
// |
|||
// @Override |
|||
// public void onPageSelected(final int position) { |
|||
// // Log.d(TAG, "onPageSelected: " + position + ", hasInitialResult: " + hasInitialResult); |
|||
// if (!hasInitialResult) { |
|||
// return; |
|||
// } |
|||
// currentPostIndex = position; |
|||
// fetchPost(); |
|||
// } |
|||
// }); |
|||
// viewerPostViewModel.getList().observe(fragmentActivity, list -> adapter.submitList(list)); |
|||
// } |
|||
// |
|||
// private void init() { |
|||
// if (getArguments() == null) return; |
|||
// final PostViewFragmentArgs fragmentArgs = PostViewFragmentArgs.fromBundle(getArguments()); |
|||
// final String[] idOrCodeArray = fragmentArgs.getIdOrCodeArray(); |
|||
// if (idOrCodeArray.length == 0) return; |
|||
// currentPostIndex = fragmentArgs.getIndex(); |
|||
// if (currentPostIndex < 0) return; |
|||
// if (currentPostIndex >= idOrCodeArray.length) return; |
|||
// idOrCodeList = Arrays.asList(idOrCodeArray); |
|||
// viewerPostViewModel.getList().setValue(createPlaceholderModels(idOrCodeArray.length)); |
|||
// isId = fragmentArgs.getIsId(); |
|||
// fetchPost(); |
|||
// } |
|||
// |
|||
// private List<ViewerPostModelWrapper> createPlaceholderModels(final int size) { |
|||
// final List<ViewerPostModelWrapper> viewerPostModels = new ArrayList<>(size); |
|||
// for (int i = 0; i < size; i++) { |
|||
// // viewerPostModels.add(new ViewerPostModel[]{ViewerPostModel.getDefaultModel(-i, "")}); |
|||
// viewerPostModels.add(new ViewerPostModelWrapper(i, null)); |
|||
// } |
|||
// return viewerPostModels; |
|||
// } |
|||
// |
|||
// private void fetchPost() { |
|||
// // Log.d(TAG, "fetchPost, currentPostIndex: " + currentPostIndex); |
|||
// final List<ViewerPostModelWrapper> list = viewerPostViewModel.getList().getValue(); |
|||
// if (list != null) { |
|||
// final ViewerPostModelWrapper viewerPostModels = list.get(currentPostIndex); |
|||
// if (viewerPostModels != null && viewerPostModels |
|||
// .getViewerPostModels() != null && viewerPostModels |
|||
// .getViewerPostModels().size() > 0) { |
|||
// Log.d(TAG, "returning without fetching"); |
|||
// return; |
|||
// } |
|||
// } |
|||
// if (currentPostIndex >= idOrCodeList.size() || currentPostIndex < 0) return; |
|||
// final String idOrShortCode = idOrCodeList.get(currentPostIndex); |
|||
// if (isId) { |
|||
// new iPostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
// return; |
|||
// } |
|||
// new PostFetcher(idOrShortCode, pfl).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
// } |
|||
// |
|||
// private void showDownloadDialog(final List<ViewerPostModel> postModels, |
|||
// final int childPosition, |
|||
// final String username) { |
|||
// final List<ViewerPostModel> postModelsToDownload = new ArrayList<>(); |
|||
// final Context context = getContext(); |
|||
// if (context == null) return; |
|||
// if (!session && postModels.size() > 1) { |
|||
// 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))); |
|||
// } |
|||
// } |
|||
// } |
1245
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,121 @@ |
|||
package awais.instagrabber.models; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
|
|||
public final class PostChild implements Serializable { |
|||
private String postId; |
|||
private MediaItemType itemType; |
|||
private String displayUrl; |
|||
private final String thumbnailUrl; |
|||
private final long videoViews; |
|||
private int width; |
|||
private int height; |
|||
|
|||
public static class Builder { |
|||
private String postId; |
|||
private MediaItemType itemType; |
|||
private String displayUrl; |
|||
private long videoViews; |
|||
private String thumbnailUrl; |
|||
private int width; |
|||
private int height; |
|||
|
|||
public Builder setPostId(final String postId) { |
|||
this.postId = postId; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setItemType(final MediaItemType itemType) { |
|||
this.itemType = itemType; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setDisplayUrl(final String displayUrl) { |
|||
this.displayUrl = displayUrl; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setVideoViews(final long videoViews) { |
|||
this.videoViews = videoViews; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setHeight(final int height) { |
|||
this.height = height; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setWidth(final int width) { |
|||
this.width = width; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setThumbnailUrl(final String thumbnailUrl) { |
|||
this.thumbnailUrl = thumbnailUrl; |
|||
return this; |
|||
} |
|||
|
|||
public PostChild build() { |
|||
return new PostChild(postId, itemType, displayUrl, thumbnailUrl, videoViews, height, width); |
|||
} |
|||
} |
|||
|
|||
public PostChild(final String postId, |
|||
final MediaItemType itemType, |
|||
final String displayUrl, |
|||
final String thumbnailUrl, |
|||
final long videoViews, |
|||
final int height, |
|||
final int width) { |
|||
this.postId = postId; |
|||
this.itemType = itemType; |
|||
this.displayUrl = displayUrl; |
|||
this.thumbnailUrl = thumbnailUrl; |
|||
this.height = height; |
|||
this.width = width; |
|||
this.videoViews = videoViews; |
|||
} |
|||
|
|||
public String getPostId() { |
|||
return postId; |
|||
} |
|||
|
|||
public MediaItemType getItemType() { |
|||
return itemType; |
|||
} |
|||
|
|||
public String getDisplayUrl() { |
|||
return displayUrl; |
|||
} |
|||
|
|||
public String getThumbnailUrl() { |
|||
return thumbnailUrl; |
|||
} |
|||
|
|||
public long getVideoViews() { |
|||
return videoViews; |
|||
} |
|||
|
|||
public int getWidth() { |
|||
return width; |
|||
} |
|||
|
|||
public int getHeight() { |
|||
return height; |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "PostChild{" + |
|||
"postId='" + postId + '\'' + |
|||
", itemType=" + itemType + |
|||
", displayUrl='" + displayUrl + '\'' + |
|||
", thumbnailUrl='" + thumbnailUrl + '\'' + |
|||
", videoViews=" + videoViews + |
|||
", width=" + width + |
|||
", height=" + height + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,204 @@ |
|||
package awais.instagrabber.models; |
|||
|
|||
import com.google.gson.Gson; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
public final class PostsLayoutPreferences { |
|||
private final PostsLayoutType type; |
|||
private final int colCount; |
|||
private final boolean isAvatarVisible; |
|||
private final boolean isNameVisible; |
|||
private final ProfilePicSize profilePicSize; |
|||
private final boolean hasRoundedCorners; |
|||
private final boolean hasGap; |
|||
|
|||
public static class Builder { |
|||
private PostsLayoutType type = PostsLayoutType.GRID; |
|||
private int colCount = 2; |
|||
private boolean isAvatarVisible = false; |
|||
private boolean isNameVisible = false; |
|||
private ProfilePicSize profilePicSize = ProfilePicSize.REGULAR; |
|||
private boolean hasRoundedCorners = true; |
|||
private boolean hasGap = true; |
|||
|
|||
public Builder setType(final PostsLayoutType type) { |
|||
this.type = type; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setColCount(final int colCount) { |
|||
this.colCount = (colCount <= 0 || colCount > 3) ? 1 : colCount; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setAvatarVisible(final boolean avatarVisible) { |
|||
this.isAvatarVisible = avatarVisible; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setNameVisible(final boolean nameVisible) { |
|||
this.isNameVisible = nameVisible; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setProfilePicSize(final ProfilePicSize profilePicSize) { |
|||
this.profilePicSize = profilePicSize; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setHasRoundedCorners(final boolean hasRoundedCorners) { |
|||
this.hasRoundedCorners = hasRoundedCorners; |
|||
return this; |
|||
} |
|||
|
|||
public Builder setHasGap(final boolean hasGap) { |
|||
this.hasGap = hasGap; |
|||
return this; |
|||
} |
|||
|
|||
// Breaking builder pattern and adding getters to avoid too many object creations in PostsLayoutPreferencesDialogFragment |
|||
public PostsLayoutType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public int getColCount() { |
|||
return colCount; |
|||
} |
|||
|
|||
public boolean isAvatarVisible() { |
|||
return isAvatarVisible; |
|||
} |
|||
|
|||
public boolean isNameVisible() { |
|||
return isNameVisible; |
|||
} |
|||
|
|||
public ProfilePicSize getProfilePicSize() { |
|||
return profilePicSize; |
|||
} |
|||
|
|||
public boolean getHasRoundedCorners() { |
|||
return hasRoundedCorners; |
|||
} |
|||
|
|||
public boolean getHasGap() { |
|||
return hasGap; |
|||
} |
|||
|
|||
public Builder mergeFrom(final PostsLayoutPreferences preferences) { |
|||
setColCount(preferences.getColCount()); |
|||
setAvatarVisible(preferences.isAvatarVisible()); |
|||
setNameVisible(preferences.isNameVisible()); |
|||
setType(preferences.getType()); |
|||
setProfilePicSize(preferences.getProfilePicSize()); |
|||
setHasRoundedCorners(preferences.getHasRoundedCorners()); |
|||
setHasGap(preferences.getHasGap()); |
|||
return this; |
|||
} |
|||
|
|||
public PostsLayoutPreferences build() { |
|||
return new PostsLayoutPreferences(type, colCount, isAvatarVisible, isNameVisible, profilePicSize, hasRoundedCorners, hasGap); |
|||
} |
|||
} |
|||
|
|||
public static Builder builder() { |
|||
return new Builder(); |
|||
} |
|||
|
|||
private PostsLayoutPreferences(final PostsLayoutType type, |
|||
final int colCount, |
|||
final boolean isAvatarVisible, |
|||
final boolean isNameVisible, |
|||
final ProfilePicSize profilePicSize, |
|||
final boolean hasRoundedCorners, |
|||
final boolean hasGap) { |
|||
|
|||
this.type = type; |
|||
this.colCount = colCount; |
|||
this.isAvatarVisible = isAvatarVisible; |
|||
this.isNameVisible = isNameVisible; |
|||
this.profilePicSize = profilePicSize; |
|||
this.hasRoundedCorners = hasRoundedCorners; |
|||
this.hasGap = hasGap; |
|||
} |
|||
|
|||
public PostsLayoutType getType() { |
|||
return type; |
|||
} |
|||
|
|||
public int getColCount() { |
|||
return colCount; |
|||
} |
|||
|
|||
public boolean isAvatarVisible() { |
|||
return isAvatarVisible; |
|||
} |
|||
|
|||
public boolean isNameVisible() { |
|||
return isNameVisible; |
|||
} |
|||
|
|||
public ProfilePicSize getProfilePicSize() { |
|||
return profilePicSize; |
|||
} |
|||
|
|||
public boolean getHasRoundedCorners() { |
|||
return hasRoundedCorners; |
|||
} |
|||
|
|||
public boolean getHasGap() { |
|||
return hasGap; |
|||
} |
|||
|
|||
public String getJson() { |
|||
return new Gson().toJson(this); |
|||
} |
|||
|
|||
public static PostsLayoutPreferences fromJson(final String json) { |
|||
if (json == null) return null; |
|||
return new Gson().fromJson(json, PostsLayoutPreferences.class); |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final PostsLayoutPreferences that = (PostsLayoutPreferences) o; |
|||
return colCount == that.colCount && |
|||
isAvatarVisible == that.isAvatarVisible && |
|||
isNameVisible == that.isNameVisible && |
|||
type == that.type && |
|||
profilePicSize == that.profilePicSize; |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(type, colCount, isAvatarVisible, isNameVisible, profilePicSize); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "PostsLayoutPreferences{" + |
|||
"type=" + type + |
|||
", colCount=" + colCount + |
|||
", isAvatarVisible=" + isAvatarVisible + |
|||
", isNameVisible=" + isNameVisible + |
|||
", profilePicSize=" + profilePicSize + |
|||
", hasRoundedCorners=" + hasRoundedCorners + |
|||
", hasGap=" + hasGap + |
|||
'}'; |
|||
} |
|||
|
|||
public enum PostsLayoutType { |
|||
GRID, |
|||
STAGGERED_GRID, |
|||
LINEAR |
|||
} |
|||
|
|||
public enum ProfilePicSize { |
|||
REGULAR, |
|||
SMALL, |
|||
TINY |
|||
} |
|||
} |
@ -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 FeedRepository { |
|||
@GET("/graphql/query/") |
|||
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); |
|||
} |
@ -0,0 +1,29 @@ |
|||
package awais.instagrabber.repositories.responses; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.models.FeedModel; |
|||
|
|||
public class FeedFetchResponse { |
|||
private List<FeedModel> feedModels; |
|||
private boolean hasNextPage; |
|||
private String nextCursor; |
|||
|
|||
public FeedFetchResponse(final List<FeedModel> feedModels, final boolean hasNextPage, final String nextCursor) { |
|||
this.feedModels = feedModels; |
|||
this.hasNextPage = hasNextPage; |
|||
this.nextCursor = nextCursor; |
|||
} |
|||
|
|||
public List<FeedModel> getFeedModels() { |
|||
return feedModels; |
|||
} |
|||
|
|||
public boolean hasNextPage() { |
|||
return hasNextPage; |
|||
} |
|||
|
|||
public String getNextCursor() { |
|||
return nextCursor; |
|||
} |
|||
} |
@ -0,0 +1,307 @@ |
|||
package awais.instagrabber.webservices; |
|||
|
|||
import android.os.Handler; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONException; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.io.InputStreamReader; |
|||
import java.io.Reader; |
|||
import java.nio.charset.StandardCharsets; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.PostChild; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.repositories.FeedRepository; |
|||
import awais.instagrabber.repositories.responses.FeedFetchResponse; |
|||
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 FeedService extends BaseService { |
|||
private static final String TAG = "FeedService"; |
|||
private static final boolean loadFromMock = false; |
|||
|
|||
private final FeedRepository repository; |
|||
|
|||
private static FeedService instance; |
|||
|
|||
private FeedService() { |
|||
final Retrofit retrofit = getRetrofitBuilder() |
|||
.baseUrl("https://www.instagram.com") |
|||
.build(); |
|||
repository = retrofit.create(FeedRepository.class); |
|||
} |
|||
|
|||
public static FeedService getInstance() { |
|||
if (instance == null) { |
|||
instance = new FeedService(); |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
public void fetch(final int maxItemsToLoad, |
|||
final String cursor, |
|||
final ServiceCallback<FeedFetchResponse> callback) { |
|||
if (loadFromMock) { |
|||
final Handler handler = new Handler(); |
|||
handler.postDelayed(() -> { |
|||
final ClassLoader classLoader = getClass().getClassLoader(); |
|||
if (classLoader == null) { |
|||
Log.e(TAG, "fetch: classLoader is null!"); |
|||
return; |
|||
} |
|||
try (InputStream resourceAsStream = classLoader.getResourceAsStream("feed_response.json"); |
|||
Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) { |
|||
final int bufferSize = 1024; |
|||
final char[] buffer = new char[bufferSize]; |
|||
final StringBuilder out = new StringBuilder(); |
|||
int charsRead; |
|||
while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) { |
|||
out.append(buffer, 0, charsRead); |
|||
} |
|||
callback.onSuccess(parseResponseBody(out.toString())); |
|||
} catch (IOException | JSONException e) { |
|||
Log.e(TAG, "fetch: ", e); |
|||
} |
|||
}, 1000); |
|||
return; |
|||
} |
|||
final Map<String, String> queryMap = new HashMap<>(); |
|||
queryMap.put("query_hash", "6b838488258d7a4820e48d209ef79eb1"); |
|||
queryMap.put("variables", "{" + |
|||
"\"fetch_media_item_count\":" + maxItemsToLoad + "," + |
|||
"\"has_threaded_comments\":true," + |
|||
"\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"" + |
|||
"}"); |
|||
final Call<String> request = repository.fetch(queryMap); |
|||
request.enqueue(new Callback<String>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |
|||
try { |
|||
// Log.d(TAG, "onResponse: body: " + response.body()); |
|||
final FeedFetchResponse feedFetchResponse = parseResponse(response); |
|||
if (callback != null) { |
|||
callback.onSuccess(feedFetchResponse); |
|||
} |
|||
} catch (JSONException e) { |
|||
Log.e(TAG, "onResponse", e); |
|||
if (callback != null) { |
|||
callback.onFailure(e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { |
|||
if (callback != null) { |
|||
callback.onFailure(t); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
} |
|||
|
|||
@NonNull |
|||
private FeedFetchResponse parseResponse(@NonNull final Response<String> response) throws JSONException { |
|||
if (TextUtils.isEmpty(response.body())) { |
|||
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code()); |
|||
return new FeedFetchResponse(Collections.emptyList(), false, null); |
|||
} |
|||
return parseResponseBody(response.body()); |
|||
} |
|||
|
|||
@NonNull |
|||
private FeedFetchResponse parseResponseBody(@NonNull final String body) |
|||
throws JSONException { |
|||
final List<FeedModel> feedModels = new ArrayList<>(); |
|||
final JSONObject timelineFeed = new JSONObject(body) |
|||
.getJSONObject("data") |
|||
.getJSONObject(Constants.EXTRAS_USER) |
|||
.getJSONObject("edge_web_feed_timeline"); |
|||
final String endCursor; |
|||
final boolean hasNextPage; |
|||
|
|||
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); |
|||
if (pageInfo.has("has_next_page")) { |
|||
hasNextPage = pageInfo.getBoolean("has_next_page"); |
|||
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; |
|||
} else { |
|||
hasNextPage = false; |
|||
endCursor = null; |
|||
} |
|||
|
|||
final JSONArray feedItems = timelineFeed.getJSONArray("edges"); |
|||
|
|||
for (int i = 0; i < feedItems.length(); ++i) { |
|||
final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node"); |
|||
final String mediaType = feedItem.optString("__typename"); |
|||
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) |
|||
continue; |
|||
|
|||
final boolean isVideo = feedItem.optBoolean("is_video"); |
|||
final long videoViews = feedItem.optLong("video_view_count", 0); |
|||
|
|||
final String displayUrl = feedItem.optString("display_url"); |
|||
if (TextUtils.isEmpty(displayUrl)) continue; |
|||
final String resourceUrl; |
|||
|
|||
if (isVideo) { |
|||
resourceUrl = feedItem.getString("video_url"); |
|||
} else { |
|||
resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl; |
|||
} |
|||
|
|||
ProfileModel profileModel = null; |
|||
if (feedItem.has("owner")) { |
|||
final JSONObject owner = feedItem.getJSONObject("owner"); |
|||
profileModel = new ProfileModel( |
|||
owner.optBoolean("is_private"), |
|||
false, // if you can see it then you def follow |
|||
owner.optBoolean("is_verified"), |
|||
owner.getString(Constants.EXTRAS_ID), |
|||
owner.getString(Constants.EXTRAS_USERNAME), |
|||
owner.optString("full_name"), |
|||
null, |
|||
null, |
|||
owner.getString("profile_pic_url"), |
|||
null, |
|||
0, |
|||
0, |
|||
0, |
|||
false, |
|||
false, |
|||
false, |
|||
false); |
|||
} |
|||
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); |
|||
final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; |
|||
tempJsonObject = feedItem.optJSONObject("edge_media_preview_like"); |
|||
final long likesCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; |
|||
tempJsonObject = feedItem.optJSONObject("edge_media_to_caption"); |
|||
final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; |
|||
String captionText = null; |
|||
if (captions != null && captions.length() > 0) { |
|||
if ((tempJsonObject = captions.optJSONObject(0)) != null && |
|||
(tempJsonObject = tempJsonObject.optJSONObject("node")) != null) { |
|||
captionText = tempJsonObject.getString("text"); |
|||
} |
|||
} |
|||
final JSONObject location = feedItem.optJSONObject("location"); |
|||
// Log.d(TAG, "location: " + (location == null ? null : location.toString())); |
|||
String locationId = null; |
|||
String locationName = null; |
|||
if (location != null) { |
|||
locationName = location.optString("name"); |
|||
if (location.has("id")) { |
|||
locationId = location.getString("id"); |
|||
} else if (location.has("pk")) { |
|||
locationId = location.getString("pk"); |
|||
} |
|||
// Log.d(TAG, "locationId: " + locationId); |
|||
} |
|||
int height = 0; |
|||
int width = 0; |
|||
final JSONObject dimensions = feedItem.optJSONObject("dimensions"); |
|||
if (dimensions != null) { |
|||
height = dimensions.optInt("height"); |
|||
width = dimensions.optInt("width"); |
|||
} |
|||
String thumbnailUrl = null; |
|||
try { |
|||
thumbnailUrl = feedItem.getJSONArray("display_resources") |
|||
.getJSONObject(0) |
|||
.getString("src"); |
|||
} catch (JSONException ignored) {} |
|||
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder() |
|||
.setProfileModel(profileModel) |
|||
.setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO |
|||
: MediaItemType.MEDIA_TYPE_IMAGE) |
|||
.setViewCount(videoViews) |
|||
.setPostId(feedItem.getString(Constants.EXTRAS_ID)) |
|||
.setDisplayUrl(resourceUrl) |
|||
.setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl) |
|||
.setShortCode(feedItem.getString(Constants.EXTRAS_SHORTCODE)) |
|||
.setPostCaption(captionText) |
|||
.setCommentsCount(commentsCount) |
|||
.setTimestamp(feedItem.optLong("taken_at_timestamp", -1)) |
|||
.setLiked(feedItem.getBoolean("viewer_has_liked")) |
|||
.setBookmarked(feedItem.getBoolean("viewer_has_saved")) |
|||
.setLikesCount(likesCount) |
|||
.setLocationName(locationName) |
|||
.setLocationId(locationId) |
|||
.setImageHeight(height) |
|||
.setImageWidth(width); |
|||
|
|||
final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children"); |
|||
|
|||
if (isSlider) { |
|||
feedModelBuilder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER); |
|||
final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children"); |
|||
if (sidecar != null) { |
|||
final JSONArray children = sidecar.optJSONArray("edges"); |
|||
if (children != null) { |
|||
final List<PostChild> sliderItems = getSliderItems(children); |
|||
feedModelBuilder.setSliderItems(sliderItems); |
|||
} |
|||
} |
|||
} |
|||
final FeedModel feedModel = feedModelBuilder.build(); |
|||
feedModels.add(feedModel); |
|||
} |
|||
return new FeedFetchResponse(feedModels, hasNextPage, endCursor); |
|||
} |
|||
|
|||
@NonNull |
|||
private List<PostChild> getSliderItems(final JSONArray children) throws JSONException { |
|||
final List<PostChild> sliderItems = new ArrayList<>(); |
|||
for (int j = 0; j < children.length(); ++j) { |
|||
final JSONObject childNode = children.optJSONObject(j).getJSONObject("node"); |
|||
final boolean isChildVideo = childNode.optBoolean("is_video"); |
|||
int height = 0; |
|||
int width = 0; |
|||
final JSONObject dimensions = childNode.optJSONObject("dimensions"); |
|||
if (dimensions != null) { |
|||
height = dimensions.optInt("height"); |
|||
width = dimensions.optInt("width"); |
|||
} |
|||
String thumbnailUrl = null; |
|||
try { |
|||
thumbnailUrl = childNode.getJSONArray("display_resources") |
|||
.getJSONObject(0) |
|||
.getString("src"); |
|||
} catch (JSONException ignored) {} |
|||
final PostChild sliderItem = new PostChild.Builder() |
|||
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO |
|||
: MediaItemType.MEDIA_TYPE_IMAGE) |
|||
.setPostId(childNode.getString(Constants.EXTRAS_ID)) |
|||
.setDisplayUrl(isChildVideo ? childNode.getString("video_url") |
|||
: childNode.getString("display_url")) |
|||
.setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl |
|||
: childNode.getString("display_url")) |
|||
.setVideoViews(childNode.optLong("video_view_count", 0)) |
|||
.setHeight(height) |
|||
.setWidth(width) |
|||
.build(); |
|||
// Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem); |
|||
sliderItems.add(sliderItem); |
|||
} |
|||
return sliderItems; |
|||
} |
|||
} |
@ -0,0 +1,382 @@ |
|||
package awais.instagrabber.workers; |
|||
|
|||
import android.app.Notification; |
|||
import android.app.PendingIntent; |
|||
import android.content.ContentResolver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.BitmapFactory; |
|||
import android.media.MediaMetadataRetriever; |
|||
import android.media.MediaScannerConnection; |
|||
import android.net.Uri; |
|||
import android.os.Build; |
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.core.app.NotificationCompat; |
|||
import androidx.core.app.NotificationManagerCompat; |
|||
import androidx.core.content.FileProvider; |
|||
import androidx.work.Data; |
|||
import androidx.work.ForegroundInfo; |
|||
import androidx.work.Worker; |
|||
import androidx.work.WorkerParameters; |
|||
|
|||
import com.google.gson.Gson; |
|||
import com.google.gson.JsonSyntaxException; |
|||
|
|||
import java.io.BufferedInputStream; |
|||
import java.io.File; |
|||
import java.io.FileOutputStream; |
|||
import java.io.InputStream; |
|||
import java.net.URL; |
|||
import java.net.URLConnection; |
|||
import java.util.Collection; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.MainActivity; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; |
|||
import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public class DownloadWorker extends Worker { |
|||
private static final String TAG = "DownloadWorker"; |
|||
private static final String PROGRESS = "PROGRESS"; |
|||
private static final String URL = "URL"; |
|||
private static final String DOWNLOAD_GROUP = "DOWNLOAD_GROUP"; |
|||
|
|||
public static final String KEY_DOWNLOAD_REQUEST_JSON = "download_request_json"; |
|||
public static final int DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE = 2020; |
|||
public static final int DELETE_IMAGE_REQUEST_CODE = 2030; |
|||
|
|||
private final NotificationManagerCompat notificationManager; |
|||
|
|||
public DownloadWorker(@NonNull final Context context, @NonNull final WorkerParameters workerParams) { |
|||
super(context, workerParams); |
|||
notificationManager = NotificationManagerCompat.from(context); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Result doWork() { |
|||
final String downloadRequestString = getInputData().getString(KEY_DOWNLOAD_REQUEST_JSON); |
|||
if (TextUtils.isEmpty(downloadRequestString)) { |
|||
return Result.failure(new Data.Builder() |
|||
.putString("error", "downloadRequest is empty or null") |
|||
.build()); |
|||
} |
|||
final DownloadRequest downloadRequest; |
|||
try { |
|||
downloadRequest = new Gson().fromJson(downloadRequestString, DownloadRequest.class); |
|||
} catch (JsonSyntaxException e) { |
|||
Log.e(TAG, "doWork", e); |
|||
return Result.failure(new Data.Builder() |
|||
.putString("error", e.getLocalizedMessage()) |
|||
.build()); |
|||
} |
|||
if (downloadRequest == null) { |
|||
return Result.failure(new Data.Builder() |
|||
.putString("error", "downloadRequest is null") |
|||
.build()); |
|||
} |
|||
final Map<String, String> urlToFilePathMap = downloadRequest.getUrlToFilePathMap(); |
|||
download(urlToFilePathMap); |
|||
new Handler(Looper.getMainLooper()).postDelayed(() -> showSummary(urlToFilePathMap), 500); |
|||
return Result.success(); |
|||
} |
|||
|
|||
private void download(final Map<String, String> urlToFilePathMap) { |
|||
final int notificationId = getNotificationId(); |
|||
final Set<Map.Entry<String, String>> entries = urlToFilePathMap.entrySet(); |
|||
int count = 1; |
|||
final int total = urlToFilePathMap.size(); |
|||
for (final Map.Entry<String, String> urlAndFilePath : entries) { |
|||
final String url = urlAndFilePath.getKey(); |
|||
updateDownloadProgress(notificationId, count, total, 0); |
|||
download(notificationId, count, total, url, urlAndFilePath.getValue()); |
|||
count++; |
|||
} |
|||
} |
|||
|
|||
private int getNotificationId() { |
|||
return Math.abs(getId().hashCode()); |
|||
} |
|||
|
|||
private void download(final int notificationId, |
|||
final int position, |
|||
final int total, |
|||
final String url, |
|||
final String filePath) { |
|||
final File outFile = new File(filePath); |
|||
try { |
|||
final URLConnection urlConnection = new URL(url).openConnection(); |
|||
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : |
|||
urlConnection.getContentLength(); |
|||
float totalRead = 0; |
|||
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); |
|||
final FileOutputStream fos = new FileOutputStream(outFile)) { |
|||
final byte[] buffer = new byte[0x2000]; |
|||
int count; |
|||
boolean deletedIPTC = false; |
|||
while ((count = bis.read(buffer, 0, 0x2000)) != -1) { |
|||
totalRead = totalRead + count; |
|||
if (!deletedIPTC) { |
|||
int iptcStart = -1; |
|||
int fbmdStart = -1; |
|||
int fbmdBytesLen = -1; |
|||
for (int i = 0; i < buffer.length; ++i) { |
|||
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED) |
|||
iptcStart = i; |
|||
else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B' |
|||
&& buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') { |
|||
fbmdStart = i; |
|||
fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 | |
|||
(buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) | |
|||
(buffer[i - 6] & 0xFF); |
|||
break; |
|||
} |
|||
} |
|||
if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) { |
|||
final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4; |
|||
fos.write(buffer, 0, iptcStart); |
|||
fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart); |
|||
// setProgressAsync(new Data.Builder().putString(URL, url) |
|||
// .putFloat(PROGRESS, totalRead * 100f / fileSize) |
|||
// .build()); |
|||
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize); |
|||
deletedIPTC = true; |
|||
continue; |
|||
} |
|||
} |
|||
fos.write(buffer, 0, count); |
|||
// setProgressAsync(new Data.Builder().putString(URL, url) |
|||
// .putFloat(PROGRESS, totalRead * 100f / fileSize) |
|||
// .build()); |
|||
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize); |
|||
} |
|||
fos.flush(); |
|||
} |
|||
} catch (final Exception e) { |
|||
Log.e(TAG, "Error while downloading: " + url, e); |
|||
} |
|||
updateDownloadProgress(notificationId, position, total, 100); |
|||
} |
|||
|
|||
// private void showCompleteNotification(final String url) { |
|||
// final Context context = getApplicationContext(); |
|||
// final Notification notification = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID) |
|||
// .setCategory(NotificationCompat.CATEGORY_STATUS) |
|||
// .setSmallIcon(R.drawable.ic_download) |
|||
// .setAutoCancel(false) |
|||
// .setOnlyAlertOnce(true) |
|||
// .setContentTitle(context.getString(R.string.downloader_complete)) |
|||
// .setGroup(DOWNLOAD_GROUP) |
|||
// .build(); |
|||
// final int id = Math.abs(url.hashCode()); |
|||
// Log.d(TAG, "showCompleteNotification: cancelling: " + id); |
|||
// notificationManager.cancel(id); |
|||
// // WorkManager.getInstance(getApplicationContext()). |
|||
// notificationManager.notify(id + 1, notification); |
|||
// } |
|||
|
|||
private void updateDownloadProgress(final int notificationId, |
|||
final int position, |
|||
final int total, |
|||
final float percent) { |
|||
final Notification notification = createProgressNotification(position, total, percent); |
|||
try { |
|||
setForegroundAsync(new ForegroundInfo(notificationId, notification)).get(); |
|||
} catch (ExecutionException | InterruptedException e) { |
|||
Log.e(TAG, "updateDownloadProgress", e); |
|||
} |
|||
} |
|||
|
|||
private Notification createProgressNotification(final int position, final int total, final float percent) { |
|||
final Context context = getApplicationContext(); |
|||
boolean ongoing = true; |
|||
int totalPercent; |
|||
if (position == total && percent == 100) { |
|||
ongoing = false; |
|||
totalPercent = 100; |
|||
} else { |
|||
totalPercent = (int) ((100f * (position - 1) / total) + (1f / total) * (percent)); |
|||
} |
|||
// Log.d(TAG, "createProgressNotification: position: " + position |
|||
// + ", total: " + total |
|||
// + ", percent: " + percent |
|||
// + ", totalPercent: " + totalPercent); |
|||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID) |
|||
.setCategory(NotificationCompat.CATEGORY_PROGRESS) |
|||
.setSmallIcon(R.drawable.ic_download) |
|||
.setOngoing(ongoing) |
|||
.setProgress(100, totalPercent, totalPercent < 0) |
|||
.setAutoCancel(false) |
|||
.setOnlyAlertOnce(true) |
|||
.setContentTitle(context.getString(R.string.downloader_downloading_post)); |
|||
if (total > 1) { |
|||
builder.setContentText(context.getString(R.string.downloader_downloading_child, position, total)); |
|||
} |
|||
return builder.build(); |
|||
} |
|||
|
|||
private void showSummary(final Map<String, String> urlToFilePathMap) { |
|||
final Context context = getApplicationContext(); |
|||
final Collection<String> filePaths = urlToFilePathMap.values(); |
|||
final List<NotificationCompat.Builder> notifications = filePaths |
|||
.stream() |
|||
.map(filePath -> { |
|||
final File file = new File(filePath); |
|||
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); |
|||
MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); |
|||
final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); |
|||
final ContentResolver contentResolver = context.getContentResolver(); |
|||
Bitmap bitmap = null; |
|||
if (Utils.isImage(uri, contentResolver)) { |
|||
try (final InputStream inputStream = contentResolver.openInputStream(uri)) { |
|||
bitmap = BitmapFactory.decodeStream(inputStream); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); |
|||
if (BuildConfig.DEBUG) Log.e(TAG, "", e); |
|||
} |
|||
} |
|||
if (bitmap == null) { |
|||
final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
|||
try { |
|||
try { |
|||
retriever.setDataSource(context, uri); |
|||
} catch (final Exception e) { |
|||
retriever.setDataSource(file.getAbsolutePath()); |
|||
} |
|||
bitmap = retriever.getFrameAtTime(); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) |
|||
try { |
|||
retriever.close(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); |
|||
} |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e(TAG, "", e); |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); |
|||
} |
|||
} |
|||
final String downloadComplete = context.getString(R.string.downloader_complete); |
|||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri) |
|||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|||
| Intent.FLAG_FROM_BACKGROUND |
|||
| Intent.FLAG_GRANT_READ_URI_PERMISSION |
|||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|||
.putExtra(Intent.EXTRA_STREAM, uri); |
|||
final PendingIntent pendingIntent = PendingIntent.getActivity( |
|||
context, |
|||
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, |
|||
intent, |
|||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT |
|||
); |
|||
final Intent deleteIntent = new Intent(getApplicationContext(), MainActivity.class) |
|||
.setAction(Constants.ACTION_SHOW_ACTIVITY) |
|||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); |
|||
final PendingIntent deleteItemIntent = PendingIntent |
|||
.getActivity(getApplicationContext(), DELETE_IMAGE_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
|||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
|||
.setSmallIcon(R.drawable.ic_download) |
|||
.setContentText(null) |
|||
.setContentTitle(downloadComplete) |
|||
.setWhen(System.currentTimeMillis()) |
|||
.setOnlyAlertOnce(true) |
|||
.setAutoCancel(true) |
|||
.setGroup(NOTIF_GROUP_NAME + "_" + getId()) |
|||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) |
|||
.setContentIntent(pendingIntent) |
|||
.addAction(R.drawable.ic_delete, context.getString(R.string.delete), deleteItemIntent); |
|||
if (bitmap != null) { |
|||
builder.setLargeIcon(bitmap) |
|||
.setStyle(new NotificationCompat.BigPictureStyle() |
|||
.bigPicture(bitmap) |
|||
.bigLargeIcon(null)) |
|||
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); |
|||
} |
|||
return builder; |
|||
}) |
|||
.collect(Collectors.toList()); |
|||
Notification summaryNotification = null; |
|||
if (urlToFilePathMap.size() != 1) { |
|||
final String text = "Downloaded " + urlToFilePathMap.size() + " items"; |
|||
summaryNotification = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
|||
.setContentTitle("Downloaded") |
|||
.setContentText(text) |
|||
.setSmallIcon(R.drawable.ic_download) |
|||
.setStyle(new NotificationCompat.InboxStyle().setSummaryText(text)) |
|||
.setGroup(NOTIF_GROUP_NAME + "_" + getId()) |
|||
.setGroupSummary(true) |
|||
.build(); |
|||
} |
|||
int count = 1; |
|||
for (final NotificationCompat.Builder builder : notifications) { |
|||
// only make sound and vibrate for the last notification |
|||
if (count != notifications.size()) { |
|||
builder.setSound(null) |
|||
.setVibrate(null); |
|||
} |
|||
notificationManager.notify(getNotificationId() + count, builder.build()); |
|||
count++; |
|||
} |
|||
if (summaryNotification != null) { |
|||
notificationManager.notify(getNotificationId() + count, summaryNotification); |
|||
} |
|||
|
|||
} |
|||
|
|||
public static class DownloadRequest { |
|||
private final Map<String, String> urlToFilePathMap; |
|||
|
|||
public static class Builder { |
|||
private Map<String, String> urlToFilePathMap; |
|||
|
|||
public Builder setUrlToFilePathMap(final Map<String, String> urlToFilePathMap) { |
|||
this.urlToFilePathMap = urlToFilePathMap; |
|||
return this; |
|||
} |
|||
|
|||
public Builder addUrl(@NonNull final String url, @NonNull final String filePath) { |
|||
if (urlToFilePathMap == null) { |
|||
urlToFilePathMap = new HashMap<>(); |
|||
} |
|||
urlToFilePathMap.put(url, filePath); |
|||
return this; |
|||
} |
|||
|
|||
public DownloadRequest build() { |
|||
return new DownloadRequest(urlToFilePathMap); |
|||
} |
|||
} |
|||
|
|||
public static Builder builder() { |
|||
return new Builder(); |
|||
} |
|||
|
|||
private DownloadRequest(final Map<String, String> urlToFilePathMap) { |
|||
this.urlToFilePathMap = urlToFilePathMap; |
|||
} |
|||
|
|||
public Map<String, String> getUrlToFilePathMap() { |
|||
return urlToFilePathMap; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<scale |
|||
android:duration="400" |
|||
android:fillAfter="false" |
|||
android:fromXScale="0.9" |
|||
android:fromYScale="0.9" |
|||
android:interpolator="@android:anim/accelerate_decelerate_interpolator" |
|||
android:pivotX="50%" |
|||
android:pivotY="30%" |
|||
android:toXScale="1.0" |
|||
android:toYScale="1.0" /> |
|||
|
|||
<alpha |
|||
android:duration="200" |
|||
android:fromAlpha="0.5" |
|||
android:toAlpha="1.0" /> |
|||
</set> |
@ -0,0 +1,19 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<scale |
|||
android:duration="400" |
|||
android:fillAfter="false" |
|||
android:fromXScale="1.0" |
|||
android:fromYScale="1.0" |
|||
android:interpolator="@android:anim/accelerate_decelerate_interpolator" |
|||
android:pivotX="50%" |
|||
android:pivotY="30%" |
|||
android:toXScale="0.9" |
|||
android:toYScale="0.9" /> |
|||
|
|||
<alpha |
|||
android:duration="200" |
|||
android:startOffset="200" |
|||
android:fromAlpha="1.0" |
|||
android:toAlpha="0.0" /> |
|||
</set> |
@ -0,0 +1,3 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:color="@color/grey_500" /> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000000" |
|||
android:pathData="M9,21L7,21L7,19L9,19M5,21L3,21L3,19L5,19M17,21L15,21L15,19L17,19M13,21L11,21L11,19L13,19M5,17L3,17L3,15L5,15M5,13L3,13L3,11L5,11M21,3L21,21L19,21L19,5L3,5L3,3M5,9L3,9L3,7L5,7" /> |
|||
</vector> |
@ -0,0 +1,11 @@ |
|||
<!-- drawable/checkbox_multiple_blank.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" /> |
|||
</vector> |
@ -0,0 +1,13 @@ |
|||
<!-- drawable/checkbox_multiple_blank.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="@color/white" |
|||
android:pathData="M22,16A2,2 0 0,1 20,18H8C6.89,18 6,17.1 6,16V4C6,2.89 6.89,2 8,2H20A2,2 0 0,1 22,4V16M16,20V22H4A2,2 0 0,1 2,20V7H4V20H16Z" |
|||
android:strokeWidth="0.4" |
|||
android:strokeColor="@color/black_a50" |
|||
android:strokeLineJoin="round" /> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z"/> |
|||
</vector> |
@ -0,0 +1,13 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M18,13c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6s2.69,-6 6,-6v4l5,-5l-5,-5v4c-4.42,0 -8,3.58 -8,8c0,4.42 3.58,8 8,8c4.42,0 8,-3.58 8,-8H18z"/> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12.03,15.38c-0.44,0 -0.58,-0.31 -0.6,-0.56h-0.84c0.03,0.85 0.79,1.25 1.44,1.25c0.93,0 1.44,-0.63 1.44,-1.43c0,-1.33 -0.97,-1.44 -1.3,-1.44c-0.2,0 -0.43,0.05 -0.64,0.16l0.11,-0.92h1.7v-0.71h-2.39l-0.25,2.17l0.67,0.17c0.13,-0.13 0.28,-0.23 0.57,-0.23c0.4,0 0.69,0.23 0.69,0.75C12.62,14.64 12.65,15.38 12.03,15.38z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M3,18h12v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h18v-2L3,11v2z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M8,5v14l11,-7z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="40dp" |
|||
android:height="40dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" /> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="48dp" |
|||
android:height="48dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z" /> |
|||
</vector> |
@ -0,0 +1,13 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6s-6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8S16.42,5 12,5z"/> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M10.69,13.9l0.25,-2.17h2.39v0.71h-1.7l-0.11,0.92c0.03,-0.02 0.07,-0.03 0.11,-0.05s0.09,-0.04 0.15,-0.05s0.12,-0.03 0.18,-0.04s0.13,-0.02 0.2,-0.02c0.21,0 0.39,0.03 0.55,0.1s0.3,0.16 0.41,0.28s0.2,0.27 0.25,0.45s0.09,0.38 0.09,0.6c0,0.19 -0.03,0.37 -0.09,0.54s-0.15,0.32 -0.27,0.45s-0.27,0.24 -0.45,0.31s-0.39,0.12 -0.64,0.12c-0.18,0 -0.36,-0.03 -0.53,-0.08s-0.32,-0.14 -0.46,-0.24s-0.24,-0.24 -0.32,-0.39s-0.13,-0.33 -0.13,-0.53h0.84c0.02,0.18 0.08,0.32 0.19,0.41s0.25,0.15 0.42,0.15c0.11,0 0.2,-0.02 0.27,-0.06s0.14,-0.1 0.18,-0.17s0.08,-0.15 0.11,-0.25s0.03,-0.2 0.03,-0.31s-0.01,-0.21 -0.04,-0.31s-0.07,-0.17 -0.13,-0.24s-0.13,-0.12 -0.21,-0.15s-0.19,-0.05 -0.3,-0.05c-0.08,0 -0.15,0.01 -0.2,0.02s-0.11,0.03 -0.15,0.05s-0.08,0.05 -0.12,0.07s-0.07,0.06 -0.1,0.09L10.69,13.9z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M19,19h2v2h-2V19zM19,17h2v-2h-2V17zM3,13h2v-2H3V13zM3,17h2v-2H3V17zM3,9h2V7H3V9zM3,5h2V3H3V5zM7,5h2V3H7V5zM15,21h2v-2h-2V21zM11,21h2v-2h-2V21zM15,21h2v-2h-2V21zM7,21h2v-2H7V21zM3,21h2v-2H3V21zM21,8c0,-2.76 -2.24,-5 -5,-5h-5v2h5c1.65,0 3,1.35 3,3v5h2V8z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M20,13L3,13c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM20,3L3,3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1L21,4c0,-0.55 -0.45,-1 -1,-1z"/> |
|||
</vector> |
@ -0,0 +1,11 @@ |
|||
<!-- drawable/view_grid.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M3,11H11V3H3M3,21H11V13H3M13,21H21V13H13M13,3V11H21V3" /> |
|||
</vector> |
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shape="rectangle"> |
|||
|
|||
<corners android:radius="2dp" /> |
|||
<solid android:color="@color/grey_800" /> |
|||
|
|||
</shape> |
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<solid android:color="@android:color/transparent" /> |
|||
<corners android:radius="8dp" /> |
|||
</shape> |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue