Initial commit for new posts layout

This commit is contained in:
Ammar Githam 2020-10-17 19:07:03 +09:00
parent 03f52d5058
commit 6bf59e83ad
132 changed files with 9030 additions and 2012 deletions

View File

@ -20,6 +20,9 @@ android {
}
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
@ -37,24 +40,29 @@ android {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
def appcompat_version = "1.2.0"
def nav_version = "2.3.0"
def preference_version = "1.1.1"
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.exoplayer:exoplayer:2.11.1'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'com.google.android.exoplayer:exoplayer:2.12.0'
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha05"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
implementation "androidx.preference:preference:$preference_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.2"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.work:work-runtime:2.4.0"
implementation 'com.google.guava:guava:27.0.1-android'
// implementation 'com.github.hendrawd:StorageUtil:1.1.0'
implementation 'com.github.armcha:AutoLinkTextViewV2:2.1.1'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0'
@ -63,5 +71,7 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.0'
}

View File

@ -32,12 +32,16 @@ public final class InstaGrabberApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig
.newBuilder(this)
// .setMainDiskCacheConfig(diskCacheConfig)
// .setRequestListeners(requestListeners)
.setDownsampleEnabled(true)
.build();
Fresco.initialize(this, imagePipelineConfig);
// FLog.setMinimumLoggingLevel(FLog.VERBOSE);
if (BuildConfig.DEBUG) {
try {

View File

@ -5,6 +5,7 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
@ -59,7 +60,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater());
loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(getApplicationContext()));
setContentView(loginBinding.getRoot());
initWebView();
@ -117,12 +118,9 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
webSettings.setDisplayZoomControls(false);
webSettings.setLoadWithOverviewMode(true);
webSettings.setUseWideViewPort(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {

View File

@ -9,6 +9,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.database.MatrixCursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@ -94,7 +95,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.notificationsViewer,
R.id.themePreferencesFragment,
R.id.favoritesFragment,
R.id.backupPreferencesFragment);
R.id.backupPreferencesFragment
// , R.id.postViewV2Fragment
);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final List<Integer> REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment);
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
@ -157,10 +160,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
bindActivityCheckerService();
}
getSupportFragmentManager().addOnBackStackChangedListener(this);
// Log.d("austin_debug", "dir: "+Arrays.toString(StorageUtil.getStorageDirectories(getApplicationContext())));
// final File sdcard = new File(StorageUtil.getStorageDirectories(getApplicationContext())[0]);
// Log.d("austin_debug", "files: "+Arrays.toString(sdcard.listFiles()));
}
@Override
@ -222,7 +221,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override
public void onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTaskRoot() && isBackStackEmpty) {
if (isTaskRoot() && isBackStackEmpty) {
finishAfterTransition();
} else {
super.onBackPressed();
@ -626,4 +625,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
public BottomNavigationView getBottomNavView() {
return binding.bottomNavView;
}
public void fitSystemWindows(final boolean fit) {
binding.appBarLayout.setBackground(null);
binding.appBarLayout.setFitsSystemWindows(fit);
binding.collapsingToolbarLayout.setBackground(null);
binding.collapsingToolbarLayout.setFitsSystemWindows(fit);
final Drawable toolbarBackground = binding.toolbar.getBackground();
binding.toolbar.setFitsSystemWindows(fit);
binding.toolbar.setBackground(null);
binding.toolbar.setClickable(false);
}
public int getNavHostContainerId() {
return binding.mainNavHost.getId();
}
}

View File

@ -83,7 +83,7 @@ public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder
return;
}
feedModel.setPosition(position);
viewHolder.bind(feedModel);
viewHolder.bind(feedModel, (feedModel1, view, postImage) -> {});
}
@Override

View File

@ -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);
}
}

View File

@ -12,8 +12,8 @@ public abstract class MultiSelectListAdapter<T extends MultiSelectListAdapter.Se
ListAdapter<T, VH> {
private boolean isSelecting;
private final OnItemClickListener<T> internalOnItemClickListener;
private final OnItemLongClickListener<T> internalOnLongItemClickListener;
private OnItemClickListener<T> internalOnItemClickListener;
private OnItemLongClickListener<T> internalOnLongItemClickListener;
private final List<T> selectedItems = new ArrayList<>();

View File

@ -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);
// }
// }

View File

@ -15,6 +15,7 @@ import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
@ -171,8 +172,9 @@ public class PostViewerChildAdapter extends ListAdapter<ViewerPostModel, PostVie
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(itemView.getContext(), "instagram"))
.createMediaSource(Uri.parse(item.getDisplayUrl()));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(
new DefaultDataSourceFactory(itemView.getContext(), "instagram")
).createMediaSource(MediaItem.fromUri(item.getDisplayUrl()));
// mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
// @Override
// public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
@ -194,7 +196,8 @@ public class PostViewerChildAdapter extends ListAdapter<ViewerPostModel, PostVie
// viewerBinding.progressView.setVisibility(View.GONE);
// }
// });
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol);
// viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
// viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener);

View File

@ -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) {}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -24,6 +24,6 @@ public final class PostMediaViewHolder extends RecyclerView.ViewHolder {
itemView.setOnClickListener(clickListener);
binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
binding.isDownloaded.setVisibility(model.isDownloaded() ? View.VISIBLE : View.GONE);
binding.icon.setImageURI(model.getSliderDisplayUrl());
binding.icon.setImageURI(model.getDisplayUrl());
}
}

View File

@ -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);
// }
// }
// }

View File

@ -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);
}

View File

@ -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);
// }
// }
}

View File

@ -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);
// }
// }
}

View File

@ -9,6 +9,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.CommentMentionClickSpan;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedBottomBinding;
@ -34,17 +35,19 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
this.topBinding = topBinding;
this.bottomBinding = bottomBinding;
this.mentionClickListener = mentionClickListener;
// itemView.setOnClickListener(clickListener);
// topBinding.title.setMovementMethod(new LinkMovementMethod());
bottomBinding.btnComments.setOnClickListener(clickListener);
topBinding.viewStoryPost.setOnClickListener(clickListener);
topBinding.ivProfilePic.setOnClickListener(clickListener);
bottomBinding.btnDownload.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnClickListener(clickListener);
bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
// bottomBinding.btnComments.setOnClickListener(clickListener);
// topBinding.viewStoryPost.setOnClickListener(clickListener);
// topBinding.ivProfilePic.setOnClickListener(clickListener);
// bottomBinding.btnDownload.setOnClickListener(clickListener);
// bottomBinding.viewerCaption.setOnClickListener(clickListener);
// bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
// bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
}
public void bind(final FeedModel feedModel) {
public void bind(final FeedModel feedModel,
final FeedAdapterV2.OnPostClickListener postClickListener) {
if (feedModel == null) {
return;
}
@ -83,7 +86,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
}
}
expandCollapseTextView(bottomBinding.viewerCaption, feedModel.getPostCaption());
bindItem(feedModel);
bindItem(feedModel, postClickListener);
}
private void setLocation(final String locationName, final String locationId) {
@ -106,7 +109,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation]
*
* @param textView the {@link RamboTextView} view, to expand and collapse
* @param caption caption
* @param caption caption
* @return isExpanded
*/
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, final CharSequence caption) {
@ -116,7 +119,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
if (textView.isCaptionExpanded()) {
textView.setText(caption, bufferType);
textView.setCaptionIsExpanded(false);
// textView.setCaptionIsExpanded(false);
return true;
}
int i = TextUtils.indexOfChar(caption, '\r', 0);
@ -129,10 +132,15 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
if (TextUtils.hasMentions(caption))
textView.setText(TextUtils.getMentionText(caption), TextView.BufferType.SPANNABLE);
textView.setCaptionIsExpandable(true);
textView.setCaptionIsExpanded(true);
// textView.setCaptionIsExpandable(true);
// textView.setCaptionIsExpanded(true);
return true;
}
public abstract void bindItem(final FeedModel feedModel);
public abstract void bindItem(final FeedModel feedModel,
final FeedAdapterV2.OnPostClickListener postClickListener);
public void clearAnimation() {
itemView.clearAnimation();
}
}

View File

@ -1,8 +1,9 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.net.Uri;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@ -13,16 +14,17 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class FeedPhotoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedPhotoViewHolder";
private final ItemFeedPhotoBinding binding;
// private final long animationDuration;
public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding,
final MentionClickListener mentionClickListener,
@ -30,6 +32,7 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding;
// this.animationDuration = animationDuration;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE);
binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false);
@ -40,15 +43,13 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
}
@Override
public void bindItem(final FeedModel feedModel) {
public void bindItem(final FeedModel feedModel,
final FeedAdapterV2.OnPostClickListener postClickListener) {
if (feedModel == null) {
return;
}
final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.imageViewer.requestLayout();
setDimensions(feedModel);
showOrHideDetails(false);
final String thumbnailUrl = feedModel.getThumbnailUrl();
String url = feedModel.getDisplayUrl();
if (TextUtils.isEmpty(url)) url = thumbnailUrl;
@ -61,16 +62,66 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
.setOldController(binding.imageViewer.getController())
.setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl))
.build());
// binding.imageViewer.setImageURI(url);
// final RequestBuilder<Bitmap> thumbnailRequestBuilder = glide
// .asBitmap()
// .load(thumbnailUrl)
// .diskCacheStrategy(DiskCacheStrategy.ALL);
// glide.asBitmap()
// .load(url)
// .thumbnail(thumbnailRequestBuilder)
// .diskCacheStrategy(DiskCacheStrategy.ALL)
// .into(customTarget);
binding.imageViewer.setTapListener(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapConfirmed(final MotionEvent e) {
if (postClickListener != null) {
postClickListener.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.imageViewer);
return true;
}
return false;
}
});
}
private void setDimensions(final FeedModel feedModel) {
// 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;
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
binding.imageViewer.setAspectRatio(aspectRatio);
// Log.d(TAG, "setDimensions: aspectRatio:" + aspectRatio);
// 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 boolean show) {
if (show) {
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);
}
}
}

View File

@ -1,24 +1,15 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.view.ViewTreeObserver;
import android.widget.ViewSwitcher;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
@ -28,13 +19,18 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -75,69 +71,69 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
}
@Override
public void bindItem(final FeedModel feedModel) {
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0;
if (sliderItemLen <= 0) {
return;
}
public void bindItem(final FeedModel feedModel,
final FeedAdapterV2.OnPostClickListener postClickListener) {
final List<PostChild> sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.size() : 0;
if (sliderItemLen <= 0) return;
final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen));
final PagerAdapter adapter = binding.mediaList.getAdapter();
if (adapter != null) {
final int count = adapter.getCount();
for (int i = 0; i < count; i++) {
adapter.destroyItem(binding.mediaList, i, binding.mediaList.getChildAt(i));
}
binding.mediaList.setOffscreenPageLimit(1);
SliderItemsAdapter adapter = (SliderItemsAdapter) binding.mediaList.getAdapter();
if (adapter == null) {
adapter = new SliderItemsAdapter();
}
final ChildMediaItemsAdapter itemsAdapter = new ChildMediaItemsAdapter(sliderItems,
cacheDataSourceFactory != null
? cacheDataSourceFactory
: dataSourceFactory,
playerChangeListener);
binding.mediaList.setAdapter(itemsAdapter);
//noinspection deprecation
binding.mediaList.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
private int prevPos = 0;
// adapter.setSpanCount(spanCount);
binding.mediaList.setAdapter(adapter);
binding.mediaList.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(final int position) {
ViewerPostModel sliderItem = sliderItems[prevPos];
if (sliderItem != null) {
sliderItem.setSelected(false);
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// stop playing prev video
final ViewSwitcher prevChild = (ViewSwitcher) binding.mediaList.getChildAt(prevPos);
if (prevChild == null || prevChild.getTag() == null || !(prevChild.getTag() instanceof SimpleExoPlayer)) {
return;
}
((SimpleExoPlayer) prevChild.getTag()).setPlayWhenReady(false);
}
}
sliderItem = sliderItems[position];
if (sliderItem == null) return;
sliderItem.setSelected(true);
final String text = (position + 1) + "/" + sliderItemLen;
binding.mediaCounter.setText(text);
prevPos = position;
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
if (shouldAutoPlay) {
autoPlay(position);
}
} else binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
if (position >= sliderItemLen) return;
setDimensions(binding.mediaList, sliderItems.get(position));
}
});
setDimensions(binding.mediaList, sliderItems.get(0));
//noinspection deprecation
// binding.mediaList.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
// private int prevPos = 0;
//
// @Override
// public void onPageSelected(final int position) {
// ViewerPostModel sliderItem = sliderItems.get(prevPos);
// if (sliderItem != null) {
// sliderItem.setSelected(false);
// if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// // stop playing prev video
// final ViewSwitcher prevChild = (ViewSwitcher) binding.mediaList.getChildAt(prevPos);
// if (prevChild == null || prevChild.getTag() == null || !(prevChild.getTag() instanceof SimpleExoPlayer)) {
// return;
// }
// ((SimpleExoPlayer) prevChild.getTag()).setPlayWhenReady(false);
// }
// }
// sliderItem = sliderItems.get(position);
// if (sliderItem == null) return;
// sliderItem.setSelected(true);
// final String text = (position + 1) + "/" + sliderItemLen;
// binding.mediaCounter.setText(text);
// prevPos = position;
// if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
// binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
// if (shouldAutoPlay) {
// autoPlay(position);
// }
// } else binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
// }
// });
adapter.submitList(sliderItems);
final View.OnClickListener muteClickListener = v -> {
final int currentItem = binding.mediaList.getCurrentItem();
if (currentItem < 0 || currentItem >= binding.mediaList.getChildCount()) {
return;
}
final ViewerPostModel sliderItem = sliderItems[currentItem];
final PostChild sliderItem = sliderItems.get(currentItem);
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
@ -156,7 +152,7 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
binding.itemFeedBottom.btnMute.setImageResource(intVol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
Utils.sessionVolumeFull = intVol == 1f;
};
final ViewerPostModel firstItem = sliderItems[0];
final PostChild firstItem = sliderItems.get(0);
if (firstItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
}
@ -164,24 +160,50 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener);
}
private void setDimensions(final View view, final PostChild model) {
final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
int requiredWidth = layoutParams.width;
if (requiredWidth <= 0) {
final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
setLayoutParamDimens(binding.mediaList, model);
return true;
}
};
view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
return;
}
setLayoutParamDimens(binding.mediaList, model);
}
private void setLayoutParamDimens(final View view, final PostChild model) {
final int requiredWidth = view.getMeasuredWidth();
final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
final int spanHeight = NumberUtils.getResultingHeight(requiredWidth, model.getHeight(), model.getWidth());
layoutParams.height = spanHeight == 0 ? requiredWidth + 1 : spanHeight;
view.requestLayout();
}
private void autoPlay(final int position) {
if (!shouldAutoPlay) {
return;
}
final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
if (adapter == null) {
return;
}
final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return;
}
final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
loadPlayer(binding.getRoot().getContext(),
position, sliderItem.getDisplayUrl(),
viewSwitcher,
cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
playerChangeListener);
// if (!shouldAutoPlay) {
// return;
// }
// final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
// if (adapter == null) {
// return;
// }
// final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
// if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
// return;
// }
// final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
// loadPlayer(binding.getRoot().getContext(),
// position, sliderItem.getDisplayUrl(),
// viewSwitcher,
// cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
// playerChangeListener);
}
public void startPlayingVideo() {
@ -200,7 +222,8 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
}
private static void loadPlayer(final Context context,
final int position, final String displayUrl,
final int position,
final String displayUrl,
final ViewSwitcher viewSwitcher,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
@ -223,119 +246,14 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(displayUrl));
final MediaItem mediaItem = MediaItem.fromUri(displayUrl);
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem);
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol);
playerChangeListener.playerChanged(position, player);
viewSwitcher.setTag(player);
}
private static final class ChildMediaItemsAdapter extends PagerAdapter {
// private static final String TAG = "ChildMediaItemsAdapter";
private final ViewerPostModel[] sliderItems;
private final DataSource.Factory factory;
private final PlayerChangeListener playerChangeListener;
private final ViewGroup.LayoutParams layoutParams;
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems,
final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) {
this.sliderItems = sliderItems;
this.factory = factory;
this.playerChangeListener = playerChangeListener;
layoutParams = new ViewGroup.LayoutParams(Utils.displayMetrics.widthPixels, Utils.displayMetrics.widthPixels + 1);
}
@NonNull
@Override
public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
final Context context = container.getContext();
final ViewerPostModel sliderItem = sliderItems[position];
final String displayUrl = sliderItem.getDisplayUrl();
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getSliderDisplayUrl(), displayUrl);
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(displayUrl))
.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 sliderDisplayUrl, 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(sliderDisplayUrl);
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;
}
@Override
public void destroyItem(@NonNull final ViewGroup container, final int position, @NonNull final Object object) {
final View view = container.getChildAt(position);
// Log.d(TAG, "destroy position: " + position + ", view: " + view);
if (view instanceof ViewSwitcher) {
final Object tag = view.getTag();
if (tag instanceof SimpleExoPlayer) {
final SimpleExoPlayer player = (SimpleExoPlayer) tag;
player.release();
}
}
container.removeView((View) object);
}
@Override
public int getCount() {
return sliderItems != null ? sliderItems.length : 0;
}
@Override
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
return view.equals(object);
}
public ViewerPostModel getItemAtPosition(final int position) {
return sliderItems[0];
}
}
}

View File

@ -1,7 +1,6 @@
package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
@ -9,19 +8,14 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
@ -40,14 +34,13 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
private CacheDataSourceFactory cacheDataSourceFactory;
private FeedModel feedModel;
private SimpleExoPlayer player;
private final Runnable loadRunnable = new Runnable() {
@Override
public void run() {
loadPlayer(feedModel);
}
};
// private final Runnable loadRunnable = new Runnable() {
// @Override
// public void run() {
// // loadPlayer(feedModel);
// }
// };
public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding,
final MentionClickListener mentionClickListener,
@ -66,72 +59,55 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
}
@Override
public void bindItem(final FeedModel feedModel) {
public void bindItem(final FeedModel feedModel,
final FeedAdapterV2.OnPostClickListener postClickListener) {
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
this.feedModel = feedModel;
setThumbnail(feedModel);
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(feedModel.getViewCount()));
}
// showOrHideDetails(false);
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
private void setThumbnail(final FeedModel feedModel) {
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
binding.thumbnailParent.requestLayout();
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl()))
.setProgressiveRenderingEnabled(true)
.build();
final DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(thumbnailRequest)
.build();
binding.thumbnail.setController(controller);
binding.thumbnailParent.setOnClickListener(v -> loadPlayer(feedModel));
}
@Override
public void onThumbnailClick() {
postClickListener.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.videoPost.thumbnail);
}
private void loadPlayer(final FeedModel feedModel) {
if (feedModel == null) {
return;
}
// Log.d(TAG, "playing post:" + feedModel.getPostId());
if (binding.viewSwitcher.getDisplayedChild() == 0) {
binding.viewSwitcher.showNext();
}
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, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.playerView.requestLayout();
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
setMuteIcon(vol);
player = (SimpleExoPlayer) binding.playerView.getPlayer();
if (player != null) {
player.release();
}
player = new SimpleExoPlayer.Builder(itemView.getContext())
.setLooper(Looper.getMainLooper())
.build();
player.setVolume(vol);
player.setPlayWhenReady(true);
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource);
binding.playerView.setPlayer(player);
final SimpleExoPlayer finalPlayer = player;
@Override
public void onPlayerViewLoaded() {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams();
final int requiredWidth = Utils.displayMetrics.widthPixels;
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
layoutParams.width = requiredWidth;
layoutParams.height = resultingHeight;
binding.videoPost.playerView.requestLayout();
setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
}
};
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final Uri uri = Uri.parse(feedModel.getDisplayUrl());
// final MediaItem mediaItem = MediaItem.fromUri(uri);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
final VideoPlayerViewHelper videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
binding.videoPost,
feedModel.getDisplayUrl(),
vol,
aspectRatio,
feedModel.getThumbnailUrl(),
null,
videoPlayerCallback);
binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
final float intVol = finalPlayer.getVolume() == 0f ? 1f : 0f;
finalPlayer.setVolume(intVol);
setMuteIcon(intVol);
Utils.sessionVolumeFull = intVol == 1f;
final float newVol = videoPlayerViewHelper.toggleMute();
setMuteIcon(newVol);
Utils.sessionVolumeFull = newVol == 1f;
});
binding.playerView.setOnClickListener(v -> finalPlayer.setPlayWhenReady(!finalPlayer.getPlayWhenReady()));
binding.videoPost.playerView.setOnClickListener(v -> videoPlayerViewHelper.togglePlayback());
}
private void setMuteIcon(final float vol) {
binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
}
@ -140,19 +116,29 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
return feedModel;
}
public void stopPlaying() {
// Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
handler.removeCallbacks(loadRunnable);
if (player != null) {
player.release();
}
if (binding.viewSwitcher.getDisplayedChild() == 1) {
binding.viewSwitcher.showPrevious();
}
}
// public void stopPlaying() {
// // Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
// handler.removeCallbacks(loadRunnable);
// if (player != null) {
// player.release();
// }
// if (binding.videoPost.root.getDisplayedChild() == 1) {
// binding.videoPost.root.showPrevious();
// }
// }
//
// public void startPlaying() {
// handler.removeCallbacks(loadRunnable);
// handler.postDelayed(loadRunnable, 800);
// }
public void startPlaying() {
handler.removeCallbacks(loadRunnable);
handler.postDelayed(loadRunnable, 800);
private void showOrHideDetails(final boolean show) {
if (show) {
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);
}
}
}

View File

@ -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;
}
}

View File

@ -12,7 +12,6 @@ import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
@ -177,11 +176,7 @@ public final class DownloadAsync extends AsyncTask<Void, Float, File> {
protected void onPostExecute(final File result) {
if (result != null) {
final Context context = this.context.get();
context.sendBroadcast(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ?
new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())) :
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile()))
);
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) {

View File

@ -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();
// }
// }

View File

@ -3,20 +3,22 @@ package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NetworkUtils;
@ -26,27 +28,27 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
public final class FeedFetcher extends AsyncTask<Void, Void, List<FeedModel>> {
private static final String TAG = "FeedFetcher";
private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts, setting more than 30 is gay
private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts
private final String endCursor;
private final FetchListener<FeedModel[]> fetchListener;
private final FetchListener<List<FeedModel>> fetchListener;
public FeedFetcher(final FetchListener<FeedModel[]> fetchListener) {
public FeedFetcher(final FetchListener<List<FeedModel>> fetchListener) {
this.endCursor = "";
this.fetchListener = fetchListener;
}
public FeedFetcher(final String endCursor, final FetchListener<FeedModel[]> fetchListener) {
public FeedFetcher(final String endCursor, final FetchListener<List<FeedModel>> fetchListener) {
this.endCursor = endCursor == null ? "" : endCursor;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected final FeedModel[] doInBackground(final Void... voids) {
FeedModel[] result = null;
protected final List<FeedModel> doInBackground(final Void... voids) {
final List<FeedModel> result = new ArrayList<>();
HttpURLConnection urlConnection = null;
try {
//
// stories: 04334405dbdef91f2c4e207b84c204d7 && https://i.instagram.com/api/v1/feed/reels_tray/
@ -62,13 +64,14 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
final String url = "https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=" +
"{\"fetch_media_item_count\":" + maxItemsToLoad + ",\"has_threaded_comments\":true,\"fetch_media_item_cursor\":\"" + endCursor + "\"}";
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection = (HttpURLConnection) new URL(url).openConnection();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
final String json = NetworkUtils.readFromConnection(urlConnection);
// Log.d(TAG, json);
final JSONObject timelineFeed = new JSONObject(json).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_web_feed_timeline");
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("edge_web_feed_timeline");
final String endCursor;
final boolean hasNextPage;
@ -84,9 +87,7 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
final int feedLen = feedItems.length();
final ArrayList<FeedModel> feedModelsList = new ArrayList<>(feedLen);
for (int i = 0; i < feedLen; ++i) {
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))
@ -99,9 +100,11 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
if (TextUtils.isEmpty(displayUrl)) continue;
final String resourceUrl;
if (isVideo) resourceUrl = feedItem.getString("video_url");
else
if (isVideo) {
resourceUrl = feedItem.getString("video_url");
} else {
resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl;
}
ProfileModel profileModel = null;
if (feedItem.has("owner")) {
@ -125,20 +128,17 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
false,
false);
}
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment");
final long commentsCount = 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)
(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;
@ -152,92 +152,119 @@ public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> {
}
// Log.d(TAG, "locationId: " + locationId);
}
final FeedModel feedModel = new FeedModel(
profileModel,
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
videoViews,
feedItem.getString(Constants.EXTRAS_ID),
resourceUrl,
displayUrl,
feedItem.getString(Constants.EXTRAS_SHORTCODE),
captionText,
commentsCount,
feedItem.optLong("taken_at_timestamp", -1),
feedItem.getBoolean("viewer_has_liked"),
feedItem.getBoolean("viewer_has_saved"),
feedItem.getJSONObject("edge_media_preview_like").getLong("count"),
locationName,
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(feedItem.getJSONObject("edge_media_preview_like")
.getLong("count"))
.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 ViewerPostModel[] sliderItems = new ViewerPostModel[children.length()];
for (int j = 0; j < sliderItems.length; ++j) {
final JSONObject node = children.optJSONObject(j).getJSONObject("node");
final boolean isChildVideo = node.optBoolean("is_video");
sliderItems[j] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
node.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : ResponseBodyUtils.getHighQualityImage(node),
null,
null,
null,
node.optLong("video_view_count", -1),
-1,
false,
false,
feedItem.getJSONObject("edge_media_preview_like").getLong("count"),
locationName,
locationId);
sliderItems[j].setSliderDisplayUrl(node.getString("display_url"));
}
feedModel.setSliderItems(sliderItems);
final List<PostChild> sliderItems = getSliderItems(children);
feedModelBuilder.setSliderItems(sliderItems);
}
}
}
feedModelsList.add(feedModel);
final FeedModel feedModel = feedModelBuilder.build();
result.add(feedModel);
}
feedModelsList.trimToSize();
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]);
final int length = feedModels.length;
if (length >= 1 && feedModels[length - 1] != null) {
feedModels[length - 1].setPageCursor(hasNextPage, endCursor);
if (!result.isEmpty() && result.get(result.size() - 1) != null) {
result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor);
}
result = feedModels;
}
urlConnection.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FEED_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return result;
}
@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", -1))
.setHeight(height)
.setWidth(width)
.build();
// Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem);
sliderItems.add(sliderItem);
}
return sliderItems;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final FeedModel[] postModels) {
protected void onPostExecute(final List<FeedModel> postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
}
}

View File

@ -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;
}
}

View File

@ -10,14 +10,16 @@ import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
@ -29,23 +31,23 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> {
public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
private static final String TAG = "PostFetcher";
private final String shortCode;
private final FetchListener<ViewerPostModel[]> fetchListener;
private final FetchListener<FeedModel> fetchListener;
public PostFetcher(final String shortCode, final FetchListener<ViewerPostModel[]> fetchListener) {
public PostFetcher(final String shortCode, final FetchListener<FeedModel> fetchListener) {
this.shortCode = shortCode;
this.fetchListener = fetchListener;
}
@Override
protected ViewerPostModel[] doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
protected FeedModel doInBackground(final Void... voids) {
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download
HttpURLConnection conn = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
conn.setUseCaches(false);
conn.connect();
@ -53,7 +55,6 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
final JSONObject media = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
.getJSONObject("shortcode_media");
ProfileModel profileModel = null;
if (media.has("owner")) {
final JSONObject owner = media.getJSONObject("owner");
@ -111,75 +112,63 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
JSONObject commentObject = media.optJSONObject("edge_media_to_parent_comment");
final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0;
String endCursor = null;
if (commentObject != null && (commentObject = commentObject.optJSONObject("page_info")) != null) {
endCursor = commentObject.optString("end_cursor");
}
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) {
final ViewerPostModel postModel = new ViewerPostModel(
mediaItemType,
media.getString(Constants.EXTRAS_ID),
isVideo ? media.getString("video_url") : ResponseBodyUtils.getHighQualityImage(media),
shortCode,
TextUtils.isEmpty(postCaption) ? null : postCaption,
profileModel,
isVideo && media.has("video_view_count") ? media.getLong("video_view_count") : -1,
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"),
media.getJSONObject("edge_media_preview_like").getLong("count"),
media.isNull("location") ? null : media.getJSONObject("location").optString("name"),
media.isNull("location") ? null :
(media.getJSONObject("location").optString("id") + "/" +
media.getJSONObject("location").optString("slug")));
postModel.setCommentsCount(commentsCount);
DownloadUtils.checkExistence(downloadDir, customDir, false, postModel);
result = new ViewerPostModel[]{postModel};
} else {
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
.setItemType(mediaItemType)
.setPostId(media.getString(Constants.EXTRAS_ID))
.setDisplayUrl(isVideo ? media.getString("video_url")
: ResponseBodyUtils.getHighQualityImage(media))
.setShortCode(shortCode)
.setPostCaption(TextUtils.isEmpty(postCaption) ? null : postCaption)
.setProfileModel(profileModel)
.setViewCount(isVideo && media.has("video_view_count")
? media.getLong("video_view_count")
: -1)
.setTimestamp(timestamp)
.setLiked(media.getBoolean("viewer_has_liked"))
.setBookmarked(media.getBoolean("viewer_has_saved"))
.setLikesCount(media.getJSONObject("edge_media_preview_like")
.getLong("count"))
.setLocationName(media.isNull("location")
? null
: media.getJSONObject("location").optString("name"))
.setLocationId(media.isNull("location")
? null
: media.getJSONObject("location").optString("id"))
.setCommentsCount(commentsCount);
// DownloadUtils.checkExistence(downloadDir, customDir, false, feedModelBuilder);
if (isSlider) {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()];
for (int i = 0; i < postModels.length; ++i) {
final JSONObject node = children.getJSONObject(i).getJSONObject("node");
final boolean isChildVideo = node.getBoolean("is_video");
postModels[i] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
media.getString(Constants.EXTRAS_ID),
isChildVideo ? node.getString("video_url") : ResponseBodyUtils.getHighQualityImage(node),
node.getString(Constants.EXTRAS_SHORTCODE),
postCaption,
profileModel,
isChildVideo && node.has("video_view_count") ? node.getLong("video_view_count") : -1,
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"),
media.getJSONObject("edge_media_preview_like").getLong("count"),
media.isNull("location") ? null : media.getJSONObject("location").optString("name"),
media.isNull("location") ? null :
(media.getJSONObject("location").optString("id") + "/" +
media.getJSONObject("location").optString("slug")));
postModels[i].setSliderDisplayUrl(node.getString("display_url"));
DownloadUtils.checkExistence(downloadDir, customDir, true, postModels[i]);
final List<PostChild> postModels = new ArrayList<>();
for (int i = 0; i < children.length(); ++i) {
final JSONObject childNode = children.getJSONObject(i).getJSONObject("node");
final boolean isChildVideo = childNode.getBoolean("is_video");
postModels.add(new PostChild.Builder()
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE)
.setDisplayUrl(isChildVideo ? childNode.getString("video_url")
: childNode.getString("display_url"))
// .setShortCode(childNode.getString(Constants.EXTRAS_SHORTCODE))
.setVideoViews(isChildVideo && childNode.has("video_view_count")
? childNode.getLong("video_view_count")
: -1)
.build());
// DownloadUtils.checkExistence(downloadDir, customDir, true, postModels.get(i));
}
postModels[0].setCommentsCount(commentsCount);
result = postModels;
feedModelBuilder.setSliderItems(postModels);
}
return feedModelBuilder.build();
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
}
Log.e(TAG, "Error fetching post", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return result;
return null;
}
@Override
@ -188,7 +177,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
}
@Override
protected void onPostExecute(final ViewerPostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
protected void onPostExecute(final FeedModel feedModel) {
if (fetchListener != null) fetchListener.onResult(feedModel);
}
}

View File

@ -153,8 +153,8 @@ public final class PostsFetcher extends AsyncTask<Void, Void, List<PostModel>> {
: null,
mediaNode.getLong("taken_at_timestamp"),
mediaNode.optBoolean("viewer_has_liked"),
mediaNode.optBoolean("viewer_has_saved"),
mediaNode.isNull("edge_liked_by") ? 0 : mediaNode.getJSONObject("edge_liked_by").getLong("count")
mediaNode.optBoolean("viewer_has_saved")
// , mediaNode.isNull("edge_liked_by") ? 0 : mediaNode.getJSONObject("edge_liked_by").getLong("count")
);
result.add(model);
DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);

View File

@ -96,8 +96,9 @@ public final class iLikedFetcher extends AsyncTask<Void, Void, List<PostModel>>
mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"),
mediaNode.getLong("taken_at"),
true,
mediaNode.optBoolean("has_viewer_saved"),
mediaNode.getLong("like_count"));
mediaNode.optBoolean("has_viewer_saved")
// , mediaNode.getLong("like_count")
);
result.add(model);
String username = mediaNode.getJSONObject("user").getString("username");
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" +

View File

@ -10,14 +10,16 @@ import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
@ -29,22 +31,22 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Utils.logCollector;
public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> {
public final class iPostFetcher extends AsyncTask<Void, Void, FeedModel> {
private static final String TAG = "iPostFetcher";
private final String id;
private final FetchListener<ViewerPostModel[]> fetchListener;
private final FetchListener<FeedModel> fetchListener;
public iPostFetcher(final String id, final FetchListener<ViewerPostModel[]> fetchListener) {
public iPostFetcher(final String id, final FetchListener<FeedModel> fetchListener) {
this.id = id;
this.fetchListener = fetchListener;
}
@Override
protected ViewerPostModel[] doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
protected FeedModel doInBackground(final Void... voids) {
HttpURLConnection conn = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://i.instagram.com/api/v1/media/" + id + "/info").openConnection();
conn = (HttpURLConnection) new URL("https://i.instagram.com/api/v1/media/" + id + "/info").openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.connect();
@ -86,7 +88,7 @@ public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
);
}
if (profileModel == null) {
return new ViewerPostModel[]{};
return new FeedModel.Builder().build();
}
// to check if file exists
@ -130,69 +132,65 @@ public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
}
}
// final String locationString = location.optString("id") + "/" + location.optString("slug");
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) {
final ViewerPostModel postModel = new ViewerPostModel(
mediaItemType,
media.getString(Constants.EXTRAS_ID),
isVideo ? ResponseBodyUtils.getHighQualityPost(media.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(media),
media.getString("code"),
TextUtils.isEmpty(postCaption) ? null : postCaption,
profileModel,
isVideo && media.has("view_count") ? media.getLong("view_count") : -1,
timestamp, media.optBoolean("has_liked"),
media.optBoolean("has_viewer_saved"),
media.getLong("like_count"),
locationName,
locationId);
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
.setItemType(mediaItemType)
.setPostId(media.getString(Constants.EXTRAS_ID))
.setDisplayUrl(isVideo ? ResponseBodyUtils.getHighQualityPost(media.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(media))
.setShortCode(media.getString("code"))
.setPostCaption(TextUtils.isEmpty(postCaption) ? null : postCaption)
.setProfileModel(profileModel)
.setViewCount(isVideo && media.has("view_count")
? media.getLong("view_count")
: -1)
.setTimestamp(timestamp)
.setLiked(media.optBoolean("has_liked"))
.setBookmarked(media.optBoolean("has_viewer_saved"))
.setLikesCount(media.getLong("like_count"))
.setLocationName(locationName)
.setLocationId(locationId)
.setCommentsCount(commentsCount);
// DownloadUtils.checkExistence(downloadDir, customDir, false, postModel);
postModel.setCommentsCount(commentsCount);
DownloadUtils.checkExistence(downloadDir, customDir, false, postModel);
result = new ViewerPostModel[]{postModel};
} else {
if (isSlider) {
final JSONArray children = media.getJSONArray("carousel_media");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()];
for (int i = 0; i < postModels.length; ++i) {
final JSONObject node = children.getJSONObject(i);
final boolean isChildVideo = node.has("video_duration");
postModels[i] = new ViewerPostModel(
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE,
media.getString(Constants.EXTRAS_ID),
isChildVideo ? ResponseBodyUtils.getHighQualityPost(node.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(node),
media.getString("code"),
postCaption,
profileModel,
-1,
timestamp, media.optBoolean("has_liked"),
media.optBoolean("has_viewer_saved"),
media.getLong("like_count"),
locationName,
locationId);
postModels[i].setSliderDisplayUrl(ResponseBodyUtils.getHighQualityImage(node));
DownloadUtils.checkExistence(downloadDir, customDir, true, postModels[i]);
final List<PostChild> postModels = new ArrayList<>();
for (int i = 0; i < children.length(); ++i) {
final JSONObject childNode = children.getJSONObject(i);
final boolean isChildVideo = childNode.has("video_duration");
postModels.add(new PostChild.Builder()
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE)
.setPostId(childNode.has(Constants.EXTRAS_ID)
? childNode.getString(Constants.EXTRAS_ID)
: media.getString(Constants.EXTRAS_ID))
// .setShortCode(childNode.optString(Constants.EXTRAS_SHORTCODE))
.setDisplayUrl(
isChildVideo ? ResponseBodyUtils.getHighQualityPost(
childNode.optJSONArray("video_versions"), true, true, false)
: ResponseBodyUtils.getHighQualityImage(childNode))
.setVideoViews(isChildVideo && childNode.has("video_view_count")
? childNode.getLong("video_view_count")
: -1)
.build());
// DownloadUtils.checkExistence(downloadDir, customDir, true, postModels[i]);
}
postModels[0].setCommentsCount(commentsCount);
result = postModels;
feedModelBuilder.setSliderItems(postModels);
}
return feedModelBuilder.build();
}
conn.disconnect();
} catch (Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground (i)");
if (BuildConfig.DEBUG) {
Log.e(TAG, "", e);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
return result;
return null;
}
@Override
@ -201,7 +199,7 @@ public final class iPostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
}
@Override
protected void onPostExecute(final ViewerPostModel[] postModels) {
if (fetchListener != null) fetchListener.onResult(postModels);
protected void onPostExecute(final FeedModel feedModel) {
if (fetchListener != null) fetchListener.onResult(feedModel);
}
}

View File

@ -2,10 +2,7 @@ package awais.instagrabber.customviews;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
@ -17,36 +14,27 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.drawee.view.SimpleDraweeView;
public final class CircularImageView extends SimpleDraweeView {
private final int borderSize = 8;
private int color = Color.TRANSPARENT;
private final Paint paint = new Paint();
private final Paint paintBorder = new Paint();
private BitmapShader shader;
private Bitmap bitmap;
import awais.instagrabber.R;
public class CircularImageView extends SimpleDraweeView {
public CircularImageView(Context context, GenericDraweeHierarchy hierarchy) {
super(context);
setHierarchy(hierarchy);
setup();
}
public CircularImageView(final Context context) {
super(context);
inflateHierarchy(context, null);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs) {
super(context, attrs);
inflateHierarchy(context, attrs);
setup();
}
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
inflateHierarchy(context, attrs);
setup();
}
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
@ -58,77 +46,12 @@ public final class CircularImageView extends SimpleDraweeView {
GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs);
setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build());
setBackgroundResource(R.drawable.shape_oval_light);
}
private void setup() {
// paint.setAntiAlias(true);
// paintBorder.setColor(color);
// paintBorder.setAntiAlias(true);
//
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// setOutlineProvider(new ViewOutlineProvider() {
// private int viewHeight;
// private int viewWidth;
//
// @Override
// public void getOutline(final View view, final Outline outline) {
// if (viewHeight == 0) viewHeight = getHeight();
// if (viewWidth == 0) viewWidth = getWidth();
// outline.setRoundRect(borderSize, borderSize, viewWidth - borderSize, viewHeight - borderSize, viewHeight >> 1);
// }
// });
// }
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources())
// .setRoundingParams(RoundingParams.)
// .build();
// setHierarchy(hierarchy);
// invalidate();
}
// @Override
// public void onDraw(final Canvas canvas) {
// final BitmapDrawable bitmapDrawable = (BitmapDrawable) getDrawable();
// if (bitmapDrawable != null) {
// final Bitmap prevBitmap = bitmap;
// bitmap = bitmapDrawable.getBitmap();
// final boolean changed = prevBitmap != bitmap;
// if (bitmap != null) {
// final int width = getWidth();
// final int height = getHeight();
//
// if (shader == null || changed) {
// shader = new BitmapShader(Bitmap.createScaledBitmap(bitmap, width, height, true), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
// paint.setShader(shader);
// }
//
// if (changed) color = 0;
// paintBorder.setColor(color);
//
// final int circleCenter = (width - borderSize) / 2;
// final int position = circleCenter + (borderSize / 2);
// canvas.drawCircle(position, position, position - 4.0f, paintBorder);
// canvas.drawCircle(position, position, circleCenter - 4.0f, paint);
// }
// }
// }
//
// @Override
// protected void onAttachedToWindow() {
// super.onAttachedToWindow();
// setLayerType(LAYER_TYPE_HARDWARE, null);
// }
//
// @Override
// protected void onDetachedFromWindow() {
// setLayerType(LAYER_TYPE_NONE, null);
// super.onDetachedFromWindow();
// }
public void setStoriesBorder() {
this.color = Color.GREEN;
// invalidate();
// final RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f);
//
// 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);

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -34,7 +34,6 @@ public final class RamboTextView extends AppCompatTextView {
private ClickableSpan clickableSpanUnderTouchOnActionDown;
private MentionClickListener mentionClickListener;
private boolean isUrlHighlighted;
private boolean isExpandable;
private boolean isExpanded;
private OnLongClickListener longClickListener;
@ -59,13 +58,13 @@ public final class RamboTextView extends AppCompatTextView {
this.mentionClickListener = mentionClickListener;
}
public void setCaptionIsExpandable(final boolean isExpandable) {
this.isExpandable = isExpandable;
}
// public void setCaptionIsExpandable(final boolean isExpandable) {
// this.isExpandable = isExpandable;
// }
public void setCaptionIsExpanded(final boolean isExpanded) {
this.isExpanded = isExpanded;
}
// public void setCaptionIsExpanded(final boolean isExpanded) {
// this.isExpanded = isExpanded;
// }
@Override
public void setOnLongClickListener(@Nullable final OnLongClickListener l) {
@ -98,7 +97,7 @@ public final class RamboTextView extends AppCompatTextView {
case MotionEvent.ACTION_DOWN:
final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
handler.postDelayed(longPressRunnable, longPressTimeout);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) {
highlightUrl(clickableSpanUnderTouch, spanText);
}
@ -107,19 +106,19 @@ public final class RamboTextView extends AppCompatTextView {
handler.removeCallbacks(longPressRunnable);
if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
dispatchUrlClick(spanText, clickableSpanUnderTouch);
if (feedModel != null) feedModel.setMentionClicked(true);
// if (feedModel != null) feedModel.setMentionClicked(true);
}
cleanupOnTouchUp(spanText);
return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE:
// handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText);
else removeUrlHighlightColor(spanText);
return super.onTouchEvent(event);
case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false);
// if (feedModel != null) feedModel.setMentionClicked(false);
cleanupOnTouchUp(spanText);
return super.onTouchEvent(event);
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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() {}
}

View File

@ -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();
}
}

View File

@ -572,16 +572,16 @@ public class DefaultZoomableController
RectF b = mTempRect;
b.set(mImageBounds);
transform.mapRect(b);
float offsetLeft =
shouldLimit(limitTypes, LIMIT_TRANSLATION_X)
? getOffset(
b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
: 0;
float offsetTop =
shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
? getOffset(
b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
: 0;
final boolean shouldLimitX = shouldLimit(limitTypes, LIMIT_TRANSLATION_X);
float offsetLeft = shouldLimitX
? getOffset(b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
: 0;
float offsetTop = shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
? getOffset(b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
: 0;
if (mListener != null) {
mListener.onTranslationLimited(offsetLeft, offsetTop);
}
if (offsetLeft != 0 || offsetTop != 0) {
transform.postTranslate(offsetLeft, offsetTop);
return true;

View File

@ -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;
}
}

View File

@ -41,6 +41,13 @@ public class MultiZoomableControllerListener implements ZoomableController.Liste
}
}
@Override
public void onTranslationLimited(final float offsetLeft, final float offsetTop) {
for (ZoomableController.Listener listener : mListeners) {
listener.onTranslationLimited(offsetLeft, offsetTop);
}
}
public synchronized void addListener(ZoomableController.Listener listener) {
mListeners.add(listener);
}

View File

@ -42,6 +42,8 @@ public interface ZoomableController {
* @param transform the current transform matrix
*/
void onTransformEnd(Matrix transform);
void onTranslationLimited(float offsetLeft, float offsetTop);
}
/**

View File

@ -33,8 +33,6 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeView;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
/**
* DraweeView that has zoomable capabilities.
@ -54,7 +52,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
private DraweeController mHugeImageController;
private ZoomableController mZoomableController;
private GestureDetector mTapGestureDetector;
private boolean mAllowTouchInterceptionWhileZoomed = true;
private boolean mAllowTouchInterceptionWhileZoomed = false;
private boolean mIsDialtoneEnabled = false;
private boolean mZoomingEnabled = true;
@ -76,7 +74,9 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
private final ZoomableController.Listener mZoomableListener =
new ZoomableController.Listener() {
@Override
public void onTransformBegin(Matrix transform) {}
public void onTransformBegin(Matrix transform) {
ZoomableDraweeView.this.onTransformBegin(transform);
}
@Override
public void onTransformChanged(Matrix transform) {
@ -84,7 +84,14 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
}
@Override
public void onTransformEnd(Matrix transform) {}
public void onTransformEnd(Matrix transform) {
ZoomableDraweeView.this.onTransformEnd(transform);
}
@Override
public void onTranslationLimited(final float offsetLeft, final float offsetTop) {
ZoomableDraweeView.this.onTranslationLimited(offsetLeft, offsetTop);
}
};
private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper();
@ -302,11 +309,10 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
int a = event.getActionMasked();
FLog.v(getLogTag(), "onTouchEvent: %d, view %x, received", a, this.hashCode());
if (!mIsDialtoneEnabled && mTapGestureDetector.onTouchEvent(event)) {
FLog.v(
getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
FLog.v(getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
return true;
}
@ -389,23 +395,29 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
mZoomableController.setEnabled(false);
}
protected void onTransformBegin(final Matrix transform) {}
protected void onTransformChanged(Matrix transform) {
FLog.v(getLogTag(), "onTransformChanged: view %x, transform: %s", this.hashCode(), transform);
maybeSetHugeImageController();
invalidate();
}
protected void onTransformEnd(final Matrix transform) {}
protected void onTranslationLimited(final float offsetLeft, final float offsetTop) {}
protected void updateZoomableControllerBounds() {
getImageBounds(mImageBounds);
getLimitBounds(mViewBounds);
// Log.d(TAG.getSimpleName(), "updateZoomableControllerBounds: mImageBounds: " + mImageBounds);
mZoomableController.setImageBounds(mImageBounds);
mZoomableController.setViewBounds(mViewBounds);
FLog.v(
getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,
mImageBounds);
FLog.v(getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,
mImageBounds);
}
protected Class<?> getLogTag() {

View File

@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@ -16,16 +15,14 @@ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final RecyclerView.LayoutManager manager = parent.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final int spanCount = ((GridLayoutManager) manager).getSpanCount();
final int position = parent.getChildAdapterPosition(view);
final int column = position % spanCount;
outRect.left = column * spacing / spanCount;
outRect.right = spacing - (column + 1) * spacing / spanCount;
if (position < spanCount) outRect.top = spacing;
outRect.bottom = spacing;
final int halfSpace = spacing / 2;
if (parent.getPaddingLeft() != halfSpace) {
parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
parent.setClipToPadding(false);
}
outRect.top = halfSpace;
outRect.bottom = halfSpace;
outRect.left = halfSpace;
outRect.right = halfSpace;
}
}

View File

@ -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();
}
}

View File

@ -1,23 +1,41 @@
package awais.instagrabber.customviews.helpers;
import android.os.Handler;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import awais.instagrabber.interfaces.LazyLoadListener;
// thanks to nesquena's EndlessRecyclerViewScrollListener
// https://gist.github.com/nesquena/d09dc68ff07e845cc622
/**
* thanks to nesquena's <a href="https://gist.github.com/nesquena/d09dc68ff07e845cc622">EndlessRecyclerViewScrollListener</a>
*/
public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
private int currentPage = 0; // The current offset index of data you have loaded
private int previousTotalItemCount = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private final int visibleThreshold; // The minimum amount of items to have below your current scroll position before loading more.
/**
* The current offset index of data you have loaded
*/
private int currentPage = 0;
/**
* The total number of items in the data set after the last load
*/
private int previousTotalItemCount = 0;
/**
* <code>true</code> if we are still waiting for the last set of data to load.
*/
private boolean loading = true;
/**
* The minimum amount of items to have below your current scroll position before loading more.
*/
private final int visibleThreshold;
private final LazyLoadListener lazyLoadListener;
private final RecyclerView.LayoutManager layoutManager;
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener, final int threshold) {
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
final LazyLoadListener lazyLoadListener,
final int threshold) {
this.layoutManager = layoutManager;
this.lazyLoadListener = lazyLoadListener;
if (threshold > 0) {
@ -26,6 +44,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
}
if (layoutManager instanceof GridLayoutManager) {
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
this.visibleThreshold = 4 * Math.max(3, ((StaggeredGridLayoutManager) layoutManager).getSpanCount());
} else if (layoutManager instanceof LinearLayoutManager) {
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8;
} else {
@ -33,7 +53,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
}
}
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener) {
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
final LazyLoadListener lazyLoadListener) {
this(layoutManager, lazyLoadListener, -1);
}
@ -52,22 +73,37 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
previousTotalItemCount = totalItemCount;
}
final int lastVisibleItemPosition;
int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
final StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) this.layoutManager;
final int spanCount = layoutManager.getSpanCount();
final int[] lastVisibleItemPositions = layoutManager.findLastVisibleItemPositions(null);
lastVisibleItemPosition = 0;
for (final int itemPosition : lastVisibleItemPositions) {
if (itemPosition > lastVisibleItemPosition) {
lastVisibleItemPosition = itemPosition;
}
}
} else {
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
}
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
if (lazyLoadListener != null)
lazyLoadListener.onLoadMore(++currentPage, totalItemCount);
loading = true;
if (lazyLoadListener != null) {
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage, totalItemCount), 200);
}
}
}
public int getCurrentPage() {
return currentPage;
}
public void resetState() {
this.currentPage = 0;
this.previousTotalItemCount = 0;

View File

@ -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);
}
}

View File

@ -70,7 +70,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos);
if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) {
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
currentlyPlayingViewHolder = null;
}
return;
@ -80,9 +80,9 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
return;
}
if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
}
videoHolder.startPlaying();
// videoHolder.startPlaying();
currentlyPlayingViewHolder = videoHolder;
}
// boolean processFirstItem = false, processLastItem = false;
@ -196,7 +196,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) {
final View view = layoutManager.findViewByPosition(pos);
if (view != null && view.getId() == R.id.videoHolder) {
final View viewSwitcher = view.findViewById(R.id.view_switcher);
final View viewSwitcher = view.findViewById(R.id.root);
if (viewSwitcher == null) {
continue;
}
@ -220,113 +220,113 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
if (currentlyPlayingViewHolder == null) {
return;
}
currentlyPlayingViewHolder.startPlaying();
// currentlyPlayingViewHolder.startPlaying();
}
public void stopPlaying() {
if (currentlyPlayingViewHolder == null) {
return;
}
currentlyPlayingViewHolder.stopPlaying();
// currentlyPlayingViewHolder.stopPlaying();
}
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// synchronized (LOCK) {
// if (recyclerView != null) {
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
// if (adapter instanceof FeedAdapter) {
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
// }
// }
// if (itemView == null) {
// return;
// }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// }
// }
//
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) {
// return;
// }
// if (player != null) {
// player.stop(true);
// player.release();
// player = null;
// }
//
// player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay)
// .build();
// player.setPlayWhenReady(shouldAutoplay);
//
// final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else {
// btnComments.setTag(feedModel);
// btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener);
// }
// }
// playerView.setPlayer(player);
// btnMute = itemView.findViewById(R.id.btnMute);
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
// player.setVolume(vol);
//
// if (btnMute != null) {
// btnMute.setVisibility(View.VISIBLE);
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
// btnMute.setOnClickListener(muteClickListener);
// }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
//
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource);
// player.setVolume(vol);
//
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
//
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
// }
//
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // Log.d("AWAISKING_APP", "release: " + itemPos);
// // if (player != null) {
// // player.stop(true);
// // player.release();
// // }
// // player = null;
// }
//
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // if (player != null) {
// // final int playbackState = player.getPlaybackState();
// // if (!player.isPlaying()
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// // ) {
// // player.setPlayWhenReady(true);
// // }
// // }
// // if (player != null) {
// // player.setPlayWhenReady(true);
// // player.getPlaybackState();
// // }
// }
//
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) {
// player.setPlayWhenReady(false);
// player.getPlaybackState();
// }
// }
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// synchronized (LOCK) {
// if (recyclerView != null) {
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
// if (adapter instanceof FeedAdapter) {
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
// }
// }
// if (itemView == null) {
// return;
// }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// }
// }
//
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) {
// return;
// }
// if (player != null) {
// player.stop(true);
// player.release();
// player = null;
// }
//
// player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay)
// .build();
// player.setPlayWhenReady(shouldAutoplay);
//
// final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else {
// btnComments.setTag(feedModel);
// btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener);
// }
// }
// playerView.setPlayer(player);
// btnMute = itemView.findViewById(R.id.btnMute);
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
// player.setVolume(vol);
//
// if (btnMute != null) {
// btnMute.setVisibility(View.VISIBLE);
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
// btnMute.setOnClickListener(muteClickListener);
// }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
//
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource);
// player.setVolume(vol);
//
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
//
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
// }
//
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // Log.d("AWAISKING_APP", "release: " + itemPos);
// // if (player != null) {
// // player.stop(true);
// // player.release();
// // }
// // player = null;
// }
//
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // if (player != null) {
// // final int playbackState = player.getPlaybackState();
// // if (!player.isPlaying()
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// // ) {
// // player.setPlayWhenReady(true);
// // }
// // }
// // if (player != null) {
// // player.setPlayWhenReady(true);
// // player.getPlaybackState();
// // }
// }
//
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) {
// player.setPlayWhenReady(false);
// player.getPlaybackState();
// }
// }
public interface VideoChangeCallback {
void playerChanged(final int itemPos, final SimpleExoPlayer player);

View File

@ -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);
}
}

View File

@ -21,15 +21,16 @@ import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import java.util.Arrays;
import java.util.Collections;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.IntentUtils;
@ -115,7 +116,7 @@ public final class DirectDownload extends Activity {
if (model != null && model.getType() == IntentModelType.POST) {
final String text = model.getText();
new PostFetcher(text, new FetchListener<ViewerPostModel[]>() {
new PostFetcher(text, new FetchListener<FeedModel>() {
@Override
public void doBefore() {
final Notification fetchingPostNotif = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID)
@ -126,13 +127,15 @@ public final class DirectDownload extends Activity {
}
@Override
public void onResult(final ViewerPostModel[] result) {
public void onResult(final FeedModel result) {
if (notificationManager != null) notificationManager.cancel(1900000000);
if (result != null) {
if (result.length == 1) {
DownloadUtils.batchDownload(context, result[0].getProfileModel().getUsername(), DownloadMethod.DOWNLOAD_DIRECT,
Arrays.asList(result));
} else if (result.length > 1) {
if (result.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER) {
DownloadUtils.batchDownload(context,
result.getProfileModel().getUsername(),
DownloadMethod.DOWNLOAD_DIRECT,
Collections.singletonList(result));
} else {
context.startActivity(new Intent(context, MultiDirectDialog.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.putExtra(Constants.EXTRAS_POST, result));

View File

@ -18,8 +18,8 @@ import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
@ -39,30 +39,29 @@ public final class MultiDirectDialog extends BaseLanguageActivity {
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
final ViewerPostModel[] postModels;
final FeedModel feedModel;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST)
|| (postModels = (ViewerPostModel[]) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) {
|| (feedModel = (FeedModel) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) {
Utils.errorFinish(this);
return;
}
username = postModels[0].getProfileModel().getUsername();
username = feedModel.getProfileModel().getUsername();
toolbar.setTitle(username);
toolbar.setSubtitle(postModels[0].getShortCode());
toolbar.setSubtitle(feedModel.getShortCode());
final RecyclerView recyclerView = findViewById(R.id.mainPosts);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new GridAutofitLayoutManager(this, Utils.convertDpToPx(130)));
recyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
// final ArrayList<PostModel> models = new ArrayList<>(feedModel.length - 1);
// for (final ViewerPostModel postModel : feedModel)
// models.add(new PostModel(postModel.getItemType(), postModel.getPostId(), postModel.getDisplayUrl(),
// postModel.getDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp(),
// postModel.getLike(), postModel.isSaved(), postModel.getLikes()));
final ArrayList<PostModel> models = new ArrayList<>(postModels.length - 1);
for (final ViewerPostModel postModel : postModels)
models.add(new PostModel(postModel.getItemType(), postModel.getPostId(), postModel.getDisplayUrl(),
postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp(),
postModel.getLike(), postModel.getBookmark(), postModel.getLikes()));
// postsAdapter = new PostsAdapter(models, v -> {
// postsAdapter = new PostsAdapter(v -> {
// final Object tag = v.getTag();
// if (tag instanceof PostModel) {
// final PostModel postModel = (PostModel) tag;
@ -80,8 +79,8 @@ public final class MultiDirectDialog extends BaseLanguageActivity {
// }
// return true;
// });
recyclerView.setAdapter(postsAdapter);
//
// recyclerView.setAdapter(postsAdapter);
}
@Override

View File

@ -327,8 +327,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
binding.mainLocPostCount.setVisibility(View.VISIBLE);
binding.locationFullName.setText(locationModel.getName());
CharSequence biography = locationModel.getBio();
binding.locationBiography.setCaptionIsExpandable(true);
binding.locationBiography.setCaptionIsExpanded(true);
// binding.locationBiography.setCaptionIsExpandable(true);
// binding.locationBiography.setCaptionIsExpanded(true);
if (TextUtils.isEmpty(biography)) {
binding.locationBiography.setVisibility(View.GONE);

View File

@ -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)));
// }
// }
// }

File diff suppressed because it is too large Load Diff

View File

@ -45,8 +45,11 @@ 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.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
@ -81,7 +84,6 @@ import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -622,8 +624,11 @@ public class StoryViewerFragment extends Fragment {
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO
? currentStory.getVideoUrl()
: currentStory.getStoryUrl();
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp()
+ DownloadUtils.getExtensionFromModel(storyUrl, currentStory));
final File saveFile = new File(
dir,
currentStory.getStoryMediaId()
+ "_" + currentStory.getTimestamp()
+ DownloadUtils.getFileExtensionFromUrl(storyUrl));
new DownloadAsync(context, storyUrl, saveFile, result -> {
final int toastRes = result != null && result.exists() ? R.string.downloader_complete
@ -687,8 +692,10 @@ public class StoryViewerFragment extends Fragment {
binding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final Uri uri = Uri.parse(url);
final MediaItem mediaItem = MediaItem.fromUri(uri);
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram"))
.createMediaSource(Uri.parse(url));
.createMediaSource(mediaItem);
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override
public void onLoadCompleted(final int windowIndex,
@ -732,7 +739,8 @@ public class StoryViewerFragment extends Fragment {
binding.progressView.setVisibility(View.GONE);
}
});
player.prepare(mediaSource);
player.setMediaSource(mediaSource);
player.prepare();
binding.playerView.setOnClickListener(v -> {
if (player != null) {
@ -751,12 +759,10 @@ public class StoryViewerFragment extends Fragment {
if (t == '@') {
final NavDirections action = HashTagFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action);
}
else if (t == '#') {
} else if (t == '#') {
final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(username.substring(1));
NavHostFragment.findNavController(this).navigate(action);
}
else {
} else {
final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(username.split(" \\(")[1].replace(")", ""));
NavHostFragment.findNavController(this).navigate(action);
}

View File

@ -1,18 +1,18 @@
package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
@ -20,47 +20,25 @@ import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapter;
import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.asyncs.FeedPostFetchService;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
@ -77,98 +55,101 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private StoriesService storiesService;
private boolean feedHasNextPage = false;
private String feedEndCursor = null;
private FeedViewModel feedViewModel;
// private FeedViewModel feedViewModel;
private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
private boolean shouldRefresh = true;
private boolean isPullToRefresh;
private FeedStoriesViewModel feedStoriesViewModel;
private StaggeredGridLayoutManager gridLayoutManager;
private boolean storiesFetching;
private final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
@Override
public void doBefore() {
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
}
@Override
public void onResult(final FeedModel[] result) {
if (result == null || result.length <= 0) {
binding.feedSwipeRefreshLayout.setRefreshing(false);
return;
}
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
for (final FeedModel feedModel : result) {
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
@Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
final Map<String, Object> extras = dataSource.getExtras();
if (extras == null) return;
final Uri thumbUri = (Uri) extras.get("uri_source");
if (thumbUri == null) return;
final Integer encodedWidth = (Integer) extras.get("encoded_width");
final Integer encodedHeight = (Integer) extras.get("encoded_height");
if (encodedWidth == null || encodedHeight == null) return;
final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
if (feedModel == null) return;
int requiredWidth = Utils.displayMetrics.widthPixels;
int resultingHeight = NumberUtils
.getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
if (feedModel
.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
// If its a video and the height is too large, need to reduce the height,
// so that entire video fits on screen
resultingHeight = RESIZED_VIDEO_HEIGHT;
requiredWidth = NumberUtils.getResultingWidth(RESIZED_VIDEO_HEIGHT,
resultingHeight,
requiredWidth);
}
feedModel.setImageWidth(requiredWidth);
feedModel.setImageHeight(resultingHeight);
success++;
updateAdapter();
}
@Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
failed++;
updateAdapter();
}
public void updateAdapter() {
if (failed + success != result.length) return;
List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
? new ArrayList<>()
: new ArrayList<>(currentFeedModelList);
final List<FeedModel> resultList = Arrays.asList(result);
if (isPullToRefresh) {
finalList = resultList;
isPullToRefresh = false;
} else {
finalList.addAll(resultList);
}
feedViewModel.getList().postValue(finalList);
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
binding.feedSwipeRefreshLayout.setRefreshing(false);
}
};
for (final FeedModel feedModel : result) {
final DataSource<Void> ds = Fresco.getImagePipeline()
.prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
}
}
};
// private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
// @Override
// public void doBefore() {
// binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
// }
//
// @Override
// public void onResult(final FeedModel[] result) {
// if (result == null || result.length <= 0) {
// binding.feedSwipeRefreshLayout.setRefreshing(false);
// return;
// }
// final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
// final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
// for (final FeedModel feedModel : result) {
// thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
// }
// final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
// int success = 0;
// int failed = 0;
//
// @Override
// protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
// final Map<String, Object> extras = dataSource.getExtras();
// if (extras == null) return;
// final Uri thumbUri = (Uri) extras.get("uri_source");
// if (thumbUri == null) return;
// final Integer encodedWidth = (Integer) extras.get("encoded_width");
// final Integer encodedHeight = (Integer) extras.get("encoded_height");
// if (encodedWidth == null || encodedHeight == null) return;
// final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
// if (feedModel == null) return;
// int requiredWidth = Utils.displayMetrics.widthPixels;
// int resultingHeight = NumberUtils
// .getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
// if (feedModel
// .getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
// // If its a video and the height is too large, need to reduce the height,
// // so that entire video fits on screen
// resultingHeight = RESIZED_VIDEO_HEIGHT;
// requiredWidth = NumberUtils.getResultingWidth(RESIZED_VIDEO_HEIGHT,
// resultingHeight,
// requiredWidth);
// }
// feedModel.setImageWidth(requiredWidth);
// feedModel.setImageHeight(resultingHeight);
// success++;
// updateAdapter();
// }
//
// @Override
// protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
// failed++;
// updateAdapter();
// }
//
// public void updateAdapter() {
// if (failed + success != result.length) return;
//
// }
// };
// for (final FeedModel feedModel : result) {
// final DataSource<Void> ds = Fresco.getImagePipeline()
// .prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
// ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
// }
// List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
// ? new ArrayList<>()
// : new ArrayList<>(currentFeedModelList);
// final List<FeedModel> resultList = Arrays.asList(result);
// if (isPullToRefresh) {
// finalList = resultList;
// isPullToRefresh = false;
// } else {
// finalList.addAll(resultList);
// }
// // feedViewModel.getList().postValue(finalList);
// final PostModel feedPostModel = result[result.length - 1];
// if (feedPostModel != null) {
// feedEndCursor = feedPostModel.getEndCursor();
// feedHasNextPage = feedPostModel.hasNextPage();
// feedPostModel.setPageCursor(false, null);
// }
// binding.feedSwipeRefreshLayout.setRefreshing(false);
// }
// };
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
if (isHashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(text);
@ -184,105 +165,105 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
NavHostFragment.findNavController(this).navigate(action);
};
private final View.OnClickListener postViewClickListener = v -> {
final Object tag = v.getTag();
if (!(tag instanceof FeedModel)) return;
final FeedModel feedModel = (FeedModel) tag;
if (v instanceof RamboTextView) {
if (feedModel.isMentionClicked()) feedModel.toggleCaption();
feedModel.setMentionClicked(false);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
feedModel.toggleCaption();
return;
}
final int id = v.getId();
switch (id) {
case R.id.btnComments:
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(this).navigate(commentsAction);
break;
case R.id.viewStoryPost:
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
if (feedModels == null || feedModels.size() == 0) return;
if (feedModels.get(0) == null) return;
final String postId = feedModels.get(0).getPostId();
final boolean isId = postId != null;
final String[] idsOrShortCodes = new String[feedModels.size()];
for (int i = 0; i < feedModels.size(); i++) {
idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
: feedModels.get(i).getShortCode();
}
final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
feedModel.getPosition(),
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
break;
case R.id.btnDownload:
ProfileModel profileModel = feedModel.getProfileModel();
final String username = profileModel != null ? profileModel.getUsername() : null;
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
final Context context = getContext();
if (context == null) return;
if (feedModel
.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_FEED,
Collections.singletonList(feedModel));
else {
final ArrayList<BasePostModel> postModels = new ArrayList<>();
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
postModels.clear();
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
for (final ViewerPostModel sliderItem : sliderItems) {
if (sliderItem != null) {
if (!breakWhenFoundSelected) postModels.add(sliderItem);
else if (sliderItem.isSelected()) {
postModels.add(sliderItem);
break;
}
}
}
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
if (breakWhenFoundSelected && postModels.size() == 0) {
postModels.add(sliderItems[0]);
}
if (postModels.size() > 0) {
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_FEED,
postModels);
}
};
new AlertDialog.Builder(context)
.setTitle(R.string.post_viewer_download_dialog_title).setPositiveButton(
R.string.post_viewer_download_current,
clickListener1)
.setNegativeButton(R.string.post_viewer_download_album, clickListener1)
.show();
}
break;
case R.id.ivProfilePic:
profileModel = feedModel.getProfileModel();
if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
break;
}
// gridLayoutManager.setSpanCount(1);
// final Object tag = v.getTag();
// if (!(tag instanceof FeedModel)) return;
//
// final FeedModel feedModel = (FeedModel) tag;
// if (v instanceof RamboTextView) {
// if (feedModel.isMentionClicked()) feedModel.toggleCaption();
// feedModel.setMentionClicked(false);
// if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
// feedModel.toggleCaption();
// return;
// }
//
// final int id = v.getId();
// switch (id) {
// case R.id.btnComments:
// final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
// feedModel.getShortCode(),
// feedModel.getPostId(),
// feedModel.getProfileModel().getId()
// );
// NavHostFragment.findNavController(this).navigate(commentsAction);
// break;
// // case R.id.viewStoryPost:
// // final List<FeedModel> feedModels = feedViewModel.getList().getValue();
// // if (feedModels == null || feedModels.size() == 0) return;
// // if (feedModels.get(0) == null) return;
// // final String postId = feedModels.get(0).getPostId();
// // final boolean isId = postId != null;
// // final String[] idsOrShortCodes = new String[feedModels.size()];
// // for (int i = 0; i < feedModels.size(); i++) {
// // idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
// // : feedModels.get(i).getShortCode();
// // }
// // final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
// // feedModel.getPosition(),
// // idsOrShortCodes,
// // isId);
// // NavHostFragment.findNavController(this).navigate(action);
// // break;
// case R.id.btnDownload:
// ProfileModel profileModel = feedModel.getProfileModel();
// final String username = profileModel != null ? profileModel.getUsername() : null;
//
// final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
//
// final Context context = getContext();
// if (context == null) return;
// if (feedModel
// .getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
// DownloadUtils.batchDownload(context,
// username,
// DownloadMethod.DOWNLOAD_FEED,
// Collections.singletonList(feedModel));
// else {
// final ArrayList<BasePostModel> postModels = new ArrayList<>();
// final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
// postModels.clear();
//
// final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
//
// for (final ViewerPostModel sliderItem : sliderItems) {
// if (sliderItem != null) {
// if (!breakWhenFoundSelected) postModels.add(sliderItem);
// else if (sliderItem.isSelected()) {
// postModels.add(sliderItem);
// break;
// }
// }
// }
//
// // shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
// if (breakWhenFoundSelected && postModels.size() == 0) {
// postModels.add(sliderItems[0]);
// }
// if (postModels.size() > 0) {
// DownloadUtils.batchDownload(context,
// username,
// DownloadMethod.DOWNLOAD_FEED,
// postModels);
// }
// };
//
// new AlertDialog.Builder(context)
// .setTitle(R.string.post_viewer_download_dialog_title)
// .setPositiveButton(R.string.post_viewer_download_current, clickListener1)
// .setNegativeButton(R.string.post_viewer_download_album, clickListener1)
// .show();
// }
// break;
//
// case R.id.ivProfilePic:
// profileModel = feedModel.getProfileModel();
// if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
// break;
// default:
// break;
// }
};
private FeedStoriesViewModel feedStoriesViewModel;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -290,6 +271,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance();
// feedService = FeedService.getInstance();
// final TransitionInflater inflater = TransitionInflater.from(getContext());
// setExitTransition(inflater.inflateTransition(android.R.transition.move));
setHasOptionsMenu(true);
}
@Override
@ -312,6 +296,21 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
setupFeedStories();
setupFeed();
shouldRefresh = false;
// showPostsLayoutPreferences();
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.feed_menu, menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
@ -335,37 +334,60 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
public void onRefresh() {
isPullToRefresh = true;
feedEndCursor = null;
fetchFeed();
binding.feedRecyclerView.refresh();
fetchStories();
}
private void setupFeed() {
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
// feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
final Context context = getContext();
if (context == null) return;
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
binding.feedRecyclerView.setLayoutManager(layoutManager);
binding.feedRecyclerView.setHasFixedSize(true);
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener);
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
binding.feedRecyclerView.setAdapter(feedAdapter);
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (feedHasNextPage) {
fetchFeed();
}
});
if (shouldAutoPlay) {
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
}
binding.feedRecyclerView.addOnScrollListener(lazyLoader);
fetchFeed();
// final PostFetcher.PostFetchService feedPostsFetchService = new PostFetcher.PostFetchService() {
// @Override
// public void fetch(final int page, final FetchListener<List<FeedModel>> fetchListener) {
// new FeedFetcher(feedEndCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
// };
binding.feedRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new FeedPostFetchService())
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setOnPostClickListener((feedModel, profilePicView, mainPostImage) -> {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
})
.init();
binding.feedSwipeRefreshLayout.setRefreshing(true);
// final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
// binding.feedRecyclerView.setLayoutManager(gridLayoutManager);
// feedAdapter = new FeedAdapterV2(spanCount[0], postViewClickListener, mentionClickListener, feedModel -> {
// final ChangeBounds transition = new ChangeBounds();
// transition.setDuration(200);
// TransitionManager.beginDelayedTransition(binding.feedRecyclerView, transition);
// spanCount[0] = spanCount[0] - 1;
// if (spanCount[0] == 0) {
// spanCount[0] = 3;
// }
// gridLayoutManager.setSpanCount(spanCount[0]);
// });
// feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
// binding.feedRecyclerView.setAdapter(feedAdapter);
// feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
// if (shouldAutoPlay) {
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
// }
// fetchFeed();
}
private void fetchFeed() {
binding.feedSwipeRefreshLayout.setRefreshing(true);
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
private void updateSwipeRefreshState() {
binding.feedSwipeRefreshLayout.setRefreshing(binding.feedRecyclerView.isFetching() || storiesFetching);
}
private void setupFeedStories() {
@ -383,16 +405,28 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
private void fetchStories() {
storiesFetching = true;
updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result);
storiesFetching = false;
updateSwipeRefreshState();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "failed", t);
storiesFetching = false;
updateSwipeRefreshState();
}
});
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(preferences -> new Handler()
.postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
}

View File

@ -3,6 +3,7 @@ package awais.instagrabber.fragments.main;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@ -39,6 +40,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.imagepipeline.image.ImageInfo;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
@ -170,8 +174,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
return;
}
else {
} else {
binding.privatePage.setVisibility(View.GONE);
}
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
@ -352,6 +355,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onDestroy() {
super.onDestroy();
postsAdapter = null;
if (usernameSettingHandler != null) {
usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
}
@ -446,31 +450,31 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) {
storiesService.getUserStory(profileId,
profileModel.getUsername(),
false,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainProfileImage.setStoriesBorder();
hasStories = true;
}
}
profileModel.getUsername(),
false,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainProfileImage.setStoriesBorder();
hasStories = true;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
new HighlightsFetcher(profileId,
result -> {
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
result -> {
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
@ -529,7 +533,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} else {
binding.favCb.setVisibility(View.GONE);
}
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
final ControllerListener<ImageInfo> listener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(final String id, final ImageInfo imageInfo, final Animatable animatable) {
startPostponedEnterTransition();
}
};
binding.mainProfileImage.setImageURI(profileModel.getHdProfilePic());
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
@ -562,8 +572,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
: profileModel.getName());
CharSequence biography = profileModel.getBiography();
binding.mainBiography.setCaptionIsExpandable(true);
binding.mainBiography.setCaptionIsExpanded(true);
// binding.mainBiography.setCaptionIsExpandable(true);
// binding.mainBiography.setCaptionIsExpanded(true);
if (TextUtils.hasMentions(biography)) {
biography = TextUtils.getMentionText(biography);
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);

View File

@ -2,5 +2,8 @@ package awais.instagrabber.interfaces;
public interface FetchListener<T> {
void onResult(T result);
default void doBefore() { }
default void doBefore() {}
default void onFailure(Throwable t) {}
}

View File

@ -20,14 +20,15 @@ public abstract class BasePostModel implements Serializable, Selectable {
protected boolean isDownloaded;
protected long timestamp;
protected int position;
boolean liked, bookmarked;
boolean liked;
boolean saved;
public boolean getLike() {
return liked;
}
public boolean getBookmark() {
return bookmarked;
public boolean isSaved() {
return saved;
}
public MediaItemType getItemType() {

View File

@ -1,47 +1,174 @@
package awais.instagrabber.models;
import java.util.List;
import awais.instagrabber.models.enums.MediaItemType;
public final class FeedModel extends PostModel {
private final ProfileModel profileModel;
private final long commentsCount;
private long likesCount;
private final long viewCount;
private boolean captionExpanded = false;
private boolean mentionClicked = false;
private ViewerPostModel[] sliderItems;
private List<PostChild> sliderItems;
private int imageWidth;
private int imageHeight;
private String locationName;
private String locationId;
public FeedModel(final ProfileModel profileModel,
final MediaItemType itemType,
final long viewCount,
final String postId,
final String displayUrl,
final String thumbnailUrl,
final String shortCode,
final String postCaption,
final long commentsCount,
final long timestamp,
final boolean liked,
final boolean bookmarked,
final long likes,
final String locationName,
final String locationId) {
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked, likes);
public static class Builder {
private ProfileModel profileModel;
private MediaItemType itemType;
private long viewCount;
private String postId;
private String displayUrl;
private String thumbnailUrl;
private String shortCode;
private String postCaption;
private long commentsCount;
private long timestamp;
private boolean liked;
private boolean bookmarked;
private long likesCount;
private String locationName;
private String locationId;
private List<PostChild> sliderItems;
private int imageWidth;
private int imageHeight;
public Builder setProfileModel(final ProfileModel profileModel) {
this.profileModel = profileModel;
return this;
}
public Builder setItemType(final MediaItemType itemType) {
this.itemType = itemType;
return this;
}
public Builder setViewCount(final long viewCount) {
this.viewCount = viewCount;
return this;
}
public Builder setPostId(final String postId) {
this.postId = postId;
return this;
}
public Builder setDisplayUrl(final String displayUrl) {
this.displayUrl = displayUrl;
return this;
}
public Builder setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
return this;
}
public Builder setShortCode(final String shortCode) {
this.shortCode = shortCode;
return this;
}
public Builder setPostCaption(final String postCaption) {
this.postCaption = postCaption;
return this;
}
public Builder setCommentsCount(final long commentsCount) {
this.commentsCount = commentsCount;
return this;
}
public Builder setTimestamp(final long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder setLiked(final boolean liked) {
this.liked = liked;
return this;
}
public Builder setBookmarked(final boolean bookmarked) {
this.bookmarked = bookmarked;
return this;
}
public Builder setLikesCount(final long likesCount) {
this.likesCount = likesCount;
return this;
}
public Builder setLocationName(final String locationName) {
this.locationName = locationName;
return this;
}
public Builder setLocationId(final String locationId) {
this.locationId = locationId;
return this;
}
public Builder setSliderItems(final List<PostChild> sliderItems) {
this.sliderItems = sliderItems;
return this;
}
public Builder setImageHeight(final int imageHeight) {
this.imageHeight = imageHeight;
return this;
}
public Builder setImageWidth(final int imageWidth) {
this.imageWidth = imageWidth;
return this;
}
public FeedModel build() {
return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, commentsCount,
timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth);
}
}
private FeedModel(final ProfileModel profileModel,
final MediaItemType itemType,
final long viewCount,
final String postId,
final String displayUrl,
final String thumbnailUrl,
final String shortCode,
final String postCaption,
final long commentsCount,
final long timestamp,
final boolean liked,
final boolean bookmarked,
final long likesCount,
final String locationName,
final String locationId,
final List<PostChild> sliderItems,
final int imageHeight,
final int imageWidth) {
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked);
this.profileModel = profileModel;
this.commentsCount = commentsCount;
this.likesCount = likesCount;
this.viewCount = viewCount;
this.locationName = locationName;
this.locationId = locationId;
this.sliderItems = sliderItems;
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
}
public ProfileModel getProfileModel() {
return profileModel;
}
public ViewerPostModel[] getSliderItems() {
public List<PostChild> getSliderItems() {
return sliderItems;
}
@ -53,6 +180,10 @@ public final class FeedModel extends PostModel {
return commentsCount;
}
public long getLikesCount() {
return likesCount;
}
public boolean isCaptionExpanded() {
return captionExpanded;
}
@ -61,14 +192,14 @@ public final class FeedModel extends PostModel {
return !mentionClicked;
}
public void setMentionClicked(final boolean mentionClicked) {
this.mentionClicked = mentionClicked;
}
public void setSliderItems(final ViewerPostModel[] sliderItems) {
this.sliderItems = sliderItems;
setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
}
// public void setMentionClicked(final boolean mentionClicked) {
// this.mentionClicked = mentionClicked;
// }
//
// public void setSliderItems(final ViewerPostModel[] sliderItems) {
// this.sliderItems = sliderItems;
// setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
// }
public void toggleCaption() {
captionExpanded = !captionExpanded;
@ -78,13 +209,13 @@ public final class FeedModel extends PostModel {
return imageWidth;
}
public void setImageWidth(final int imageWidth) {
this.imageWidth = imageWidth;
}
// public void setImageWidth(final int imageWidth) {
// this.imageWidth = imageWidth;
// }
public void setImageHeight(final int imageHeight) {
this.imageHeight = imageHeight;
}
// public void setImageHeight(final int imageHeight) {
// this.imageHeight = imageHeight;
// }
public int getImageHeight() {
return imageHeight;
@ -94,15 +225,45 @@ public final class FeedModel extends PostModel {
return locationName;
}
public void setLocationName(final String locationName) {
this.locationName = locationName;
}
// public void setLocationName(final String locationName) {
// this.locationName = locationName;
// }
public String getLocationId() {
return locationId;
}
public void setLocationId(final String locationId) {
this.locationId = locationId;
// public void setLocationId(final String locationId) {
// this.locationId = locationId;
// }
public void setLiked(final boolean liked) {
this.liked = liked;
}
public void setLikesCount(final long count) {
this.likesCount = count;
}
public void setSaved(final boolean saved) {
this.saved = saved;
}
@Override
public String toString() {
return "FeedModel{" +
"type=" + itemType +
", displayUrl=" + displayUrl +
", thumbnailUrl=" + thumbnailUrl +
", commentsCount=" + commentsCount +
", viewCount=" + viewCount +
", captionExpanded=" + captionExpanded +
", mentionClicked=" + mentionClicked +
// ", sliderItems=" + sliderItems +
", imageWidth=" + imageWidth +
", imageHeight=" + imageHeight +
", locationName='" + locationName + '\'' +
", locationId='" + locationId + '\'' +
'}';
}
}

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 +
'}';
}
}

View File

@ -13,9 +13,15 @@ public class PostModel extends BasePostModel {
this.thumbnailUrl = null;
}
public PostModel(final MediaItemType itemType, final String postId, final String displayUrl, final String thumbnailUrl,
final String shortCode, final CharSequence postCaption, long timestamp, boolean liked, boolean bookmarked,
long likes) {
public PostModel(final MediaItemType itemType,
final String postId,
final String displayUrl,
final String thumbnailUrl,
final String shortCode,
final CharSequence postCaption,
long timestamp,
boolean liked,
boolean bookmarked) {
this.itemType = itemType;
this.postId = postId;
this.displayUrl = displayUrl;
@ -24,7 +30,7 @@ public class PostModel extends BasePostModel {
this.postCaption = postCaption;
this.timestamp = timestamp;
this.liked = liked;
this.bookmarked = bookmarked;
this.saved = bookmarked;
}
public String getThumbnailUrl() {

View File

@ -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
}
}

View File

@ -7,13 +7,124 @@ public final class ViewerPostModel extends BasePostModel {
protected final String locationName;
protected final String location;
protected final long videoViews;
protected String sliderDisplayUrl;
protected long commentsCount, likes;
private final String thumbnailUrl;
protected long commentsCount;
protected long likes;
private int imageWidth;
private int imageHeight;
private boolean isCurrentSlide = false;
public static class Builder {
private MediaItemType itemType;
private String postId;
private String displayUrl;
private String shortCode;
private String postCaption;
private ProfileModel profileModel;
private long videoViews;
private long timestamp;
private boolean liked;
private boolean bookmarked;
private long likes;
private String locationName;
private String location;
private String thumbnailUrl;
private int imageWidth;
private int imageHeight;
public Builder setItemType(final MediaItemType itemType) {
this.itemType = itemType;
return this;
}
public Builder setPostId(final String postId) {
this.postId = postId;
return this;
}
public Builder setDisplayUrl(final String displayUrl) {
this.displayUrl = displayUrl;
return this;
}
public Builder setShortCode(final String shortCode) {
this.shortCode = shortCode;
return this;
}
// public Builder setPostCaption(final String postCaption) {
// this.postCaption = postCaption;
// return this;
// }
// public Builder setProfileModel(final ProfileModel profileModel) {
// this.profileModel = profileModel;
// return this;
// }
public Builder setVideoViews(final long videoViews) {
this.videoViews = videoViews;
return this;
}
// public Builder setTimestamp(final long timestamp) {
// this.timestamp = timestamp;
// return this;
// }
//
// public Builder setLiked(final boolean liked) {
// this.liked = liked;
// return this;
// }
//
// public Builder setBookmarked(final boolean bookmarked) {
// this.bookmarked = bookmarked;
// return this;
// }
//
// public Builder setLikes(final long likes) {
// this.likes = likes;
// return this;
// }
// public Builder setLocationName(final String locationName) {
// this.locationName = locationName;
// return this;
// }
//
// public Builder setLocation(final String location) {
// this.location = location;
// return this;
// }
public Builder setImageHeight(final int imageHeight) {
this.imageHeight = imageHeight;
return this;
}
public Builder setImageWidth(final int imageWidth) {
this.imageWidth = imageWidth;
return this;
}
public Builder setThumbnailUrl(final String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
return this;
}
public ViewerPostModel build() {
return new ViewerPostModel(itemType, postId, displayUrl, thumbnailUrl, imageHeight, imageWidth, shortCode, postCaption, profileModel,
videoViews, timestamp, liked, bookmarked, likes, locationName, location);
}
}
public ViewerPostModel(final MediaItemType itemType,
final String postId,
final String displayUrl,
final String thumbnailUrl,
final int imageHeight,
final int imageWidth,
final String shortCode,
final String postCaption,
final ProfileModel profileModel,
@ -27,6 +138,9 @@ public final class ViewerPostModel extends BasePostModel {
this.itemType = itemType;
this.postId = postId;
this.displayUrl = displayUrl;
this.thumbnailUrl = thumbnailUrl;
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
this.postCaption = postCaption;
this.profileModel = profileModel;
this.shortCode = shortCode;
@ -34,23 +148,15 @@ public final class ViewerPostModel extends BasePostModel {
this.timestamp = timestamp;
this.liked = liked;
this.likes = likes;
this.bookmarked = bookmarked;
this.saved = bookmarked;
this.locationName = locationName;
this.location = location;
}
public static ViewerPostModel getDefaultModel(final int postId, final String shortCode) {
return new ViewerPostModel(null, String.valueOf(postId), null, "", null, null, -1, -1, false, false, -1, null, null);
}
public long getCommentsCount() {
return commentsCount;
}
public String getSliderDisplayUrl() {
return sliderDisplayUrl;
}
public ProfileModel getProfileModel() {
return profileModel;
}
@ -72,29 +178,42 @@ public final class ViewerPostModel extends BasePostModel {
}
// setManualLike means user liked from InstaGrabber
public boolean setManualLike(final boolean like) {
public void setManualLike(final boolean like) {
liked = like;
likes = (like) ? (likes + 1) : (likes - 1);
return liked;
}
public void setBookmarked(final boolean bookmarked) {
this.bookmarked = bookmarked;
}
public void setSliderDisplayUrl(final String sliderDisplayUrl) {
this.sliderDisplayUrl = sliderDisplayUrl;
}
public void setCommentsCount(final long commentsCount) {
this.commentsCount = commentsCount;
}
public void setCurrentSlide(final boolean currentSlide) {
this.isCurrentSlide = currentSlide;
}
public boolean isCurrentSlide() {
return isCurrentSlide;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public int getImageHeight() {
return imageHeight;
}
public int getImageWidth() {
return imageWidth;
}
@Override
public String toString() {
return "ViewerPostModel{" +
"type=" + itemType +
", displayUrl=" + displayUrl +
", thumbnailUrl=" + thumbnailUrl +
", locationName='" + locationName + '\'' +
", location='" + location + '\'' +
", videoViews=" + videoViews +
", thumbnailUrl='" + thumbnailUrl + '\'' +
", commentsCount=" + commentsCount +
", likes=" + likes +
", imageWidth=" + imageWidth +
", imageHeight=" + imageHeight +
", isCurrentSlide=" + isCurrentSlide +
'}';
}
}

View File

@ -1,10 +1,12 @@
package awais.instagrabber.models;
import java.util.List;
public class ViewerPostModelWrapper {
private int position;
private ViewerPostModel[] viewerPostModels;
private List<PostChild> viewerPostModels;
public ViewerPostModelWrapper(final int position, final ViewerPostModel[] viewerPostModels) {
public ViewerPostModelWrapper(final int position, final List<PostChild> viewerPostModels) {
this.position = position;
this.viewerPostModels = viewerPostModels;
}
@ -13,11 +15,11 @@ public class ViewerPostModelWrapper {
return position;
}
public ViewerPostModel[] getViewerPostModels() {
public List<PostChild> getViewerPostModels() {
return viewerPostModels;
}
public void setViewerPostModels(final ViewerPostModel[] viewerPostModels) {
public void setViewerPostModels(final List<PostChild> viewerPostModels) {
this.viewerPostModels = viewerPostModels;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -18,7 +18,6 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.asyncs.GetActivityAsyncTask;
import awais.instagrabber.asyncs.GetActivityAsyncTask.NotificationCounts;
import awais.instagrabber.asyncs.GetActivityAsyncTask.OnTaskCompleteListener;
import awais.instagrabber.utils.Constants;
@ -37,8 +36,8 @@ public class ActivityCheckerService extends Service {
private final IBinder binder = new LocalBinder();
private final Runnable runnable = () -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final GetActivityAsyncTask activityAsyncTask = new GetActivityAsyncTask(onTaskCompleteListener);
activityAsyncTask.execute(cookie);
// final GetActivityAsyncTask activityAsyncTask = new GetActivityAsyncTask(onTaskCompleteListener);
// activityAsyncTask.execute(cookie);
};
public class LocalBinder extends Binder {

View File

@ -51,6 +51,10 @@ public final class Constants {
public static final String EXTRAS_END_CURSOR = "endCursor";
public static final String FEED = "feed";
public static final String FEED_ORDER = "feedOrder";
// Notification ids
public static final int ACTIVITY_NOTIFICATION_ID = 10;
// spoof
public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 " +
@ -78,10 +82,10 @@ public final class Constants {
public static final String ACTIVITY_CHANNEL_NAME = "Activity";
public static final String DOWNLOAD_CHANNEL_NAME = "Downloads";
public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif";
public static final int ACTIVITY_NOTIFICATION_ID = 1800000000;
public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
public static final String SHARED_PREFERENCES_NAME = "settings";
public static final String PREF_POSTS_LAYOUT = "posts_layout";
}

View File

@ -8,16 +8,29 @@ import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.Pair;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
import com.google.gson.Gson;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
@ -25,17 +38,29 @@ import awais.instagrabber.R;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.workers.DownloadWorker;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class DownloadUtils {
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
private static int lastNotificationId = UUID.randomUUID().hashCode();
public synchronized static int getNextDownloadNotificationId(@NonNull final Context context) {
lastNotificationId = lastNotificationId + 1;
if (lastNotificationId == Integer.MAX_VALUE) {
lastNotificationId = UUID.randomUUID().hashCode();
}
return lastNotificationId;
}
public static void batchDownload(@NonNull final Context context,
@Nullable String username,
@ -57,20 +82,8 @@ public final class DownloadUtils {
@Nullable final String username,
final DownloadMethod method,
final List<? extends BasePostModel> itemsToDownload) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
}
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username))
dir = new File(dir, username);
if (!dir.exists() && !dir.mkdirs()) {
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
return;
}
final File dir = getDownloadDir(context, username);
if (dir == null) return;
boolean checkEachPost = false;
switch (method) {
case DOWNLOAD_SAVED:
@ -86,7 +99,11 @@ public final class DownloadUtils {
final BasePostModel selectedItem = itemsToDownload.get(i);
if (!checkEachPost) {
final boolean isSlider = itemsToDownloadSize > 1;
final File saveFile = getDownloadSaveFile(dir, selectedItem, isSlider ? "_slide_" + (i + 1) : "");
final File saveFile = getDownloadSaveFile(dir,
selectedItem.getPostId(),
isSlider ? "_slide_" + (i + 1) : "",
selectedItem.getDisplayUrl()
);
new DownloadAsync(context,
selectedItem.getDisplayUrl(),
saveFile,
@ -95,25 +112,64 @@ public final class DownloadUtils {
} else {
final File finalDir = dir;
new PostFetcher(selectedItem.getShortCode(), result -> {
if (result != null) {
final int resultsSize = result.length;
final boolean multiResult = resultsSize > 1;
for (int j = 0; j < resultsSize; j++) {
final BasePostModel model = result[j];
final File saveFile = getDownloadSaveFile(finalDir, model, multiResult ? "_slide_" + (j + 1) : "");
if (result == null) return;
final boolean isSlider = result.getItemType() == MediaItemType.MEDIA_TYPE_SLIDER;
if (isSlider) {
for (int j = 0; j < result.getSliderItems().size(); j++) {
final PostChild model = result.getSliderItems().get(j);
final File saveFile = getDownloadSaveFile(
finalDir,
model.getPostId(),
"_slide_" + (j + 1),
model.getDisplayUrl()
);
new DownloadAsync(context,
model.getDisplayUrl(),
saveFile,
file -> model.setDownloaded(true))
file -> {}/*model.setDownloaded(true)*/)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
} else {
final File saveFile = getDownloadSaveFile(
finalDir,
result.getPostId(),
result.getDisplayUrl()
);
new DownloadAsync(context,
result.getDisplayUrl(),
saveFile,
file -> result.setDownloaded(true))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
public static void dmDownload(@NonNull final Context context, @Nullable final String username, final DownloadMethod method,
@Nullable
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
}
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
final String finaleUsername = username.startsWith("@") ? username : "@" + username;
dir = new File(dir, finaleUsername);
}
if (!dir.exists() && !dir.mkdirs()) {
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
return null;
}
return dir;
}
public static void dmDownload(@NonNull final Context context,
@Nullable final String username,
final DownloadMethod method,
final DirectItemModel.DirectItemMediaModel itemsToDownload) {
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);
@ -125,8 +181,10 @@ public final class DownloadUtils {
ActivityCompat.requestPermissions((Activity) context, PERMS, 8020);
}
private static void dmDownloadImpl(@NonNull final Context context, @Nullable final String username,
final DownloadMethod method, final DirectItemModel.DirectItemMediaModel selectedItem) {
private static void dmDownloadImpl(@NonNull final Context context,
@Nullable final String username,
final DownloadMethod method,
final DirectItemModel.DirectItemMediaModel selectedItem) {
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
@ -140,46 +198,83 @@ public final class DownloadUtils {
if (dir.exists() || dir.mkdirs()) {
new DownloadAsync(context,
selectedItem.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO ? selectedItem.getVideoUrl() : selectedItem.getThumbUrl(),
getDownloadSaveFileDm(dir, selectedItem, ""),
null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
getDownloadSaveFileDm(dir, selectedItem),
null)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
}
@NonNull
private static File getDownloadSaveFile(final File finalDir, @NonNull final BasePostModel model, final String sliderPrefix) {
final String displayUrl = model.getDisplayUrl();
return new File(finalDir, model.getPostId() + '_' + model.getPosition() + sliderPrefix +
getExtensionFromModel(displayUrl, model));
private static File getDownloadSaveFile(final File finalDir,
final String postId,
final String displayUrl) {
return getDownloadSaveFile(finalDir, postId, "", displayUrl);
}
private static File getDownloadChildSaveFile(final File downloadDir,
final String postId,
final int childPosition,
final String url) {
final String sliderPostfix = "_slide_" + childPosition;
return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url);
}
@NonNull
private static File getDownloadSaveFile(final File finalDir,
final String postId,
final String sliderPostfix,
final String displayUrl) {
final String fileName = postId + sliderPostfix + "." + getFileExtensionFromUrl(displayUrl);
return new File(finalDir, fileName);
}
@NonNull
private static File getDownloadSaveFileDm(final File finalDir,
@NonNull final DirectItemModel.DirectItemMediaModel model,
final String sliderPrefix) {
final String displayUrl = model.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO ? model.getVideoUrl() : model.getThumbUrl();
return new File(finalDir, model.getId() + sliderPrefix +
getExtensionFromModel(displayUrl, model));
@NonNull final DirectItemModel.DirectItemMediaModel model) {
final boolean isVideo = model.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO;
final String displayUrl = isVideo ? model.getVideoUrl() : model.getThumbUrl();
return new File(finalDir, model.getId() + getFileExtensionFromUrl(displayUrl));
}
@NonNull
public static String getExtensionFromModel(@NonNull final String url, final Object model) {
final String extension;
final int index = url.indexOf('?');
/**
* Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)})
* <p>
* Returns the file extension or an empty string if there is no
* extension. This method is a convenience method for obtaining the
* extension of a url and has undefined results for other Strings.
*
* @param url URL
* @return The file extension of the given url.
*/
public static String getFileExtensionFromUrl(String url) {
if (!TextUtils.isEmpty(url)) {
int fragment = url.lastIndexOf('#');
if (fragment > 0) {
url = url.substring(0, fragment);
}
if (index != -1) extension = url.substring(index - 4, index);
else {
final boolean isVideo;
if (model instanceof StoryModel)
isVideo = ((StoryModel) model).getItemType() == MediaItemType.MEDIA_TYPE_VIDEO;
else if (model instanceof BasePostModel)
isVideo = ((BasePostModel) model).getItemType() == MediaItemType.MEDIA_TYPE_VIDEO;
else
isVideo = false;
extension = isVideo || url.contains(".mp4") ? ".mp4" : ".jpg";
int query = url.lastIndexOf('?');
if (query > 0) {
url = url.substring(0, query);
}
int filenamePos = url.lastIndexOf('/');
String filename =
0 <= filenamePos ? url.substring(filenamePos + 1) : url;
// if the filename contains special characters, we don't
// consider it valid for our matching purposes:
if (!filename.isEmpty() &&
Pattern.matches("[a-zA-Z_0-9.\\-()%]+", filename)) {
int dotPos = filename.lastIndexOf('.');
if (0 <= dotPos) {
return filename.substring(dotPos + 1);
}
}
}
return extension;
return "";
}
public static void checkExistence(final File downloadDir, final File customDir, final boolean isSlider,
@ -198,7 +293,7 @@ public final class DownloadUtils {
final String fileWithoutPrefix = fileName + '0' + extension;
exists = new File(downloadDir, fileWithoutPrefix).exists();
if (!exists) {
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\" + extension + ")";
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\\\" + extension + ")";
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name);
File[] files = downloadDir.listFiles(filenameFilter);
@ -217,4 +312,66 @@ public final class DownloadUtils {
model.setDownloaded(exists);
}
public static void download(@NonNull final Context context,
@NonNull final FeedModel feedModel) {
download(context, feedModel, -1);
}
public static void download(@NonNull final Context context,
@NonNull final FeedModel feedModel,
final int position) {
final File downloadDir = getDownloadDir(context, "@" + feedModel.getProfileModel().getUsername());
if (downloadDir == null) return;
switch (feedModel.getItemType()) {
case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: {
final String url = feedModel.getDisplayUrl();
final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url);
download(context, url, file.getAbsolutePath());
break;
}
case MEDIA_TYPE_SLIDER:
final List<PostChild> sliderItems = feedModel.getSliderItems();
final Map<String, String> map = new HashMap<>();
for (int i = 0; i < sliderItems.size(); i++) {
final PostChild child = sliderItems.get(i);
final String url = child.getDisplayUrl();
final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url);
map.put(url, file.getAbsolutePath());
}
download(context, map);
break;
default:
}
}
private static void download(final Context context,
final String url,
final String filePath) {
if (context == null || url == null || filePath == null) return;
download(context, Collections.singletonMap(url, filePath));
}
private static void download(final Context context, final Map<String, String> urlFilePathMap) {
final Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
final DownloadWorker.DownloadRequest request = DownloadWorker.DownloadRequest.builder()
.setUrlToFilePathMap(urlFilePathMap)
.build();
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
.setInputData(
new Data.Builder()
.putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON,
new Gson().toJson(request))
.build()
)
.setConstraints(constraints)
.addTag("download")
.build();
WorkManager.getInstance(context)
.enqueue(downloadWorkRequest);
}
}

View File

@ -131,6 +131,7 @@ public class NavigationExtensions {
}
final NavHostFragment navHostFragment = NavHostFragment.create(navGraphId);
fragmentManager.beginTransaction()
.setReorderingAllowed(true)
.add(containerId, navHostFragment, fragmentTag)
.commitNow();
return navHostFragment;

View File

@ -65,4 +65,8 @@ public final class NumberUtils {
}
return new Pair<>(tempWidth, tempHeight);
}
public static float roundFloat2Decimals(final float value) {
return ((int) ((value + (value >= 0 ? 1 : -1) * 0.005f) * 100)) / 100f;
}
}

View File

@ -30,6 +30,7 @@ import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.PREF_DARK_THEME;
import static awais.instagrabber.utils.Constants.PREF_LIGHT_THEME;
import static awais.instagrabber.utils.Constants.PREF_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
import static awais.instagrabber.utils.Constants.SKIPPED_VERSION;
@ -113,7 +114,7 @@ public final class SettingsHelper {
@StringDef(
{APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT,
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME})
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,

View File

@ -7,6 +7,8 @@ import android.text.style.URLSpan;
import androidx.annotation.NonNull;
import java.util.Locale;
import awais.instagrabber.customviews.CommentMentionClickSpan;
public final class TextUtils {
@ -109,4 +111,22 @@ public final class TextUtils {
}
return "null".contentEquals(charSequence) || "".contentEquals(charSequence) || charSequence.length() < 1;
}
public static String millisToTimeString(final long millis) {
return millisToTimeString(millis, false);
}
public static String millisToTimeString(final long millis, final boolean includeHoursAlways) {
final int sec = (int) (millis / 1000) % 60;
int min = (int) (millis / (1000 * 60));
if (min >= 60) {
min = (int) ((millis / (1000 * 60)) % 60);
final int hr = (int) ((millis / (1000 * 60 * 60)) % 24);
return String.format(Locale.ENGLISH, "%02d:%02d:%02d", hr, min, sec);
}
if (includeHoursAlways) {
return String.format(Locale.ENGLISH, "%02d:%02d:%02d", 0, min, sec);
}
return String.format(Locale.ENGLISH, "%02d:%02d", min, sec);
}
}

View File

@ -47,6 +47,7 @@ public final class Utils {
public static DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
public static SimpleDateFormat datetimeParser;
public static SimpleCache simpleCache;
private static int statusBarHeight;
public static int convertDpToPx(final float dp) {
if (displayMetrics == null)
@ -146,4 +147,15 @@ public final class Utils {
}
return null;
}
public static int getStatusBarHeight(final Context context) {
if (statusBarHeight > 0) {
return statusBarHeight;
}
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
}

View File

@ -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;
}
}

View File

@ -1,5 +1,6 @@
package awais.instagrabber.webservices;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
@ -8,6 +9,11 @@ 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.HashMap;
import java.util.List;
@ -30,6 +36,7 @@ import retrofit2.Retrofit;
public class StoriesService extends BaseService {
private static final String TAG = "StoriesService";
private static final boolean loadFromMock = false;
private final StoriesRepository repository;
@ -50,6 +57,30 @@ public class StoriesService extends BaseService {
}
public void getFeedStories(final ServiceCallback<List<FeedStoryModel>> callback) {
if (loadFromMock) {
final Handler handler = new Handler();
handler.postDelayed(() -> {
final ClassLoader classLoader = getClass().getClassLoader();
if (classLoader == null) {
Log.e(TAG, "getFeedStories: classLoader is null!");
return;
}
try (InputStream resourceAsStream = classLoader.getResourceAsStream("stories_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);
}
parseStoriesBody(out.toString(), callback);
} catch (IOException e) {
Log.e(TAG, "getFeedStories: ", e);
}
}, 1000);
return;
}
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "b7b84d884400bc5aa7cfe12ae843a091");
queryMap.put("variables", "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}");
@ -62,31 +93,7 @@ public class StoriesService extends BaseService {
Log.e(TAG, "getFeedStories: body is empty");
return;
}
try {
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
final JSONArray feedStoriesReel = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("feed_reels_tray")
.getJSONObject("edge_reels_tray_to_reel")
.getJSONArray("edges");
for (int i = 0; i < feedStoriesReel.length(); ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
final ProfileModel profileModel = new ProfileModel(false, false, false,
user.getString("id"),
user.getString("username"),
null, null, null,
user.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final String id = node.getString("id");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead));
}
callback.onSuccess(feedStoryModels);
} catch (JSONException e) {
Log.e(TAG, "Error parsing json", e);
}
parseStoriesBody(body, callback);
}
@Override
@ -96,6 +103,34 @@ public class StoriesService extends BaseService {
});
}
private void parseStoriesBody(final String body, final ServiceCallback<List<FeedStoryModel>> callback) {
try {
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
final JSONArray feedStoriesReel = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("feed_reels_tray")
.getJSONObject("edge_reels_tray_to_reel")
.getJSONArray("edges");
for (int i = 0; i < feedStoriesReel.length(); ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
final ProfileModel profileModel = new ProfileModel(false, false, false,
user.getString("id"),
user.getString("username"),
null, null, null,
user.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false);
final String id = node.getString("id");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead));
}
callback.onSuccess(feedStoryModels);
} catch (JSONException e) {
Log.e(TAG, "Error parsing json", e);
}
}
public void getUserStory(final String id,
final String username,
final boolean isLoc,
@ -138,12 +173,15 @@ public class StoriesService extends BaseService {
data = media.getJSONObject(i);
final boolean isVideo = data.has("video_duration");
final StoryModel model = new StoryModel(data.getString("id"),
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0).getString("url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0),
(isLoc || isHashtag) ? data.getJSONObject("user").getString("username") : localUsername,
data.getJSONObject("user").getString("pk"),
data.getBoolean("can_reply"));
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
.getString("url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0),
(isLoc || isHashtag)
? data.getJSONObject("user").getString("username")
: localUsername,
data.getJSONObject("user").getString("pk"),
data.getBoolean("can_reply"));
final JSONArray videoResources = data.optJSONArray("video_versions");
if (isVideo && videoResources != null)
@ -174,7 +212,8 @@ public class StoriesService extends BaseService {
));
}
if (data.has("story_questions")) {
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0).optJSONObject("question_sticker");
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0)
.optJSONObject("question_sticker");
if (tappableObject != null && !tappableObject.getString("question_type").equals("music"))
model.setQuestion(new QuestionModel(
String.valueOf(tappableObject.getLong("question_id")),
@ -229,8 +268,7 @@ public class StoriesService extends BaseService {
models.add(model);
}
callback.onSuccess(models);
}
else {
} else {
callback.onSuccess(null);
}
} catch (JSONException e) {
@ -251,14 +289,11 @@ public class StoriesService extends BaseService {
builder.append("https://i.instagram.com/api/v1/");
if (isLoc) {
builder.append("locations/");
}
else if (isHashtag) {
} else if (isHashtag) {
builder.append("tags/");
}
else if (highlight) {
} else if (highlight) {
builder.append("feed/reels_media/?user_ids=");
}
else {
} else {
builder.append("feed/user/");
}
builder.append(userId);

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 have changed in this diff Show More