1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-18 12:47:30 +00:00

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,11 +118,8 @@ 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);
}

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) {
@ -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() {
@Override
public void onThumbnailClick() {
postClickListener.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.videoPost.thumbnail);
}
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));
}
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();
}
@Override
public void onPlayerViewLoaded() {
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams();
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.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();
binding.videoPost.playerView.requestLayout();
setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
}
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;
};
// 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())
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())
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,8 +309,7 @@ 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(),
FLog.v(getLogTag(),
"onTouchEvent: %d, view %x, handled by tap gesture detector",
a,
this.hashCode());
@ -389,19 +395,25 @@ 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(),
FLog.v(getLogTag(),
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
this.hashCode(),
mViewBounds,

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,21 +73,36 @@ 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;

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,14 +220,14 @@ 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) {

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,112 +165,115 @@ 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);
}
// 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;
// }
};
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;
}
};
private FeedStoriesViewModel feedStoriesViewModel;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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);
}
@ -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 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,20 +1,140 @@
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,
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,
@ -26,22 +146,29 @@ public final class FeedModel extends PostModel {
final long timestamp,
final boolean liked,
final boolean bookmarked,
final long likes,
final long likesCount,
final String locationName,
final String locationId) {
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked, likes);
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('?');
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";
/**
* 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);
}
return extension;
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 "";
}
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,6 +93,17 @@ public class StoriesService extends BaseService {
Log.e(TAG, "getFeedStories: body is empty");
return;
}
parseStoriesBody(body, callback);
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
private void parseStoriesBody(final String body, final ServiceCallback<List<FeedStoryModel>> callback) {
try {
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
final JSONArray feedStoriesReel = new JSONObject(body)
@ -89,13 +131,6 @@ public class StoriesService extends BaseService {
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void getUserStory(final String id,
final String username,
final boolean isLoc,
@ -138,10 +173,13 @@ 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"),
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,
(isLoc || isHashtag)
? data.getJSONObject("user").getString("username")
: localUsername,
data.getJSONObject("user").getString("pk"),
data.getBoolean("can_reply"));
@ -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