mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-23 05:16:58 +00:00
Initial commit for new posts layout
This commit is contained in:
parent
03f52d5058
commit
6bf59e83ad
@ -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'
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
@ -59,7 +60,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater());
|
||||
loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(getApplicationContext()));
|
||||
setContentView(loginBinding.getRoot());
|
||||
|
||||
initWebView();
|
||||
@ -117,12 +118,9 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
||||
webSettings.setDisplayZoomControls(false);
|
||||
webSettings.setLoadWithOverviewMode(true);
|
||||
webSettings.setUseWideViewPort(true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
webSettings.setAllowFileAccessFromFileURLs(true);
|
||||
webSettings.setAllowUniversalAccessFromFileURLs(true);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
webSettings.setAllowFileAccessFromFileURLs(true);
|
||||
webSettings.setAllowUniversalAccessFromFileURLs(true);
|
||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
169
app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java
Normal file
169
app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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<>();
|
||||
|
||||
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,15 @@
|
||||
package awais.instagrabber.adapters;
|
||||
|
||||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
||||
@Override
|
||||
public void onThumbnailLoaded(final int position) {}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(final int position) {}
|
||||
|
||||
@Override
|
||||
public void onPlayerPlay(final int position) {}
|
||||
|
||||
@Override
|
||||
public void onPlayerPause(final int position) {}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package awais.instagrabber.adapters;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
|
||||
public final class SliderItemsAdapter extends ListAdapter<PostChild, SliderItemViewHolder> {
|
||||
|
||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
||||
private final SliderCallback sliderCallback;
|
||||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
|
||||
|
||||
private static final DiffUtil.ItemCallback<PostChild> DIFF_CALLBACK = new DiffUtil.ItemCallback<PostChild>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) {
|
||||
return oldItem.getPostId().equals(newItem.getPostId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull final PostChild oldItem, @NonNull final PostChild newItem) {
|
||||
return oldItem.getPostId().equals(newItem.getPostId());
|
||||
}
|
||||
};
|
||||
|
||||
public SliderItemsAdapter() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
||||
final LayoutExoCustomControlsBinding controlsBinding,
|
||||
final SliderCallback sliderCallback) {
|
||||
super(DIFF_CALLBACK);
|
||||
this.onVerticalDragListener = onVerticalDragListener;
|
||||
this.sliderCallback = sliderCallback;
|
||||
this.controlsBinding = controlsBinding;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SliderItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
final MediaItemType mediaItemType = MediaItemType.valueOf(viewType);
|
||||
switch (mediaItemType) {
|
||||
case MEDIA_TYPE_VIDEO: {
|
||||
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
||||
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding);
|
||||
}
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
default:
|
||||
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
|
||||
return new SliderPhotoViewHolder(binding, onVerticalDragListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final SliderItemViewHolder holder, final int position) {
|
||||
final PostChild model = getItem(position);
|
||||
holder.bind(model, position, sliderCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(final int position) {
|
||||
final PostChild viewerPostModel = getItem(position);
|
||||
return viewerPostModel.getItemType().getId();
|
||||
}
|
||||
|
||||
// @NonNull
|
||||
// @Override
|
||||
// public Object instantiateItem(@NonNull final ViewGroup container, final int position) {
|
||||
// final Context context = container.getContext();
|
||||
// final ViewerPostModel sliderItem = sliderItems.get(position);
|
||||
//
|
||||
// if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
|
||||
// final ViewSwitcher viewSwitcher = createViewSwitcher(context, position, sliderItem.getThumbnailUrl(), sliderItem.getDisplayUrl());
|
||||
// container.addView(viewSwitcher);
|
||||
// return viewSwitcher;
|
||||
// }
|
||||
// final GenericDraweeHierarchy hierarchy = GenericDraweeHierarchyBuilder.newInstance(container.getResources())
|
||||
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
|
||||
// .build();
|
||||
// final SimpleDraweeView photoView = new SimpleDraweeView(context, hierarchy);
|
||||
// photoView.setLayoutParams(layoutParams);
|
||||
// final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(sliderItem.getDisplayUrl()))
|
||||
// .setLocalThumbnailPreviewsEnabled(true)
|
||||
// .setProgressiveRenderingEnabled(true)
|
||||
// .build();
|
||||
// photoView.setImageRequest(imageRequest);
|
||||
// container.addView(photoView);
|
||||
// return photoView;
|
||||
// }
|
||||
|
||||
// @NonNull
|
||||
// private ViewSwitcher createViewSwitcher(final Context context,
|
||||
// final int position,
|
||||
// final String thumbnailUrl,
|
||||
// final String displayUrl) {
|
||||
//
|
||||
// final ViewSwitcher viewSwitcher = new ViewSwitcher(context);
|
||||
// viewSwitcher.setLayoutParams(layoutParams);
|
||||
//
|
||||
// final FrameLayout frameLayout = new FrameLayout(context);
|
||||
// frameLayout.setLayoutParams(layoutParams);
|
||||
//
|
||||
// final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
|
||||
// .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
|
||||
// .build();
|
||||
// final SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context, hierarchy);
|
||||
// simpleDraweeView.setLayoutParams(layoutParams);
|
||||
// simpleDraweeView.setImageURI(thumbnailUrl);
|
||||
// frameLayout.addView(simpleDraweeView);
|
||||
//
|
||||
// final AppCompatImageView imageView = new AppCompatImageView(context);
|
||||
// final int px = Utils.convertDpToPx(50);
|
||||
// final FrameLayout.LayoutParams playButtonLayoutParams = new FrameLayout.LayoutParams(px, px);
|
||||
// playButtonLayoutParams.gravity = Gravity.CENTER;
|
||||
// imageView.setLayoutParams(playButtonLayoutParams);
|
||||
// imageView.setImageResource(R.drawable.exo_icon_play);
|
||||
// frameLayout.addView(imageView);
|
||||
//
|
||||
// viewSwitcher.addView(frameLayout);
|
||||
//
|
||||
// final PlayerView playerView = new PlayerView(context);
|
||||
// viewSwitcher.addView(playerView);
|
||||
// if (shouldAutoPlay && position == 0) {
|
||||
// loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener);
|
||||
// } else
|
||||
// frameLayout.setOnClickListener(v -> loadPlayer(context, position, displayUrl, viewSwitcher, factory, playerChangeListener));
|
||||
// return viewSwitcher;
|
||||
// }
|
||||
|
||||
public interface SliderCallback {
|
||||
void onThumbnailLoaded(int position);
|
||||
|
||||
void onItemClicked(int position);
|
||||
|
||||
void onPlayerPlay(int position);
|
||||
|
||||
void onPlayerPause(int position);
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.databinding.ItemFeedGridBinding;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
import static awais.instagrabber.models.PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID;
|
||||
|
||||
public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ItemFeedGridBinding binding;
|
||||
|
||||
public FeedGridItemViewHolder(@NonNull final ItemFeedGridBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
// for rounded borders (clip view to background shape)
|
||||
//
|
||||
}
|
||||
|
||||
public void bind(@NonNull final FeedModel feedModel,
|
||||
@NonNull final PostsLayoutPreferences layoutPreferences,
|
||||
final FeedAdapterV2.OnPostClickListener postClickListener,
|
||||
final boolean animate) {
|
||||
if (postClickListener != null) {
|
||||
itemView.setOnClickListener(v -> postClickListener.onPostClick(feedModel, binding.profilePic, binding.postImage));
|
||||
}
|
||||
itemView.setClipToOutline(layoutPreferences.getHasRoundedCorners());
|
||||
if (layoutPreferences.getType() == STAGGERED_GRID) {
|
||||
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
|
||||
binding.postImage.setAspectRatio(aspectRatio);
|
||||
} else {
|
||||
binding.postImage.setAspectRatio(1);
|
||||
}
|
||||
if (layoutPreferences.isAvatarVisible()) {
|
||||
binding.profilePic.setVisibility(View.VISIBLE);
|
||||
binding.profilePic.setImageURI(feedModel.getProfileModel().getSdProfilePic());
|
||||
final ViewGroup.LayoutParams layoutParams = binding.profilePic.getLayoutParams();
|
||||
@DimenRes final int dimenRes;
|
||||
switch (layoutPreferences.getProfilePicSize()) {
|
||||
case SMALL:
|
||||
dimenRes = R.dimen.profile_pic_size_small;
|
||||
break;
|
||||
case TINY:
|
||||
dimenRes = R.dimen.profile_pic_size_tiny;
|
||||
break;
|
||||
default:
|
||||
case REGULAR:
|
||||
dimenRes = R.dimen.profile_pic_size_regular;
|
||||
break;
|
||||
}
|
||||
final int dimensionPixelSize = itemView.getResources().getDimensionPixelSize(dimenRes);
|
||||
layoutParams.width = dimensionPixelSize;
|
||||
layoutParams.height = dimensionPixelSize;
|
||||
binding.profilePic.requestLayout();
|
||||
} else {
|
||||
binding.profilePic.setVisibility(View.GONE);
|
||||
}
|
||||
if (layoutPreferences.isNameVisible()) {
|
||||
binding.name.setVisibility(View.VISIBLE);
|
||||
binding.name.setText(feedModel.getProfileModel().getName());
|
||||
} else {
|
||||
binding.name.setVisibility(View.GONE);
|
||||
}
|
||||
String thumbnailUrl = null;
|
||||
final int typeIconRes;
|
||||
switch (feedModel.getItemType()) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
typeIconRes = -1;
|
||||
thumbnailUrl = feedModel.getThumbnailUrl();
|
||||
break;
|
||||
case MEDIA_TYPE_VIDEO:
|
||||
thumbnailUrl = feedModel.getThumbnailUrl();
|
||||
typeIconRes = R.drawable.exo_icon_play;
|
||||
break;
|
||||
case MEDIA_TYPE_SLIDER:
|
||||
final List<PostChild> sliderItems = feedModel.getSliderItems();
|
||||
if (sliderItems != null) {
|
||||
thumbnailUrl = sliderItems.get(0).getThumbnailUrl();
|
||||
}
|
||||
typeIconRes = R.drawable.ic_checkbox_multiple_blank_stroke;
|
||||
break;
|
||||
default:
|
||||
typeIconRes = -1;
|
||||
thumbnailUrl = null;
|
||||
}
|
||||
if (TextUtils.isEmpty(thumbnailUrl)) {
|
||||
binding.postImage.setController(null);
|
||||
return;
|
||||
}
|
||||
if (typeIconRes <= 0) {
|
||||
binding.typeIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.typeIcon.setVisibility(View.VISIBLE);
|
||||
binding.typeIcon.setImageResource(typeIconRes);
|
||||
}
|
||||
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
|
||||
.setLocalThumbnailPreviewsEnabled(true)
|
||||
.setProgressiveRenderingEnabled(true)
|
||||
.build();
|
||||
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
|
||||
.setImageRequest(requestBuilder)
|
||||
.setOldController(binding.postImage.getController());
|
||||
if (animate) {
|
||||
final BaseControllerListener<ImageInfo> imageListener = new BaseControllerListener<ImageInfo>() {
|
||||
@Override
|
||||
public void onFinalImageSet(final String id, final ImageInfo imageInfo, final Animatable animatable) {
|
||||
setAnimation(binding.getRoot());
|
||||
}
|
||||
};
|
||||
builder.setControllerListener(imageListener);
|
||||
}
|
||||
binding.postImage.setController(builder.build());
|
||||
}
|
||||
|
||||
private void setAnimation(View viewToAnimate) {
|
||||
final Animation animation = AnimationUtils.loadAnimation(viewToAnimate.getContext(), android.R.anim.fade_in);
|
||||
animation.setDuration(300);
|
||||
viewToAnimate.startAnimation(animation);
|
||||
}
|
||||
|
||||
public void clearAnimation() {
|
||||
binding.getRoot().clearAnimation();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -1,239 +1,240 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener;
|
||||
import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
|
||||
import awais.instagrabber.adapters.PostViewerChildAdapter;
|
||||
import awais.instagrabber.databinding.ItemFullPostViewBinding;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.models.ViewerPostModel;
|
||||
import awais.instagrabber.models.ViewerPostModelWrapper;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public class PostViewerViewHolder extends RecyclerView.ViewHolder {
|
||||
private static final String TAG = "PostViewerViewHolder";
|
||||
|
||||
private final ItemFullPostViewBinding binding;
|
||||
private int currentChildPosition;
|
||||
|
||||
public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
binding.topPanel.viewStoryPost.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void bind(final ViewerPostModelWrapper wrapper,
|
||||
final int position,
|
||||
final OnPostViewChildViewClickListener clickListener,
|
||||
final OnPostCaptionLongClickListener longClickListener,
|
||||
final MentionClickListener mentionClickListener) {
|
||||
if (wrapper == null) return;
|
||||
final ViewerPostModel[] items = wrapper.getViewerPostModels();
|
||||
if (items == null || items.length <= 0) return;
|
||||
if (items[0] == null) return;
|
||||
final PostViewerChildAdapter adapter = new PostViewerChildAdapter();
|
||||
binding.mediaViewPager.setAdapter(adapter);
|
||||
final ViewerPostModel firstPost = items[0];
|
||||
setPostInfo(firstPost, mentionClickListener);
|
||||
setMediaItems(items, adapter);
|
||||
setupListeners(wrapper,
|
||||
position,
|
||||
clickListener,
|
||||
longClickListener,
|
||||
mentionClickListener,
|
||||
firstPost.getLocation());
|
||||
}
|
||||
|
||||
private void setPostInfo(final ViewerPostModel firstPost,
|
||||
final MentionClickListener mentionClickListener) {
|
||||
final ProfileModel profileModel = firstPost.getProfileModel();
|
||||
if (profileModel == null) return;
|
||||
binding.topPanel.title.setText(profileModel.getUsername());
|
||||
final String locationName = firstPost.getLocationName();
|
||||
if (!TextUtils.isEmpty(locationName)) {
|
||||
binding.topPanel.location.setVisibility(View.VISIBLE);
|
||||
binding.topPanel.location.setText(locationName);
|
||||
} else binding.topPanel.location.setVisibility(View.GONE);
|
||||
binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
|
||||
binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount()));
|
||||
final CharSequence postCaption = firstPost.getPostCaption();
|
||||
if (TextUtils.hasMentions(postCaption)) {
|
||||
binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener);
|
||||
binding.bottomPanel.viewerCaption
|
||||
.setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
binding.bottomPanel.viewerCaption.setMentionClickListener(null);
|
||||
binding.bottomPanel.viewerCaption.setText(postCaption);
|
||||
}
|
||||
binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate());
|
||||
setupLikes(firstPost);
|
||||
setupSave(firstPost);
|
||||
}
|
||||
|
||||
private void setupLikes(final ViewerPostModel firstPost) {
|
||||
final boolean liked = firstPost.getLike();
|
||||
final long likeCount = firstPost.getLikes();
|
||||
final Resources resources = itemView.getContext().getResources();
|
||||
if (liked) {
|
||||
final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount));
|
||||
binding.btnLike.setText(unlikeString);
|
||||
ViewCompat.setBackgroundTintList(binding.btnLike,
|
||||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background)));
|
||||
} else {
|
||||
final String likeString = resources.getString(R.string.like, String.valueOf(likeCount));
|
||||
binding.btnLike.setText(likeString);
|
||||
ViewCompat.setBackgroundTintList(binding.btnLike,
|
||||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background)));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSave(final ViewerPostModel firstPost) {
|
||||
final boolean saved = firstPost.getBookmark();
|
||||
if (saved) {
|
||||
binding.btnBookmark.setText(R.string.unbookmark);
|
||||
ViewCompat.setBackgroundTintList(binding.btnBookmark,
|
||||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background)));
|
||||
} else {
|
||||
binding.btnBookmark.setText(R.string.bookmark);
|
||||
ViewCompat.setBackgroundTintList(
|
||||
binding.btnBookmark,
|
||||
ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background)));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupListeners(final ViewerPostModelWrapper wrapper,
|
||||
final int position,
|
||||
final OnPostViewChildViewClickListener clickListener,
|
||||
final OnPostCaptionLongClickListener longClickListener,
|
||||
final MentionClickListener mentionClickListener,
|
||||
final String location) {
|
||||
final View.OnClickListener onClickListener = v -> clickListener
|
||||
.onClick(v, wrapper, position, currentChildPosition);
|
||||
binding.bottomPanel.btnComments.setOnClickListener(onClickListener);
|
||||
binding.topPanel.title.setOnClickListener(onClickListener);
|
||||
binding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
|
||||
binding.bottomPanel.btnDownload.setOnClickListener(onClickListener);
|
||||
binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener);
|
||||
binding.btnLike.setOnClickListener(onClickListener);
|
||||
binding.btnBookmark.setOnClickListener(onClickListener);
|
||||
binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> {
|
||||
longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString());
|
||||
return true;
|
||||
});
|
||||
if (!TextUtils.isEmpty(location)) {
|
||||
binding.topPanel.location.setOnClickListener(v -> mentionClickListener
|
||||
.onClick(binding.topPanel.location, location, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaItems(final ViewerPostModel[] items,
|
||||
final PostViewerChildAdapter adapter) {
|
||||
final List<ViewerPostModel> filteredList = new ArrayList<>();
|
||||
for (final ViewerPostModel model : items) {
|
||||
final MediaItemType itemType = model.getItemType();
|
||||
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) {
|
||||
filteredList.add(model);
|
||||
}
|
||||
}
|
||||
binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
final String counter = "1/" + filteredList.size();
|
||||
binding.mediaCounter.setText(counter);
|
||||
adapter.submitList(filteredList);
|
||||
binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(final int position) {
|
||||
if (filteredList.size() <= 0 || position >= filteredList.size()) return;
|
||||
currentChildPosition = position;
|
||||
final String counter = (position + 1) + "/" + filteredList.size();
|
||||
binding.mediaCounter.setText(counter);
|
||||
final ViewerPostModel viewerPostModel = filteredList.get(position);
|
||||
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
|
||||
setVideoDetails(viewerPostModel);
|
||||
setVolumeListener(position);
|
||||
return;
|
||||
}
|
||||
setImageDetails();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setVolumeListener(final int position) {
|
||||
binding.bottomPanel.btnMute.setOnClickListener(v -> {
|
||||
try {
|
||||
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
|
||||
.getChildAt(0)).findViewHolderForAdapterPosition(position);
|
||||
if (viewHolder != null) {
|
||||
final View itemView = viewHolder.itemView;
|
||||
if (itemView instanceof PlayerView) {
|
||||
final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView)
|
||||
.getPlayer();
|
||||
if (player == null) return;
|
||||
final float vol = player.getVolume() == 0f ? 1f : 0f;
|
||||
player.setVolume(vol);
|
||||
binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24
|
||||
: R.drawable.ic_volume_off_24);
|
||||
Utils.sessionVolumeFull = vol == 1f;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setImageDetails() {
|
||||
binding.bottomPanel.btnMute.setVisibility(View.GONE);
|
||||
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setVideoDetails(final ViewerPostModel viewerPostModel) {
|
||||
binding.bottomPanel.btnMute.setVisibility(View.VISIBLE);
|
||||
final long videoViews = viewerPostModel.getVideoViews();
|
||||
if (videoViews < 0) {
|
||||
binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews));
|
||||
binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void stopPlayingVideo() {
|
||||
try {
|
||||
final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
|
||||
.getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition);
|
||||
if (viewHolder != null) {
|
||||
final View itemView = viewHolder.itemView;
|
||||
if (itemView instanceof PlayerView) {
|
||||
final Player player = ((PlayerView) itemView).getPlayer();
|
||||
if (player != null) {
|
||||
player.setPlayWhenReady(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// package awais.instagrabber.adapters.viewholder;
|
||||
//
|
||||
// import android.content.res.ColorStateList;
|
||||
// import android.content.res.Resources;
|
||||
// import android.util.Log;
|
||||
// import android.view.View;
|
||||
// import android.widget.TextView;
|
||||
//
|
||||
// import androidx.annotation.NonNull;
|
||||
// import androidx.core.content.ContextCompat;
|
||||
// import androidx.core.view.ViewCompat;
|
||||
// import androidx.recyclerview.widget.RecyclerView;
|
||||
// import androidx.viewpager2.widget.ViewPager2;
|
||||
//
|
||||
// import com.google.android.exoplayer2.Player;
|
||||
// import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
// import com.google.android.exoplayer2.ui.PlayerView;
|
||||
//
|
||||
// import java.util.ArrayList;
|
||||
// import java.util.List;
|
||||
//
|
||||
// import awais.instagrabber.R;
|
||||
// import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener;
|
||||
// import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener;
|
||||
// import awais.instagrabber.adapters.PostViewerChildAdapter;
|
||||
// import awais.instagrabber.databinding.ItemFullPostViewBinding;
|
||||
// import awais.instagrabber.interfaces.MentionClickListener;
|
||||
// import awais.instagrabber.models.PostChild;
|
||||
// import awais.instagrabber.models.ProfileModel;
|
||||
// import awais.instagrabber.models.ViewerPostModel;
|
||||
// import awais.instagrabber.models.ViewerPostModelWrapper;
|
||||
// import awais.instagrabber.models.enums.MediaItemType;
|
||||
// import awais.instagrabber.utils.TextUtils;
|
||||
// import awais.instagrabber.utils.Utils;
|
||||
//
|
||||
// public class PostViewerViewHolder extends RecyclerView.ViewHolder {
|
||||
// private static final String TAG = "PostViewerViewHolder";
|
||||
//
|
||||
// private final ItemFullPostViewBinding binding;
|
||||
// private int currentChildPosition;
|
||||
//
|
||||
// public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) {
|
||||
// super(binding.getRoot());
|
||||
// this.binding = binding;
|
||||
// binding.topPanel.viewStoryPost.setVisibility(View.GONE);
|
||||
// }
|
||||
//
|
||||
// public void bind(final ViewerPostModelWrapper wrapper,
|
||||
// final int position,
|
||||
// final OnPostViewChildViewClickListener clickListener,
|
||||
// final OnPostCaptionLongClickListener longClickListener,
|
||||
// final MentionClickListener mentionClickListener) {
|
||||
// if (wrapper == null) return;
|
||||
// final List<PostChild> items = wrapper.getViewerPostModels();
|
||||
// if (items == null || items.size() == 0) return;
|
||||
// if (items.get(0) == null) return;
|
||||
// final PostViewerChildAdapter adapter = new PostViewerChildAdapter();
|
||||
// binding.mediaViewPager.setAdapter(adapter);
|
||||
// final PostChild firstPost = items.get(0);
|
||||
// setPostInfo(firstPost, mentionClickListener);
|
||||
// setMediaItems(items, adapter);
|
||||
// setupListeners(wrapper,
|
||||
// position,
|
||||
// clickListener,
|
||||
// longClickListener,
|
||||
// mentionClickListener,
|
||||
// firstPost.getLocation());
|
||||
// }
|
||||
//
|
||||
// private void setPostInfo(final PostChild firstPost,
|
||||
// final MentionClickListener mentionClickListener) {
|
||||
// final ProfileModel profileModel = firstPost.getProfileModel();
|
||||
// if (profileModel == null) return;
|
||||
// binding.topPanel.title.setText(profileModel.getUsername());
|
||||
// final String locationName = firstPost.getLocationName();
|
||||
// if (!TextUtils.isEmpty(locationName)) {
|
||||
// binding.topPanel.location.setVisibility(View.VISIBLE);
|
||||
// binding.topPanel.location.setText(locationName);
|
||||
// } else binding.topPanel.location.setVisibility(View.GONE);
|
||||
// binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic());
|
||||
// binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount()));
|
||||
// final CharSequence postCaption = firstPost.getPostCaption();
|
||||
// if (TextUtils.hasMentions(postCaption)) {
|
||||
// binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener);
|
||||
// binding.bottomPanel.viewerCaption
|
||||
// .setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE);
|
||||
// } else {
|
||||
// binding.bottomPanel.viewerCaption.setMentionClickListener(null);
|
||||
// binding.bottomPanel.viewerCaption.setText(postCaption);
|
||||
// }
|
||||
// binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate());
|
||||
// setupLikes(firstPost);
|
||||
// setupSave(firstPost);
|
||||
// }
|
||||
//
|
||||
// private void setupLikes(final ViewerPostModel firstPost) {
|
||||
// final boolean liked = firstPost.getLike();
|
||||
// final long likeCount = firstPost.getLikes();
|
||||
// final Resources resources = itemView.getContext().getResources();
|
||||
// if (liked) {
|
||||
// final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount));
|
||||
// binding.btnLike.setText(unlikeString);
|
||||
// ViewCompat.setBackgroundTintList(binding.btnLike,
|
||||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background)));
|
||||
// } else {
|
||||
// final String likeString = resources.getString(R.string.like, String.valueOf(likeCount));
|
||||
// binding.btnLike.setText(likeString);
|
||||
// ViewCompat.setBackgroundTintList(binding.btnLike,
|
||||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background)));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void setupSave(final ViewerPostModel firstPost) {
|
||||
// final boolean saved = firstPost.isSaved();
|
||||
// if (saved) {
|
||||
// binding.btnBookmark.setText(R.string.unbookmark);
|
||||
// ViewCompat.setBackgroundTintList(binding.btnBookmark,
|
||||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background)));
|
||||
// } else {
|
||||
// binding.btnBookmark.setText(R.string.bookmark);
|
||||
// ViewCompat.setBackgroundTintList(
|
||||
// binding.btnBookmark,
|
||||
// ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background)));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void setupListeners(final ViewerPostModelWrapper wrapper,
|
||||
// final int position,
|
||||
// final OnPostViewChildViewClickListener clickListener,
|
||||
// final OnPostCaptionLongClickListener longClickListener,
|
||||
// final MentionClickListener mentionClickListener,
|
||||
// final String location) {
|
||||
// final View.OnClickListener onClickListener = v -> clickListener
|
||||
// .onClick(v, wrapper, position, currentChildPosition);
|
||||
// binding.bottomPanel.btnComments.setOnClickListener(onClickListener);
|
||||
// binding.topPanel.title.setOnClickListener(onClickListener);
|
||||
// binding.topPanel.ivProfilePic.setOnClickListener(onClickListener);
|
||||
// binding.bottomPanel.btnDownload.setOnClickListener(onClickListener);
|
||||
// binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener);
|
||||
// binding.btnLike.setOnClickListener(onClickListener);
|
||||
// binding.btnBookmark.setOnClickListener(onClickListener);
|
||||
// binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> {
|
||||
// longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString());
|
||||
// return true;
|
||||
// });
|
||||
// if (!TextUtils.isEmpty(location)) {
|
||||
// binding.topPanel.location.setOnClickListener(v -> mentionClickListener
|
||||
// .onClick(binding.topPanel.location, location, false, true));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void setMediaItems(final List<ViewerPostModel> items,
|
||||
// final PostViewerChildAdapter adapter) {
|
||||
// final List<ViewerPostModel> filteredList = new ArrayList<>();
|
||||
// for (final ViewerPostModel model : items) {
|
||||
// final MediaItemType itemType = model.getItemType();
|
||||
// if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) {
|
||||
// filteredList.add(model);
|
||||
// }
|
||||
// }
|
||||
// binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
// final String counter = "1/" + filteredList.size();
|
||||
// binding.mediaCounter.setText(counter);
|
||||
// adapter.submitList(filteredList);
|
||||
// binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
// @Override
|
||||
// public void onPageSelected(final int position) {
|
||||
// if (filteredList.size() <= 0 || position >= filteredList.size()) return;
|
||||
// currentChildPosition = position;
|
||||
// final String counter = (position + 1) + "/" + filteredList.size();
|
||||
// binding.mediaCounter.setText(counter);
|
||||
// final ViewerPostModel viewerPostModel = filteredList.get(position);
|
||||
// if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) {
|
||||
// setVideoDetails(viewerPostModel);
|
||||
// setVolumeListener(position);
|
||||
// return;
|
||||
// }
|
||||
// setImageDetails();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// private void setVolumeListener(final int position) {
|
||||
// binding.bottomPanel.btnMute.setOnClickListener(v -> {
|
||||
// try {
|
||||
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
|
||||
// .getChildAt(0)).findViewHolderForAdapterPosition(position);
|
||||
// if (viewHolder != null) {
|
||||
// final View itemView = viewHolder.itemView;
|
||||
// if (itemView instanceof PlayerView) {
|
||||
// final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView)
|
||||
// .getPlayer();
|
||||
// if (player == null) return;
|
||||
// final float vol = player.getVolume() == 0f ? 1f : 0f;
|
||||
// player.setVolume(vol);
|
||||
// binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24
|
||||
// : R.drawable.ic_volume_off_24);
|
||||
// Utils.sessionVolumeFull = vol == 1f;
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// Log.e(TAG, "Error", e);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// private void setImageDetails() {
|
||||
// binding.bottomPanel.btnMute.setVisibility(View.GONE);
|
||||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
|
||||
// }
|
||||
//
|
||||
// private void setVideoDetails(final ViewerPostModel viewerPostModel) {
|
||||
// binding.bottomPanel.btnMute.setVisibility(View.VISIBLE);
|
||||
// final long videoViews = viewerPostModel.getVideoViews();
|
||||
// if (videoViews < 0) {
|
||||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE);
|
||||
// return;
|
||||
// }
|
||||
// binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews));
|
||||
// binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE);
|
||||
// }
|
||||
//
|
||||
// public void stopPlayingVideo() {
|
||||
// try {
|
||||
// final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager
|
||||
// .getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition);
|
||||
// if (viewHolder != null) {
|
||||
// final View itemView = viewHolder.itemView;
|
||||
// if (itemView instanceof PlayerView) {
|
||||
// final Player player = ((PlayerView) itemView).getPlayer();
|
||||
// if (player != null) {
|
||||
// player.setPlayWhenReady(false);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// Log.e(TAG, "Error", e);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -0,0 +1,21 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
|
||||
public abstract class SliderItemViewHolder extends RecyclerView.ViewHolder {
|
||||
private static final String TAG = "FeedSliderItemViewHolder";
|
||||
|
||||
public SliderItemViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(final PostChild model,
|
||||
final int position,
|
||||
final SliderItemsAdapter.SliderCallback sliderCallback);
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
|
||||
public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
||||
private static final String TAG = "FeedSliderPhotoViewHolder";
|
||||
|
||||
private final ItemSliderPhotoBinding binding;
|
||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
||||
|
||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
|
||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
this.onVerticalDragListener = onVerticalDragListener;
|
||||
}
|
||||
|
||||
public void bind(@NonNull final PostChild model,
|
||||
final int position,
|
||||
final SliderItemsAdapter.SliderCallback sliderCallback) {
|
||||
final ImageRequest requestBuilder = ImageRequestBuilder
|
||||
.newBuilderWithSource(Uri.parse(model.getDisplayUrl()))
|
||||
.setLocalThumbnailPreviewsEnabled(true)
|
||||
.build();
|
||||
binding.getRoot()
|
||||
.setController(Fresco.newDraweeControllerBuilder()
|
||||
.setImageRequest(requestBuilder)
|
||||
.setControllerListener(new BaseControllerListener<ImageInfo>() {
|
||||
@Override
|
||||
public void onFailure(final String id, final Throwable throwable) {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onThumbnailLoaded(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalImageSet(final String id,
|
||||
final ImageInfo imageInfo,
|
||||
final Animatable animatable) {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onThumbnailLoaded(position);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setLowResImageRequest(ImageRequest.fromUri(model.getThumbnailUrl()))
|
||||
.build());
|
||||
binding.getRoot().setOnClickListener(v -> {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onItemClicked(position);
|
||||
}
|
||||
});
|
||||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
||||
zoomableController.setMaxScaleFactor(3f);
|
||||
binding.getRoot().setZoomableController(zoomableController);
|
||||
if (onVerticalDragListener != null) {
|
||||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
|
||||
}
|
||||
}
|
||||
|
||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
||||
// final int spanWidth = deviceWidth / spanCount;
|
||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
||||
// if (animate) {
|
||||
// Animation animation = AnimationUtils.expand(
|
||||
// binding.imageViewer,
|
||||
// layoutParams.width,
|
||||
// layoutParams.height,
|
||||
// width,
|
||||
// height,
|
||||
// new Animation.AnimationListener() {
|
||||
// @Override
|
||||
// public void onAnimationStart(final Animation animation) {
|
||||
// showOrHideDetails(spanCount);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onAnimationEnd(final Animation animation) {
|
||||
// // showOrHideDetails(spanCount);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onAnimationRepeat(final Animation animation) {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// binding.imageViewer.startAnimation(animation);
|
||||
// } else {
|
||||
// layoutParams.width = width;
|
||||
// layoutParams.height = height;
|
||||
// binding.imageViewer.requestLayout();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void showOrHideDetails(final int spanCount) {
|
||||
// if (spanCount == 1) {
|
||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
||||
// } else {
|
||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
||||
// }
|
||||
// }
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package awais.instagrabber.adapters.viewholder;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
public class SliderVideoViewHolder extends SliderItemViewHolder {
|
||||
private static final String TAG = "SliderVideoViewHolder";
|
||||
|
||||
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
||||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
|
||||
private VideoPlayerViewHelper videoPlayerViewHelper;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
||||
final LayoutExoCustomControlsBinding controlsBinding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
this.controlsBinding = controlsBinding;
|
||||
final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
|
||||
final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
|
||||
thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
||||
playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
||||
binding.thumbnailParent.setOnTouchListener((v, event) -> {
|
||||
final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
|
||||
if (onDragTouch) {
|
||||
return true;
|
||||
}
|
||||
return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
|
||||
});
|
||||
binding.playerView.setOnTouchListener((v, event) -> {
|
||||
final boolean onDragTouch = playerVerticalDragHelper.onDragTouch(event);
|
||||
if (onDragTouch) {
|
||||
return true;
|
||||
}
|
||||
return playerVerticalDragHelper.onGestureTouchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
public void bind(@NonNull final PostChild model,
|
||||
final int position,
|
||||
final SliderItemsAdapter.SliderCallback sliderCallback) {
|
||||
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
||||
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
||||
@Override
|
||||
public void onThumbnailLoaded() {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onThumbnailLoaded(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerViewLoaded() {
|
||||
// binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams();
|
||||
final int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, model.getHeight(), model.getWidth());
|
||||
layoutParams.width = requiredWidth;
|
||||
layoutParams.height = resultingHeight;
|
||||
binding.playerView.requestLayout();
|
||||
// setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlay() {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onPlayerPlay(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onPlayerPause(position);
|
||||
}
|
||||
}
|
||||
};
|
||||
final float aspectRatio = (float) model.getWidth() / model.getHeight();
|
||||
videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
|
||||
binding,
|
||||
model.getDisplayUrl(),
|
||||
vol,
|
||||
aspectRatio,
|
||||
model.getThumbnailUrl(),
|
||||
controlsBinding,
|
||||
videoPlayerCallback);
|
||||
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
|
||||
// final float newVol = videoPlayerViewHelper.toggleMute();
|
||||
// setMuteIcon(newVol);
|
||||
// Utils.sessionVolumeFull = newVol == 1f;
|
||||
// });
|
||||
binding.playerView.setOnClickListener(v -> {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onItemClicked(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if (videoPlayerViewHelper == null) return;
|
||||
videoPlayerViewHelper.pause();
|
||||
}
|
||||
|
||||
public void releasePlayer() {
|
||||
if (videoPlayerViewHelper == null) return;
|
||||
videoPlayerViewHelper.releasePlayer();
|
||||
}
|
||||
|
||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
||||
// final int spanWidth = deviceWidth / spanCount;
|
||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
||||
// if (animate) {
|
||||
// Animation animation = AnimationUtils.expand(
|
||||
// binding.imageViewer,
|
||||
// layoutParams.width,
|
||||
// layoutParams.height,
|
||||
// width,
|
||||
// height,
|
||||
// new Animation.AnimationListener() {
|
||||
// @Override
|
||||
// public void onAnimationStart(final Animation animation) {
|
||||
// showOrHideDetails(spanCount);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onAnimationEnd(final Animation animation) {
|
||||
// // showOrHideDetails(spanCount);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onAnimationRepeat(final Animation animation) {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// binding.imageViewer.startAnimation(animation);
|
||||
// } else {
|
||||
// layoutParams.width = width;
|
||||
// layoutParams.height = height;
|
||||
// binding.imageViewer.requestLayout();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void showOrHideDetails(final int spanCount) {
|
||||
// if (spanCount == 1) {
|
||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
||||
// } else {
|
||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
||||
// }
|
||||
// }
|
||||
}
|
@ -9,6 +9,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.customviews.CommentMentionClickSpan;
|
||||
import awais.instagrabber.customviews.RamboTextView;
|
||||
import awais.instagrabber.databinding.ItemFeedBottomBinding;
|
||||
@ -34,17 +35,19 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
this.topBinding = topBinding;
|
||||
this.bottomBinding = bottomBinding;
|
||||
this.mentionClickListener = mentionClickListener;
|
||||
// itemView.setOnClickListener(clickListener);
|
||||
// topBinding.title.setMovementMethod(new LinkMovementMethod());
|
||||
bottomBinding.btnComments.setOnClickListener(clickListener);
|
||||
topBinding.viewStoryPost.setOnClickListener(clickListener);
|
||||
topBinding.ivProfilePic.setOnClickListener(clickListener);
|
||||
bottomBinding.btnDownload.setOnClickListener(clickListener);
|
||||
bottomBinding.viewerCaption.setOnClickListener(clickListener);
|
||||
bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
|
||||
bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
|
||||
// bottomBinding.btnComments.setOnClickListener(clickListener);
|
||||
// topBinding.viewStoryPost.setOnClickListener(clickListener);
|
||||
// topBinding.ivProfilePic.setOnClickListener(clickListener);
|
||||
// bottomBinding.btnDownload.setOnClickListener(clickListener);
|
||||
// bottomBinding.viewerCaption.setOnClickListener(clickListener);
|
||||
// bottomBinding.viewerCaption.setOnLongClickListener(longClickListener);
|
||||
// bottomBinding.viewerCaption.setMentionClickListener(mentionClickListener);
|
||||
}
|
||||
|
||||
public void bind(final FeedModel feedModel) {
|
||||
public void bind(final FeedModel feedModel,
|
||||
final FeedAdapterV2.OnPostClickListener postClickListener) {
|
||||
if (feedModel == null) {
|
||||
return;
|
||||
}
|
||||
@ -83,7 +86,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
}
|
||||
}
|
||||
expandCollapseTextView(bottomBinding.viewerCaption, feedModel.getPostCaption());
|
||||
bindItem(feedModel);
|
||||
bindItem(feedModel, postClickListener);
|
||||
}
|
||||
|
||||
private void setLocation(final String locationName, final String locationId) {
|
||||
@ -106,7 +109,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation]
|
||||
*
|
||||
* @param textView the {@link RamboTextView} view, to expand and collapse
|
||||
* @param caption caption
|
||||
* @param caption caption
|
||||
* @return isExpanded
|
||||
*/
|
||||
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, final CharSequence caption) {
|
||||
@ -116,7 +119,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
if (textView.isCaptionExpanded()) {
|
||||
textView.setText(caption, bufferType);
|
||||
textView.setCaptionIsExpanded(false);
|
||||
// textView.setCaptionIsExpanded(false);
|
||||
return true;
|
||||
}
|
||||
int i = TextUtils.indexOfChar(caption, '\r', 0);
|
||||
@ -129,10 +132,15 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
if (TextUtils.hasMentions(caption))
|
||||
textView.setText(TextUtils.getMentionText(caption), TextView.BufferType.SPANNABLE);
|
||||
textView.setCaptionIsExpandable(true);
|
||||
textView.setCaptionIsExpanded(true);
|
||||
// textView.setCaptionIsExpandable(true);
|
||||
// textView.setCaptionIsExpanded(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract void bindItem(final FeedModel feedModel);
|
||||
public abstract void bindItem(final FeedModel feedModel,
|
||||
final FeedAdapterV2.OnPostClickListener postClickListener);
|
||||
|
||||
public void clearAnimation() {
|
||||
itemView.clearAnimation();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package awais.instagrabber.adapters.viewholder.feed;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
@ -9,19 +8,14 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||
import awais.instagrabber.databinding.ItemFeedVideoBinding;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
@ -40,14 +34,13 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
||||
|
||||
private CacheDataSourceFactory cacheDataSourceFactory;
|
||||
private FeedModel feedModel;
|
||||
private SimpleExoPlayer player;
|
||||
|
||||
private final Runnable loadRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadPlayer(feedModel);
|
||||
}
|
||||
};
|
||||
// private final Runnable loadRunnable = new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// // loadPlayer(feedModel);
|
||||
// }
|
||||
// };
|
||||
|
||||
public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding,
|
||||
final MentionClickListener mentionClickListener,
|
||||
@ -66,72 +59,55 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindItem(final FeedModel feedModel) {
|
||||
public void bindItem(final FeedModel feedModel,
|
||||
final FeedAdapterV2.OnPostClickListener postClickListener) {
|
||||
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
|
||||
this.feedModel = feedModel;
|
||||
setThumbnail(feedModel);
|
||||
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(feedModel.getViewCount()));
|
||||
}
|
||||
// showOrHideDetails(false);
|
||||
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
||||
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
||||
|
||||
private void setThumbnail(final FeedModel feedModel) {
|
||||
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams();
|
||||
final int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth();
|
||||
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight();
|
||||
binding.thumbnailParent.requestLayout();
|
||||
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(feedModel.getThumbnailUrl()))
|
||||
.setProgressiveRenderingEnabled(true)
|
||||
.build();
|
||||
final DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setImageRequest(thumbnailRequest)
|
||||
.build();
|
||||
binding.thumbnail.setController(controller);
|
||||
binding.thumbnailParent.setOnClickListener(v -> loadPlayer(feedModel));
|
||||
}
|
||||
@Override
|
||||
public void onThumbnailClick() {
|
||||
postClickListener.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.videoPost.thumbnail);
|
||||
}
|
||||
|
||||
private void loadPlayer(final FeedModel feedModel) {
|
||||
if (feedModel == null) {
|
||||
return;
|
||||
}
|
||||
// Log.d(TAG, "playing post:" + feedModel.getPostId());
|
||||
if (binding.viewSwitcher.getDisplayedChild() == 0) {
|
||||
binding.viewSwitcher.showNext();
|
||||
}
|
||||
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams();
|
||||
final int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
||||
layoutParams.width = requiredWidth;
|
||||
layoutParams.height = resultingHeight;
|
||||
binding.playerView.requestLayout();
|
||||
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
||||
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
|
||||
setMuteIcon(vol);
|
||||
player = (SimpleExoPlayer) binding.playerView.getPlayer();
|
||||
if (player != null) {
|
||||
player.release();
|
||||
}
|
||||
player = new SimpleExoPlayer.Builder(itemView.getContext())
|
||||
.setLooper(Looper.getMainLooper())
|
||||
.build();
|
||||
player.setVolume(vol);
|
||||
player.setPlayWhenReady(true);
|
||||
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
|
||||
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
|
||||
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
player.prepare(mediaSource);
|
||||
binding.playerView.setPlayer(player);
|
||||
final SimpleExoPlayer finalPlayer = player;
|
||||
@Override
|
||||
public void onPlayerViewLoaded() {
|
||||
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams();
|
||||
final int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
||||
layoutParams.width = requiredWidth;
|
||||
layoutParams.height = resultingHeight;
|
||||
binding.videoPost.playerView.requestLayout();
|
||||
setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
|
||||
}
|
||||
};
|
||||
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
|
||||
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
|
||||
// final Uri uri = Uri.parse(feedModel.getDisplayUrl());
|
||||
// final MediaItem mediaItem = MediaItem.fromUri(uri);
|
||||
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
|
||||
final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
|
||||
final VideoPlayerViewHelper videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
|
||||
binding.videoPost,
|
||||
feedModel.getDisplayUrl(),
|
||||
vol,
|
||||
aspectRatio,
|
||||
feedModel.getThumbnailUrl(),
|
||||
null,
|
||||
videoPlayerCallback);
|
||||
binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
|
||||
final float intVol = finalPlayer.getVolume() == 0f ? 1f : 0f;
|
||||
finalPlayer.setVolume(intVol);
|
||||
setMuteIcon(intVol);
|
||||
Utils.sessionVolumeFull = intVol == 1f;
|
||||
final float newVol = videoPlayerViewHelper.toggleMute();
|
||||
setMuteIcon(newVol);
|
||||
Utils.sessionVolumeFull = newVol == 1f;
|
||||
});
|
||||
binding.playerView.setOnClickListener(v -> finalPlayer.setPlayWhenReady(!finalPlayer.getPlayWhenReady()));
|
||||
binding.videoPost.playerView.setOnClickListener(v -> videoPlayerViewHelper.togglePlayback());
|
||||
}
|
||||
|
||||
|
||||
private void setMuteIcon(final float vol) {
|
||||
binding.itemFeedBottom.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
|
||||
}
|
||||
@ -140,19 +116,29 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
||||
return feedModel;
|
||||
}
|
||||
|
||||
public void stopPlaying() {
|
||||
// Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
|
||||
handler.removeCallbacks(loadRunnable);
|
||||
if (player != null) {
|
||||
player.release();
|
||||
}
|
||||
if (binding.viewSwitcher.getDisplayedChild() == 1) {
|
||||
binding.viewSwitcher.showPrevious();
|
||||
}
|
||||
}
|
||||
// public void stopPlaying() {
|
||||
// // Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
|
||||
// handler.removeCallbacks(loadRunnable);
|
||||
// if (player != null) {
|
||||
// player.release();
|
||||
// }
|
||||
// if (binding.videoPost.root.getDisplayedChild() == 1) {
|
||||
// binding.videoPost.root.showPrevious();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void startPlaying() {
|
||||
// handler.removeCallbacks(loadRunnable);
|
||||
// handler.postDelayed(loadRunnable, 800);
|
||||
// }
|
||||
|
||||
public void startPlaying() {
|
||||
handler.removeCallbacks(loadRunnable);
|
||||
handler.postDelayed(loadRunnable, 800);
|
||||
private void showOrHideDetails(final boolean show) {
|
||||
if (show) {
|
||||
binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
||||
binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
||||
binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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();
|
||||
// }
|
||||
// }
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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" +
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,259 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.lifecycle.ViewModelStoreOwner;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.adapters.FeedAdapterV2.OnPostClickListener;
|
||||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
||||
import awais.instagrabber.customviews.helpers.PostFetcher;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.viewmodels.FeedViewModel;
|
||||
|
||||
public class PostsRecyclerView extends RecyclerView {
|
||||
private static final String TAG = "PostsRecyclerView";
|
||||
|
||||
private StaggeredGridLayoutManager gridLayoutManager;
|
||||
private PostsLayoutPreferences layoutPreferences;
|
||||
private PostFetcher.PostFetchService postFetchService;
|
||||
private Transition transition;
|
||||
private OnClickListener postViewClickListener;
|
||||
private MentionClickListener mentionClickListener;
|
||||
private PostFetcher postFetcher;
|
||||
private ViewModelStoreOwner viewModelStoreOwner;
|
||||
private FeedAdapterV2 feedAdapter;
|
||||
private LifecycleOwner lifeCycleOwner;
|
||||
private FeedViewModel feedViewModel;
|
||||
private boolean initCalled = false;
|
||||
private GridSpacingItemDecoration gridSpacingItemDecoration;
|
||||
private RecyclerLazyLoaderAtBottom lazyLoader;
|
||||
private OnPostClickListener onPostClickListener;
|
||||
|
||||
private final FetchListener<List<FeedModel>> fetchListener = new FetchListener<List<FeedModel>>() {
|
||||
@Override
|
||||
public void onResult(final List<FeedModel> result) {
|
||||
final int currentPage = lazyLoader.getCurrentPage();
|
||||
if (currentPage == 0) {
|
||||
feedViewModel.getList().postValue(result);
|
||||
dispatchFetchStatus();
|
||||
return;
|
||||
}
|
||||
final List<FeedModel> models = feedViewModel.getList().getValue();
|
||||
final List<FeedModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
|
||||
modelsCopy.addAll(result);
|
||||
feedViewModel.getList().postValue(modelsCopy);
|
||||
dispatchFetchStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "onFailure: ", t);
|
||||
}
|
||||
};
|
||||
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
|
||||
|
||||
public PostsRecyclerView(@NonNull final Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PostsRecyclerView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public PostsRecyclerView setViewModelStoreOwner(final ViewModelStoreOwner owner) {
|
||||
if (initCalled) {
|
||||
throw new IllegalArgumentException("init already called!");
|
||||
}
|
||||
this.viewModelStoreOwner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PostsRecyclerView setLifeCycleOwner(final LifecycleOwner lifeCycleOwner) {
|
||||
if (initCalled) {
|
||||
throw new IllegalArgumentException("init already called!");
|
||||
}
|
||||
this.lifeCycleOwner = lifeCycleOwner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PostsRecyclerView setPostFetchService(final PostFetcher.PostFetchService postFetchService) {
|
||||
if (initCalled) {
|
||||
throw new IllegalArgumentException("init already called!");
|
||||
}
|
||||
this.postFetchService = postFetchService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PostsRecyclerView setOnPostClickListener(@NonNull final OnPostClickListener onPostClickListener) {
|
||||
this.onPostClickListener = onPostClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PostsRecyclerView setMentionClickListener(final MentionClickListener mentionClickListener) {
|
||||
this.mentionClickListener = mentionClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PostsRecyclerView setLayoutPreferences(final PostsLayoutPreferences layoutPreferences) {
|
||||
this.layoutPreferences = layoutPreferences;
|
||||
if (initCalled) {
|
||||
if (layoutPreferences == null) return this;
|
||||
feedAdapter.setLayoutPreferences(layoutPreferences);
|
||||
updateLayout();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
initCalled = true;
|
||||
if (viewModelStoreOwner == null) {
|
||||
throw new IllegalArgumentException("ViewModelStoreOwner cannot be null");
|
||||
} else if (lifeCycleOwner == null) {
|
||||
throw new IllegalArgumentException("LifecycleOwner cannot be null");
|
||||
} else if (postFetchService == null) {
|
||||
throw new IllegalArgumentException("PostFetchService cannot be null");
|
||||
}
|
||||
if (layoutPreferences == null) {
|
||||
layoutPreferences = PostsLayoutPreferences.builder()
|
||||
.setType(PostsLayoutPreferences.PostsLayoutType.GRID)
|
||||
.setColCount(3)
|
||||
.setAvatarVisible(true)
|
||||
.setNameVisible(false)
|
||||
.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.TINY)
|
||||
.setHasGap(true)
|
||||
.setHasRoundedCorners(true)
|
||||
.build();
|
||||
Utils.settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, layoutPreferences.getJson());
|
||||
}
|
||||
gridSpacingItemDecoration = new GridSpacingItemDecoration(Utils.convertDpToPx(2));
|
||||
initTransition();
|
||||
initAdapter();
|
||||
initLayoutManager();
|
||||
initSelf();
|
||||
|
||||
}
|
||||
|
||||
private void initTransition() {
|
||||
transition = new ChangeBounds();
|
||||
// transition.addListener(new TransitionListenerAdapter(){
|
||||
// @Override
|
||||
// public void onTransitionEnd(@NonNull final Transition transition) {
|
||||
// super.onTransitionEnd(transition);
|
||||
// }
|
||||
// });
|
||||
transition.setDuration(300);
|
||||
}
|
||||
|
||||
private void initLayoutManager() {
|
||||
gridLayoutManager = new StaggeredGridLayoutManager(layoutPreferences.getColCount(), StaggeredGridLayoutManager.VERTICAL);
|
||||
setLayoutManager(gridLayoutManager);
|
||||
}
|
||||
|
||||
private void initAdapter() {
|
||||
feedAdapter = new FeedAdapterV2(
|
||||
layoutPreferences,
|
||||
postViewClickListener,
|
||||
mentionClickListener,
|
||||
(feedModel, view, postImage) -> {
|
||||
if (onPostClickListener != null) {
|
||||
onPostClickListener.onPostClick(feedModel, view, postImage);
|
||||
}
|
||||
});
|
||||
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||
setAdapter(feedAdapter);
|
||||
}
|
||||
|
||||
private void initSelf() {
|
||||
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
|
||||
feedViewModel.getList().observe(lifeCycleOwner, feedAdapter::submitList);
|
||||
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
||||
addItemDecoration(gridSpacingItemDecoration);
|
||||
setHasFixedSize(true);
|
||||
lazyLoader = new RecyclerLazyLoaderAtBottom(gridLayoutManager, (page) -> {
|
||||
if (postFetcher.hasMore()) {
|
||||
postFetcher.fetchNextPage();
|
||||
dispatchFetchStatus();
|
||||
}
|
||||
});
|
||||
addOnScrollListener(lazyLoader);
|
||||
postFetcher.fetch();
|
||||
dispatchFetchStatus();
|
||||
}
|
||||
|
||||
private void updateLayout() {
|
||||
post(() -> {
|
||||
TransitionManager.beginDelayedTransition(this, transition);
|
||||
feedAdapter.notifyDataSetChanged();
|
||||
if (!layoutPreferences.getHasGap()) {
|
||||
removeItemDecoration(gridSpacingItemDecoration);
|
||||
}
|
||||
gridLayoutManager.setSpanCount(layoutPreferences.getColCount());
|
||||
});
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
lazyLoader.resetState();
|
||||
postFetcher.fetch();
|
||||
dispatchFetchStatus();
|
||||
}
|
||||
|
||||
public boolean isFetching() {
|
||||
return postFetcher != null && postFetcher.isFetching();
|
||||
}
|
||||
|
||||
public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) {
|
||||
if (fetchStatusChangeListener == null) return this;
|
||||
fetchStatusChangeListeners.add(fetchStatusChangeListener);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void removeFetchStatusListener(final FetchStatusChangeListener fetchStatusChangeListener) {
|
||||
if (fetchStatusChangeListener == null) return;
|
||||
fetchStatusChangeListeners.remove(fetchStatusChangeListener);
|
||||
}
|
||||
|
||||
private void dispatchFetchStatus() {
|
||||
for (final FetchStatusChangeListener listener : fetchStatusChangeListeners) {
|
||||
listener.onFetchStatusChange(isFetching());
|
||||
}
|
||||
}
|
||||
|
||||
public PostsLayoutPreferences getLayoutPreferences() {
|
||||
return layoutPreferences;
|
||||
}
|
||||
|
||||
public interface FetchStatusChangeListener {
|
||||
void onFetchStatusChange(boolean fetching);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
lifeCycleOwner = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchy;
|
||||
import com.facebook.drawee.generic.RoundingParams;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
|
||||
public final class ProfilePicView extends CircularImageView {
|
||||
private static final String TAG = "ProfilePicView";
|
||||
|
||||
private Size size;
|
||||
private int dimensionPixelSize;
|
||||
|
||||
public ProfilePicView(Context context, GenericDraweeHierarchy hierarchy) {
|
||||
super(context);
|
||||
setHierarchy(hierarchy);
|
||||
size = Size.REGULAR;
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
public ProfilePicView(final Context context) {
|
||||
super(context);
|
||||
size = Size.REGULAR;
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
public ProfilePicView(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
parseAttrs(context, attrs);
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
public ProfilePicView(final Context context,
|
||||
final AttributeSet attrs,
|
||||
final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
parseAttrs(context, attrs);
|
||||
}
|
||||
|
||||
private void parseAttrs(final Context context, final AttributeSet attrs) {
|
||||
final TypedArray a = context.getTheme().obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.ProfilePicView,
|
||||
0,
|
||||
0);
|
||||
try {
|
||||
final int sizeValue = a.getInt(R.styleable.ProfilePicView_size, Size.REGULAR.getValue());
|
||||
size = Size.valueOf(sizeValue);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLayout() {
|
||||
@DimenRes final int dimenRes;
|
||||
switch (size) {
|
||||
case SMALL:
|
||||
dimenRes = R.dimen.profile_pic_size_small;
|
||||
break;
|
||||
case TINY:
|
||||
dimenRes = R.dimen.profile_pic_size_tiny;
|
||||
break;
|
||||
case LARGE:
|
||||
dimenRes = R.dimen.profile_pic_size_large;
|
||||
break;
|
||||
default:
|
||||
case REGULAR:
|
||||
dimenRes = R.dimen.profile_pic_size_regular;
|
||||
break;
|
||||
}
|
||||
ViewGroup.LayoutParams layoutParams = getLayoutParams();
|
||||
if (layoutParams == null) {
|
||||
layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
dimensionPixelSize = getResources().getDimensionPixelSize(dimenRes);
|
||||
layoutParams.width = dimensionPixelSize;
|
||||
layoutParams.height = dimensionPixelSize;
|
||||
|
||||
// invalidate();
|
||||
// requestLayout();
|
||||
}
|
||||
|
||||
public void setStoriesBorder() {
|
||||
// private final int borderSize = 8;
|
||||
final int color = Color.GREEN;
|
||||
RoundingParams roundingParams = getHierarchy().getRoundingParams();
|
||||
if (roundingParams == null) {
|
||||
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);
|
||||
}
|
||||
roundingParams.setBorder(color, 5.0f);
|
||||
getHierarchy().setRoundingParams(roundingParams);
|
||||
}
|
||||
|
||||
public enum Size {
|
||||
TINY(0),
|
||||
SMALL(1),
|
||||
REGULAR(2),
|
||||
LARGE(3);
|
||||
|
||||
private final int value;
|
||||
private static Map<Integer, Size> map = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Size size : Size.values()) {
|
||||
map.put(size.value, size);
|
||||
}
|
||||
}
|
||||
|
||||
Size(final int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Size valueOf(final int value) {
|
||||
final Size size = map.get(value);
|
||||
return size != null ? size : Size.REGULAR;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(dimensionPixelSize, dimensionPixelSize);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -0,0 +1,302 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.ChangeTransform;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionListenerAdapter;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public abstract class SharedElementTransitionDialogFragment extends DialogFragment {
|
||||
// private static final String TAG = "SETDialogFragment";
|
||||
private static final int DURATION = 200;
|
||||
|
||||
private final Map<Integer, View> startViews = new HashMap<>();
|
||||
private final Map<Integer, View> destViews = new HashMap<>();
|
||||
private final Map<Integer, ViewBounds> viewBoundsMap = new HashMap<>();
|
||||
private final List<Animator> additionalAnimators = new ArrayList<>();
|
||||
private final Handler initialBoundsHandler = new Handler();
|
||||
|
||||
private boolean startCalled = false;
|
||||
private boolean startInitiated = false;
|
||||
private int boundsCalculatedCount = 0;
|
||||
|
||||
protected int getAnimationDuration() {
|
||||
return DURATION;
|
||||
}
|
||||
|
||||
public void addSharedElement(@NonNull final View startView, @NonNull final View destView) {
|
||||
final int key = destView.hashCode();
|
||||
startViews.put(key, startView);
|
||||
destViews.put(key, destView);
|
||||
initialBoundsHandler.post(() -> setupInitialBounds(startView, destView));
|
||||
}
|
||||
|
||||
public void startPostponedEnterTransition() {
|
||||
startCalled = true;
|
||||
if (startInitiated) return;
|
||||
if (boundsCalculatedCount < startViews.size()) return;
|
||||
startInitiated = true;
|
||||
final Set<Integer> keySet = startViews.keySet();
|
||||
final View view = getView();
|
||||
if (!(view instanceof ViewGroup)) return;
|
||||
final TransitionSet transitionSet = new TransitionSet()
|
||||
.setDuration(DURATION)
|
||||
.setInterpolator(new AccelerateDecelerateInterpolator())
|
||||
.addTransition(new ChangeBounds())
|
||||
.addTransition(new ChangeTransform())
|
||||
.addListener(new TransitionListenerAdapter() {
|
||||
@Override
|
||||
public void onTransitionStart(@NonNull final Transition transition) {
|
||||
for (Animator animator : additionalAnimators) {
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionEnd(@NonNull final Transition transition) {
|
||||
for (final Integer key : keySet) {
|
||||
final View startView = startViews.get(key);
|
||||
final View destView = destViews.get(key);
|
||||
final ViewBounds viewBounds = viewBoundsMap.get(key);
|
||||
onEndSharedElementAnimation(startView, destView, viewBounds);
|
||||
}
|
||||
}
|
||||
});
|
||||
view.post(() -> {
|
||||
TransitionManager.beginDelayedTransition((ViewGroup) view, transitionSet);
|
||||
for (final Integer key : keySet) {
|
||||
final View startView = startViews.get(key);
|
||||
final View destView = destViews.get(key);
|
||||
final ViewBounds viewBounds = viewBoundsMap.get(key);
|
||||
onBeforeSharedElementAnimation(startView, destView, viewBounds);
|
||||
setDestBounds(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setDestBounds(final int key) {
|
||||
final View startView = startViews.get(key);
|
||||
if (startView == null) return;
|
||||
final View destView = destViews.get(key);
|
||||
if (destView == null) return;
|
||||
final ViewBounds viewBounds = viewBoundsMap.get(key);
|
||||
if (viewBounds == null) return;
|
||||
destView.setX((int) viewBounds.getDestX());
|
||||
destView.setY((int) viewBounds.getDestY());
|
||||
destView.setTranslationX(0);
|
||||
destView.setTranslationY(0);
|
||||
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams();
|
||||
layoutParams.height = viewBounds.getDestHeight();
|
||||
layoutParams.width = viewBounds.getDestWidth();
|
||||
destView.requestLayout();
|
||||
}
|
||||
|
||||
protected void onBeforeSharedElementAnimation(@NonNull final View startView,
|
||||
@NonNull final View destView,
|
||||
@NonNull final ViewBounds viewBounds) {}
|
||||
|
||||
protected void onEndSharedElementAnimation(@NonNull final View startView,
|
||||
@NonNull final View destView,
|
||||
@NonNull final ViewBounds viewBounds) {}
|
||||
|
||||
private void setupInitialBounds(@NonNull final View startView, @NonNull final View destView) {
|
||||
final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
|
||||
private boolean firstPassDone;
|
||||
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
destView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
if (!firstPassDone) {
|
||||
getViewBounds(startView, destView, this);
|
||||
firstPassDone = true;
|
||||
return false;
|
||||
}
|
||||
final int[] location = new int[2];
|
||||
startView.getLocationOnScreen(location);
|
||||
final int initX = location[0];
|
||||
final int initY = location[1];
|
||||
destView.setX(initX);
|
||||
destView.setY(initY - Utils.getStatusBarHeight(getContext()));
|
||||
destView.requestLayout();
|
||||
boundsCalculatedCount++;
|
||||
if (startCalled) {
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
|
||||
}
|
||||
|
||||
private void getViewBounds(@NonNull final View startView,
|
||||
@NonNull final View destView,
|
||||
@NonNull final ViewTreeObserver.OnPreDrawListener preDrawListener) {
|
||||
final ViewBounds viewBounds = new ViewBounds();
|
||||
viewBounds.setDestWidth(destView.getWidth());
|
||||
viewBounds.setDestHeight(destView.getHeight());
|
||||
viewBounds.setDestX(destView.getX());
|
||||
viewBounds.setDestY(destView.getY());
|
||||
|
||||
final Rect destBounds = new Rect();
|
||||
destView.getDrawingRect(destBounds);
|
||||
viewBounds.setDestBounds(destBounds);
|
||||
|
||||
final ViewGroup.LayoutParams layoutParams = destView.getLayoutParams();
|
||||
|
||||
final Rect startBounds = new Rect();
|
||||
startView.getDrawingRect(startBounds);
|
||||
viewBounds.setStartBounds(startBounds);
|
||||
|
||||
final int key = destView.hashCode();
|
||||
viewBoundsMap.put(key, viewBounds);
|
||||
|
||||
layoutParams.height = startView.getHeight();
|
||||
layoutParams.width = startView.getWidth();
|
||||
|
||||
destView.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
|
||||
destView.requestLayout();
|
||||
}
|
||||
|
||||
// private void animateBounds(@NonNull final View startView,
|
||||
// @NonNull final View destView,
|
||||
// @NonNull final ViewBounds viewBounds) {
|
||||
// final ValueAnimator heightAnimator = ObjectAnimator.ofInt(startView.getHeight(), viewBounds.getDestHeight());
|
||||
// final ValueAnimator widthAnimator = ObjectAnimator.ofInt(startView.getWidth(), viewBounds.getDestWidth());
|
||||
// heightAnimator.setDuration(DURATION);
|
||||
// widthAnimator.setDuration(DURATION);
|
||||
// additionalAnimators.add(heightAnimator);
|
||||
// additionalAnimators.add(widthAnimator);
|
||||
// heightAnimator.addUpdateListener(animation -> {
|
||||
// ViewGroup.LayoutParams params = destView.getLayoutParams();
|
||||
// params.height = (int) animation.getAnimatedValue();
|
||||
// destView.requestLayout();
|
||||
// });
|
||||
// widthAnimator.addUpdateListener(animation -> {
|
||||
// ViewGroup.LayoutParams params = destView.getLayoutParams();
|
||||
// params.width = (int) animation.getAnimatedValue();
|
||||
// destView.requestLayout();
|
||||
// });
|
||||
// onBeforeSharedElementAnimation(startView, destView, viewBounds);
|
||||
// final float destX = viewBounds.getDestX();
|
||||
// final float destY = viewBounds.getDestY();
|
||||
// final AnimatorSet animatorSet = new AnimatorSet();
|
||||
// animatorSet.addListener(new AnimatorListenerAdapter() {
|
||||
// @Override
|
||||
// public void onAnimationEnd(final Animator animation) {
|
||||
// animationEnded(startView, destView, viewBounds);
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// destView.animate()
|
||||
// .x(destX)
|
||||
// .y(destY)
|
||||
// .setDuration(DURATION)
|
||||
// .withStartAction(() -> {
|
||||
// if (!additionalAnimatorsStarted && additionalAnimators.size() > 0) {
|
||||
// additionalAnimatorsStarted = true;
|
||||
// animatorSet.playTogether(additionalAnimators);
|
||||
// animatorSet.start();
|
||||
// }
|
||||
// })
|
||||
// .withEndAction(() -> animationEnded(startView, destView, viewBounds))
|
||||
// .start();
|
||||
// }
|
||||
|
||||
// private int endCount = 0;
|
||||
// private void animationEnded(final View startView, final View destView, final ViewBounds viewBounds) {
|
||||
// ++endCount;
|
||||
// if (endCount != startViews.size() + 1) return;
|
||||
// onEndSharedElementAnimation(startView, destView, viewBounds);
|
||||
// }
|
||||
|
||||
protected void addAnimator(@NonNull final Animator animator) {
|
||||
additionalAnimators.add(animator);
|
||||
}
|
||||
|
||||
protected static class ViewBounds {
|
||||
private float destY;
|
||||
private float destX;
|
||||
private int destHeight;
|
||||
private int destWidth;
|
||||
private Rect startBounds;
|
||||
private Rect destBounds;
|
||||
|
||||
public ViewBounds() {}
|
||||
|
||||
public float getDestY() {
|
||||
return destY;
|
||||
}
|
||||
|
||||
public void setDestY(final float destY) {
|
||||
this.destY = destY;
|
||||
}
|
||||
|
||||
public float getDestX() {
|
||||
return destX;
|
||||
}
|
||||
|
||||
public void setDestX(final float destX) {
|
||||
this.destX = destX;
|
||||
}
|
||||
|
||||
public int getDestHeight() {
|
||||
return destHeight;
|
||||
}
|
||||
|
||||
public void setDestHeight(final int destHeight) {
|
||||
this.destHeight = destHeight;
|
||||
}
|
||||
|
||||
public int getDestWidth() {
|
||||
return destWidth;
|
||||
}
|
||||
|
||||
public void setDestWidth(final int destWidth) {
|
||||
this.destWidth = destWidth;
|
||||
}
|
||||
|
||||
public Rect getStartBounds() {
|
||||
return startBounds;
|
||||
}
|
||||
|
||||
public void setStartBounds(final Rect startBounds) {
|
||||
this.startBounds = startBounds;
|
||||
}
|
||||
|
||||
public Rect getDestBounds() {
|
||||
return destBounds;
|
||||
}
|
||||
|
||||
public void setDestBounds(final Rect destBounds) {
|
||||
this.destBounds = destBounds;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
startViews.clear();
|
||||
destViews.clear();
|
||||
viewBoundsMap.clear();
|
||||
additionalAnimators.clear();
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class VerticalDragHelper {
|
||||
private static final String TAG = "VerticalDragHelper";
|
||||
private static final float PIXELS_PER_SECOND = 10;
|
||||
|
||||
private final View view;
|
||||
|
||||
private GestureDetector gestureDetector;
|
||||
private Context context;
|
||||
private float flingVelocity;
|
||||
private OnVerticalDragListener onVerticalDragListener;
|
||||
|
||||
private final GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
view.performClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) {
|
||||
float maxFlingVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
|
||||
float velocityPercentY = velocityY / maxFlingVelocity;
|
||||
float normalizedVelocityY = velocityPercentY * PIXELS_PER_SECOND;
|
||||
if (Math.abs(normalizedVelocityY) > 4) {
|
||||
flingVelocity = normalizedVelocityY;
|
||||
}
|
||||
return super.onFling(e1, e2, velocityX, velocityY);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
private final GestureDetector.OnGestureListener dragPreventionGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||
float prevDistanceY = 0;
|
||||
|
||||
@Override
|
||||
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) {
|
||||
Log.d(TAG, "onScroll: distanceX: " + distanceX + ", distanceY: " + distanceY);
|
||||
return super.onScroll(e1, e2, distanceX, distanceY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(final MotionEvent e) {
|
||||
Log.d(TAG, "onSingleTapUp");
|
||||
return super.onSingleTapUp(e);
|
||||
}
|
||||
};
|
||||
|
||||
private float prevRawY;
|
||||
private boolean isDragging;
|
||||
private float prevRawX;
|
||||
private float dX;
|
||||
private float prevDY;
|
||||
private GestureDetector dragPreventionGestureDetector;
|
||||
|
||||
public VerticalDragHelper(@NonNull final View view) {
|
||||
this.view = view;
|
||||
final Context context = view.getContext();
|
||||
if (context == null) return;
|
||||
this.context = context;
|
||||
init();
|
||||
}
|
||||
|
||||
public void setOnVerticalDragListener(@NonNull final OnVerticalDragListener onVerticalDragListener) {
|
||||
this.onVerticalDragListener = onVerticalDragListener;
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
gestureDetector = new GestureDetector(context, gestureListener);
|
||||
dragPreventionGestureDetector = new GestureDetector(context, dragPreventionGestureListener);
|
||||
}
|
||||
|
||||
public boolean onDragTouch(final MotionEvent event) {
|
||||
if (onVerticalDragListener == null) {
|
||||
return false;
|
||||
}
|
||||
// dragPreventionGestureDetector.onTouchEvent(event);
|
||||
if (gestureDetector.onTouchEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
boolean handled = false;
|
||||
final float rawY = event.getRawY();
|
||||
final float dY = rawY - prevRawY;
|
||||
if (!isDragging) {
|
||||
final float rawX = event.getRawX();
|
||||
if (prevRawX != 0) {
|
||||
dX = rawX - prevRawX;
|
||||
}
|
||||
prevRawX = rawX;
|
||||
if (prevRawY != 0) {
|
||||
final float dYAbs = Math.abs(dY - prevDY);
|
||||
if (!isDragging && dYAbs < 50) {
|
||||
final float abs = Math.abs(dY) - Math.abs(dX);
|
||||
if (abs > 0) {
|
||||
isDragging = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isDragging) {
|
||||
final ViewParent parent = view.getParent();
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
onVerticalDragListener.onDrag(dY);
|
||||
handled = true;
|
||||
}
|
||||
prevDY = dY;
|
||||
prevRawY = rawY;
|
||||
return handled;
|
||||
case MotionEvent.ACTION_UP:
|
||||
// Log.d(TAG, "onDragTouch: reset prevRawY");
|
||||
prevRawY = 0;
|
||||
if (flingVelocity != 0) {
|
||||
onVerticalDragListener.onFling(flingVelocity);
|
||||
flingVelocity = 0;
|
||||
isDragging = false;
|
||||
return true;
|
||||
}
|
||||
if (isDragging) {
|
||||
onVerticalDragListener.onDragEnd();
|
||||
isDragging = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDragging() {
|
||||
return isDragging;
|
||||
}
|
||||
|
||||
public boolean onGestureTouchEvent(final MotionEvent event) {
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private final static int DIRECTION_UP = 0;
|
||||
private final static int DIRECTION_DOWN = 1;
|
||||
float prevY = -1;
|
||||
int edgeHitCount = 0;
|
||||
float prevDirection = -1;
|
||||
|
||||
|
||||
// private boolean shouldPreventDrag(final MotionEvent event) {
|
||||
// switch (event.getAction()) {
|
||||
// case MotionEvent.ACTION_DOWN:
|
||||
// if (!firstDrag) {
|
||||
// firstDrag = true;
|
||||
// }
|
||||
// return false;
|
||||
// case MotionEvent.ACTION_MOVE:
|
||||
// float y = event.getY();
|
||||
// int direction = -2;
|
||||
// if (prevY != -1) {
|
||||
// final float dy = y - prevY;
|
||||
// // Log.d(TAG, "shouldPreventDrag: dy: " + dy);
|
||||
// if (dy > 0) {
|
||||
// direction = DIRECTION_DOWN;
|
||||
// // move direction is down
|
||||
// } else {
|
||||
// direction = DIRECTION_UP;
|
||||
// // move direction is up
|
||||
// }
|
||||
// }
|
||||
// prevY = y;
|
||||
// if (prevDirection == direction) {
|
||||
// edgeHitCount++;
|
||||
// } else {
|
||||
// edgeHitCount = 1;
|
||||
// }
|
||||
// if (edgeHitCount >= 2) {
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
public interface OnVerticalDragListener {
|
||||
void onDrag(final float dY);
|
||||
|
||||
void onDragEnd();
|
||||
|
||||
void onFling(final float flingVelocity);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback {
|
||||
@Override
|
||||
public void onThumbnailLoaded() {}
|
||||
|
||||
@Override
|
||||
public void onThumbnailClick() {}
|
||||
|
||||
@Override
|
||||
public void onPlayerViewLoaded() {}
|
||||
|
||||
@Override
|
||||
public void onPlay() {}
|
||||
|
||||
@Override
|
||||
public void onPause() {}
|
||||
}
|
@ -0,0 +1,373 @@
|
||||
package awais.instagrabber.customviews;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.audio.AudioListener;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
||||
import static com.google.android.exoplayer2.Player.STATE_ENDED;
|
||||
import static com.google.android.exoplayer2.Player.STATE_IDLE;
|
||||
import static com.google.android.exoplayer2.Player.STATE_READY;
|
||||
|
||||
public class VideoPlayerViewHelper implements Player.EventListener {
|
||||
private static final String TAG = "VideoPlayerViewHelper";
|
||||
|
||||
private final Context context;
|
||||
private final awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding binding;
|
||||
private final float initialVolume;
|
||||
private final float thumbnailAspectRatio;
|
||||
private final String thumbnailUrl;
|
||||
private final awais.instagrabber.databinding.LayoutExoCustomControlsBinding controlsBinding;
|
||||
private final VideoPlayerCallback videoPlayerCallback;
|
||||
private final String videoUrl;
|
||||
private final DefaultDataSourceFactory dataSourceFactory;
|
||||
private SimpleExoPlayer player;
|
||||
private PopupMenu speedPopup;
|
||||
|
||||
public VideoPlayerViewHelper(@NonNull final Context context,
|
||||
@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
||||
@NonNull final String videoUrl,
|
||||
final float initialVolume,
|
||||
final float thumbnailAspectRatio,
|
||||
final String thumbnailUrl,
|
||||
final LayoutExoCustomControlsBinding controlsBinding,
|
||||
final VideoPlayerCallback videoPlayerCallback) {
|
||||
this.context = context;
|
||||
this.binding = binding;
|
||||
this.initialVolume = initialVolume;
|
||||
this.thumbnailAspectRatio = thumbnailAspectRatio;
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
this.controlsBinding = controlsBinding;
|
||||
this.videoPlayerCallback = videoPlayerCallback;
|
||||
this.videoUrl = videoUrl;
|
||||
this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram");
|
||||
bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
binding.thumbnailParent.setOnClickListener(v -> {
|
||||
if (videoPlayerCallback != null) {
|
||||
videoPlayerCallback.onThumbnailClick();
|
||||
}
|
||||
loadPlayer();
|
||||
});
|
||||
setThumbnail();
|
||||
setupControls();
|
||||
}
|
||||
|
||||
private void setThumbnail() {
|
||||
binding.thumbnail.setAspectRatio(thumbnailAspectRatio);
|
||||
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
|
||||
.build();
|
||||
final DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setControllerListener(new BaseControllerListener<ImageInfo>() {
|
||||
@Override
|
||||
public void onFailure(final String id, final Throwable throwable) {
|
||||
if (videoPlayerCallback != null) {
|
||||
videoPlayerCallback.onThumbnailLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalImageSet(final String id,
|
||||
final ImageInfo imageInfo,
|
||||
final Animatable animatable) {
|
||||
if (videoPlayerCallback != null) {
|
||||
videoPlayerCallback.onThumbnailLoaded();
|
||||
}
|
||||
}
|
||||
})
|
||||
.setImageRequest(thumbnailRequest)
|
||||
.build();
|
||||
binding.thumbnail.setController(controller);
|
||||
}
|
||||
|
||||
private void loadPlayer() {
|
||||
if (videoUrl == null) return;
|
||||
if (binding.root.getDisplayedChild() == 0) {
|
||||
binding.root.showNext();
|
||||
}
|
||||
if (videoPlayerCallback != null) {
|
||||
videoPlayerCallback.onPlayerViewLoaded();
|
||||
}
|
||||
player = (SimpleExoPlayer) binding.playerView.getPlayer();
|
||||
if (player != null) {
|
||||
player.release();
|
||||
}
|
||||
player = new SimpleExoPlayer.Builder(context)
|
||||
.setLooper(Looper.getMainLooper())
|
||||
.build();
|
||||
player.addListener(this);
|
||||
player.setVolume(initialVolume);
|
||||
player.setPlayWhenReady(true);
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(dataSourceFactory);
|
||||
final MediaItem mediaItem = MediaItem.fromUri(videoUrl);
|
||||
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
|
||||
player.setMediaSource(mediaSource);
|
||||
setupControls();
|
||||
player.prepare();
|
||||
binding.playerView.setPlayer(player);
|
||||
}
|
||||
|
||||
private void setupControls() {
|
||||
if (controlsBinding == null) return;
|
||||
binding.playerView.setUseController(false);
|
||||
if (player == null) {
|
||||
enableControls(false);
|
||||
controlsBinding.playPause.setEnabled(true);
|
||||
controlsBinding.playPause.setOnClickListener(v -> binding.thumbnailParent.performClick());
|
||||
return;
|
||||
}
|
||||
enableControls(true);
|
||||
final Handler handler = new Handler();
|
||||
final long initialDelay = 0;
|
||||
final long recurringDelay = 60;
|
||||
final Runnable positionChecker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handler.removeCallbacks(this);
|
||||
if (player == null) return;
|
||||
final long currentPosition = player.getCurrentPosition();
|
||||
final long duration = player.getDuration();
|
||||
if (duration == TIME_UNSET) {
|
||||
controlsBinding.timeline.setValueFrom(0);
|
||||
controlsBinding.timeline.setValueTo(0);
|
||||
controlsBinding.timeline.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
controlsBinding.timeline.setValue(Math.min(currentPosition, duration));
|
||||
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(currentPosition));
|
||||
handler.postDelayed(this, recurringDelay);
|
||||
}
|
||||
};
|
||||
updatePlayPauseDrawable(player.getPlayWhenReady());
|
||||
updateMuteIcon(player.getVolume());
|
||||
player.addListener(new Player.EventListener() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(final int state) {
|
||||
switch (state) {
|
||||
case Player.STATE_BUFFERING:
|
||||
case STATE_IDLE:
|
||||
case STATE_ENDED:
|
||||
handler.removeCallbacks(positionChecker);
|
||||
return;
|
||||
case STATE_READY:
|
||||
setupTimeline();
|
||||
handler.postDelayed(positionChecker, initialDelay);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
|
||||
updatePlayPauseDrawable(playWhenReady);
|
||||
}
|
||||
});
|
||||
player.addAudioListener(new AudioListener() {
|
||||
@Override
|
||||
public void onVolumeChanged(final float volume) {
|
||||
updateMuteIcon(volume);
|
||||
}
|
||||
});
|
||||
controlsBinding.timeline.addOnChangeListener((slider, value, fromUser) -> {
|
||||
if (!fromUser) return;
|
||||
long actualValue = (long) value;
|
||||
if (actualValue < 0) {
|
||||
actualValue = 0;
|
||||
} else if (actualValue > player.getDuration()) {
|
||||
actualValue = player.getDuration();
|
||||
}
|
||||
player.seekTo(actualValue);
|
||||
});
|
||||
controlsBinding.timeline.setLabelFormatter(value -> TextUtils.millisToTimeString((long) value));
|
||||
controlsBinding.playPause.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
|
||||
controlsBinding.mute.setOnClickListener(v -> toggleMute());
|
||||
controlsBinding.rewWithAmount.setOnClickListener(v -> {
|
||||
final long positionMs = player.getCurrentPosition() - 5000;
|
||||
player.seekTo(positionMs < 0 ? 0 : positionMs);
|
||||
});
|
||||
controlsBinding.ffWithAmount.setOnClickListener(v -> {
|
||||
long positionMs = player.getCurrentPosition() + 5000;
|
||||
long duration = player.getDuration();
|
||||
if (duration == TIME_UNSET) {
|
||||
duration = 0;
|
||||
}
|
||||
player.seekTo(Math.min(positionMs, duration));
|
||||
});
|
||||
controlsBinding.speed.setOnClickListener(this::showMenu);
|
||||
}
|
||||
|
||||
private void setupTimeline() {
|
||||
final long duration = player.getDuration();
|
||||
controlsBinding.timeline.setEnabled(true);
|
||||
controlsBinding.timeline.setValueFrom(0);
|
||||
controlsBinding.timeline.setValueTo(duration);
|
||||
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0));
|
||||
controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration));
|
||||
}
|
||||
|
||||
private void enableControls(final boolean enable) {
|
||||
controlsBinding.speed.setEnabled(enable);
|
||||
controlsBinding.mute.setEnabled(enable);
|
||||
controlsBinding.ffWithAmount.setEnabled(enable);
|
||||
controlsBinding.rewWithAmount.setEnabled(enable);
|
||||
controlsBinding.fromTime.setEnabled(enable);
|
||||
controlsBinding.toTime.setEnabled(enable);
|
||||
controlsBinding.playPause.setEnabled(enable);
|
||||
}
|
||||
|
||||
public void showMenu(View anchor) {
|
||||
PopupMenu popup = getPopupMenu(anchor);
|
||||
popup.show();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PopupMenu getPopupMenu(final View anchor) {
|
||||
if (speedPopup != null) {
|
||||
return speedPopup;
|
||||
}
|
||||
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle);
|
||||
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer);
|
||||
speedPopup = new PopupMenu(themeWrapper, anchor);
|
||||
speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu());
|
||||
speedPopup.setOnMenuItemClickListener(item -> {
|
||||
float nextSpeed;
|
||||
int textResId;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.pt_two_five_x:
|
||||
nextSpeed = 0.25f;
|
||||
textResId = R.string.pt_two_five_x;
|
||||
break;
|
||||
case R.id.pt_five_x:
|
||||
nextSpeed = 0.5f;
|
||||
textResId = R.string.pt_five_x;
|
||||
break;
|
||||
case R.id.pt_seven_five_x:
|
||||
nextSpeed = 0.75f;
|
||||
textResId = R.string.pt_seven_five_x;
|
||||
break;
|
||||
case R.id.one_x:
|
||||
nextSpeed = 1f;
|
||||
textResId = R.string.one_x;
|
||||
break;
|
||||
case R.id.one_pt_two_five_x:
|
||||
nextSpeed = 1.25f;
|
||||
textResId = R.string.one_pt_two_five_x;
|
||||
break;
|
||||
case R.id.one_pt_five_x:
|
||||
nextSpeed = 1.5f;
|
||||
textResId = R.string.one_pt_five_x;
|
||||
break;
|
||||
case R.id.two_x:
|
||||
nextSpeed = 2f;
|
||||
textResId = R.string.two_x;
|
||||
break;
|
||||
default:
|
||||
nextSpeed = 1;
|
||||
textResId = R.string.one_x;
|
||||
}
|
||||
player.setPlaybackParameters(new PlaybackParameters(nextSpeed));
|
||||
controlsBinding.speed.setText(textResId);
|
||||
return true;
|
||||
});
|
||||
return speedPopup;
|
||||
}
|
||||
|
||||
private void updateMuteIcon(final float volume) {
|
||||
if (volume == 0) {
|
||||
controlsBinding.mute.setIconResource(R.drawable.ic_volume_off_24);
|
||||
return;
|
||||
}
|
||||
controlsBinding.mute.setIconResource(R.drawable.ic_volume_up_24);
|
||||
}
|
||||
|
||||
private void updatePlayPauseDrawable(final boolean playWhenReady) {
|
||||
if (playWhenReady) {
|
||||
controlsBinding.playPause.setIconResource(R.drawable.ic_pause_24);
|
||||
return;
|
||||
}
|
||||
controlsBinding.playPause.setIconResource(R.drawable.ic_play_arrow_24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
|
||||
if (videoPlayerCallback == null) return;
|
||||
if (playWhenReady) {
|
||||
videoPlayerCallback.onPlay();
|
||||
return;
|
||||
}
|
||||
videoPlayerCallback.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(final ExoPlaybackException error) {
|
||||
Log.e(TAG, "onPlayerError", error);
|
||||
}
|
||||
|
||||
public float toggleMute() {
|
||||
if (player == null) return 0;
|
||||
final float vol = player.getVolume() == 0f ? 1f : 0f;
|
||||
player.setVolume(vol);
|
||||
return vol;
|
||||
}
|
||||
|
||||
public void togglePlayback() {
|
||||
if (player == null) return;
|
||||
final int playbackState = player.getPlaybackState();
|
||||
if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) return;
|
||||
final boolean playWhenReady = player.getPlayWhenReady();
|
||||
player.setPlayWhenReady(!playWhenReady);
|
||||
}
|
||||
|
||||
public void releasePlayer() {
|
||||
if (player == null) return;
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if (player == null) return;
|
||||
player.pause();
|
||||
}
|
||||
|
||||
public interface VideoPlayerCallback {
|
||||
void onThumbnailLoaded();
|
||||
|
||||
void onThumbnailClick();
|
||||
|
||||
void onPlayerViewLoaded();
|
||||
|
||||
void onPlay();
|
||||
|
||||
void onPause();
|
||||
}
|
||||
}
|
@ -572,16 +572,16 @@ public class DefaultZoomableController
|
||||
RectF b = mTempRect;
|
||||
b.set(mImageBounds);
|
||||
transform.mapRect(b);
|
||||
float offsetLeft =
|
||||
shouldLimit(limitTypes, LIMIT_TRANSLATION_X)
|
||||
? getOffset(
|
||||
b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
|
||||
: 0;
|
||||
float offsetTop =
|
||||
shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
|
||||
? getOffset(
|
||||
b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
|
||||
: 0;
|
||||
final boolean shouldLimitX = shouldLimit(limitTypes, LIMIT_TRANSLATION_X);
|
||||
float offsetLeft = shouldLimitX
|
||||
? getOffset(b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
|
||||
: 0;
|
||||
float offsetTop = shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
|
||||
? getOffset(b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
|
||||
: 0;
|
||||
if (mListener != null) {
|
||||
mListener.onTranslationLimited(offsetLeft, offsetTop);
|
||||
}
|
||||
if (offsetLeft != 0 || offsetTop != 0) {
|
||||
transform.postTranslate(offsetLeft, offsetTop);
|
||||
return true;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ public interface ZoomableController {
|
||||
* @param transform the current transform matrix
|
||||
*/
|
||||
void onTransformEnd(Matrix transform);
|
||||
|
||||
void onTranslationLimited(float offsetLeft, float offsetTop);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,8 +33,6 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
|
||||
import com.facebook.drawee.interfaces.DraweeController;
|
||||
import com.facebook.drawee.view.DraweeView;
|
||||
|
||||
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
|
||||
|
||||
|
||||
/**
|
||||
* DraweeView that has zoomable capabilities.
|
||||
@ -54,7 +52,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
|
||||
private DraweeController mHugeImageController;
|
||||
private ZoomableController mZoomableController;
|
||||
private GestureDetector mTapGestureDetector;
|
||||
private boolean mAllowTouchInterceptionWhileZoomed = true;
|
||||
private boolean mAllowTouchInterceptionWhileZoomed = false;
|
||||
|
||||
private boolean mIsDialtoneEnabled = false;
|
||||
private boolean mZoomingEnabled = true;
|
||||
@ -76,7 +74,9 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
|
||||
private final ZoomableController.Listener mZoomableListener =
|
||||
new ZoomableController.Listener() {
|
||||
@Override
|
||||
public void onTransformBegin(Matrix transform) {}
|
||||
public void onTransformBegin(Matrix transform) {
|
||||
ZoomableDraweeView.this.onTransformBegin(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformChanged(Matrix transform) {
|
||||
@ -84,7 +84,14 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransformEnd(Matrix transform) {}
|
||||
public void onTransformEnd(Matrix transform) {
|
||||
ZoomableDraweeView.this.onTransformEnd(transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTranslationLimited(final float offsetLeft, final float offsetTop) {
|
||||
ZoomableDraweeView.this.onTranslationLimited(offsetLeft, offsetTop);
|
||||
}
|
||||
};
|
||||
|
||||
private final GestureListenerWrapper mTapListenerWrapper = new GestureListenerWrapper();
|
||||
@ -302,11 +309,10 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
|
||||
int a = event.getActionMasked();
|
||||
FLog.v(getLogTag(), "onTouchEvent: %d, view %x, received", a, this.hashCode());
|
||||
if (!mIsDialtoneEnabled && mTapGestureDetector.onTouchEvent(event)) {
|
||||
FLog.v(
|
||||
getLogTag(),
|
||||
"onTouchEvent: %d, view %x, handled by tap gesture detector",
|
||||
a,
|
||||
this.hashCode());
|
||||
FLog.v(getLogTag(),
|
||||
"onTouchEvent: %d, view %x, handled by tap gesture detector",
|
||||
a,
|
||||
this.hashCode());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -389,23 +395,29 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
|
||||
mZoomableController.setEnabled(false);
|
||||
}
|
||||
|
||||
protected void onTransformBegin(final Matrix transform) {}
|
||||
|
||||
protected void onTransformChanged(Matrix transform) {
|
||||
FLog.v(getLogTag(), "onTransformChanged: view %x, transform: %s", this.hashCode(), transform);
|
||||
maybeSetHugeImageController();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected void onTransformEnd(final Matrix transform) {}
|
||||
|
||||
protected void onTranslationLimited(final float offsetLeft, final float offsetTop) {}
|
||||
|
||||
protected void updateZoomableControllerBounds() {
|
||||
getImageBounds(mImageBounds);
|
||||
getLimitBounds(mViewBounds);
|
||||
// Log.d(TAG.getSimpleName(), "updateZoomableControllerBounds: mImageBounds: " + mImageBounds);
|
||||
mZoomableController.setImageBounds(mImageBounds);
|
||||
mZoomableController.setViewBounds(mViewBounds);
|
||||
FLog.v(
|
||||
getLogTag(),
|
||||
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
|
||||
this.hashCode(),
|
||||
mViewBounds,
|
||||
mImageBounds);
|
||||
FLog.v(getLogTag(),
|
||||
"updateZoomableControllerBounds: view %x, view bounds: %s, image bounds: %s",
|
||||
this.hashCode(),
|
||||
mViewBounds,
|
||||
mImageBounds);
|
||||
}
|
||||
|
||||
protected Class<?> getLogTag() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,23 +1,41 @@
|
||||
package awais.instagrabber.customviews.helpers;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import awais.instagrabber.interfaces.LazyLoadListener;
|
||||
|
||||
// thanks to nesquena's EndlessRecyclerViewScrollListener
|
||||
// https://gist.github.com/nesquena/d09dc68ff07e845cc622
|
||||
/**
|
||||
* thanks to nesquena's <a href="https://gist.github.com/nesquena/d09dc68ff07e845cc622">EndlessRecyclerViewScrollListener</a>
|
||||
*/
|
||||
public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
|
||||
private int currentPage = 0; // The current offset index of data you have loaded
|
||||
private int previousTotalItemCount = 0; // The total number of items in the dataset after the last load
|
||||
private boolean loading = true; // True if we are still waiting for the last set of data to load.
|
||||
private final int visibleThreshold; // The minimum amount of items to have below your current scroll position before loading more.
|
||||
/**
|
||||
* The current offset index of data you have loaded
|
||||
*/
|
||||
private int currentPage = 0;
|
||||
/**
|
||||
* The total number of items in the data set after the last load
|
||||
*/
|
||||
private int previousTotalItemCount = 0;
|
||||
/**
|
||||
* <code>true</code> if we are still waiting for the last set of data to load.
|
||||
*/
|
||||
private boolean loading = true;
|
||||
/**
|
||||
* The minimum amount of items to have below your current scroll position before loading more.
|
||||
*/
|
||||
private final int visibleThreshold;
|
||||
private final LazyLoadListener lazyLoadListener;
|
||||
private final RecyclerView.LayoutManager layoutManager;
|
||||
|
||||
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener, final int threshold) {
|
||||
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
|
||||
final LazyLoadListener lazyLoadListener,
|
||||
final int threshold) {
|
||||
this.layoutManager = layoutManager;
|
||||
this.lazyLoadListener = lazyLoadListener;
|
||||
if (threshold > 0) {
|
||||
@ -26,6 +44,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
|
||||
}
|
||||
if (layoutManager instanceof GridLayoutManager) {
|
||||
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount());
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
this.visibleThreshold = 4 * Math.max(3, ((StaggeredGridLayoutManager) layoutManager).getSpanCount());
|
||||
} else if (layoutManager instanceof LinearLayoutManager) {
|
||||
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8;
|
||||
} else {
|
||||
@ -33,7 +53,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
|
||||
}
|
||||
}
|
||||
|
||||
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener) {
|
||||
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager,
|
||||
final LazyLoadListener lazyLoadListener) {
|
||||
this(layoutManager, lazyLoadListener, -1);
|
||||
}
|
||||
|
||||
@ -52,22 +73,37 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
|
||||
previousTotalItemCount = totalItemCount;
|
||||
}
|
||||
|
||||
final int lastVisibleItemPosition;
|
||||
int lastVisibleItemPosition;
|
||||
if (layoutManager instanceof GridLayoutManager) {
|
||||
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager;
|
||||
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) this.layoutManager;
|
||||
final int spanCount = layoutManager.getSpanCount();
|
||||
final int[] lastVisibleItemPositions = layoutManager.findLastVisibleItemPositions(null);
|
||||
lastVisibleItemPosition = 0;
|
||||
for (final int itemPosition : lastVisibleItemPositions) {
|
||||
if (itemPosition > lastVisibleItemPosition) {
|
||||
lastVisibleItemPosition = itemPosition;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager;
|
||||
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
|
||||
}
|
||||
|
||||
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
|
||||
if (lazyLoadListener != null)
|
||||
lazyLoadListener.onLoadMore(++currentPage, totalItemCount);
|
||||
loading = true;
|
||||
if (lazyLoadListener != null) {
|
||||
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage, totalItemCount), 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
|
||||
public void resetState() {
|
||||
this.currentPage = 0;
|
||||
this.previousTotalItemCount = 0;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
|
||||
final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos);
|
||||
if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) {
|
||||
if (currentlyPlayingViewHolder != null) {
|
||||
currentlyPlayingViewHolder.stopPlaying();
|
||||
// currentlyPlayingViewHolder.stopPlaying();
|
||||
currentlyPlayingViewHolder = null;
|
||||
}
|
||||
return;
|
||||
@ -80,9 +80,9 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
|
||||
return;
|
||||
}
|
||||
if (currentlyPlayingViewHolder != null) {
|
||||
currentlyPlayingViewHolder.stopPlaying();
|
||||
// currentlyPlayingViewHolder.stopPlaying();
|
||||
}
|
||||
videoHolder.startPlaying();
|
||||
// videoHolder.startPlaying();
|
||||
currentlyPlayingViewHolder = videoHolder;
|
||||
}
|
||||
// boolean processFirstItem = false, processLastItem = false;
|
||||
@ -196,7 +196,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
|
||||
for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) {
|
||||
final View view = layoutManager.findViewByPosition(pos);
|
||||
if (view != null && view.getId() == R.id.videoHolder) {
|
||||
final View viewSwitcher = view.findViewById(R.id.view_switcher);
|
||||
final View viewSwitcher = view.findViewById(R.id.root);
|
||||
if (viewSwitcher == null) {
|
||||
continue;
|
||||
}
|
||||
@ -220,113 +220,113 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
|
||||
if (currentlyPlayingViewHolder == null) {
|
||||
return;
|
||||
}
|
||||
currentlyPlayingViewHolder.startPlaying();
|
||||
// currentlyPlayingViewHolder.startPlaying();
|
||||
}
|
||||
|
||||
public void stopPlaying() {
|
||||
if (currentlyPlayingViewHolder == null) {
|
||||
return;
|
||||
}
|
||||
currentlyPlayingViewHolder.stopPlaying();
|
||||
// currentlyPlayingViewHolder.stopPlaying();
|
||||
}
|
||||
|
||||
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// synchronized (LOCK) {
|
||||
// if (recyclerView != null) {
|
||||
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
|
||||
// if (adapter instanceof FeedAdapter) {
|
||||
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
|
||||
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
|
||||
// }
|
||||
// }
|
||||
// if (itemView == null) {
|
||||
// return;
|
||||
// }
|
||||
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
|
||||
// final FeedModel feedModel = feedModels.get(itemPos);
|
||||
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
|
||||
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
|
||||
// if (playerView == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (player != null) {
|
||||
// player.stop(true);
|
||||
// player.release();
|
||||
// player = null;
|
||||
// }
|
||||
//
|
||||
// player = new SimpleExoPlayer.Builder(context)
|
||||
// .setUseLazyPreparation(!shouldAutoplay)
|
||||
// .build();
|
||||
// player.setPlayWhenReady(shouldAutoplay);
|
||||
//
|
||||
// final View btnComments = itemView.findViewById(R.id.btnComments);
|
||||
// if (btnComments != null) {
|
||||
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
|
||||
// else {
|
||||
// btnComments.setTag(feedModel);
|
||||
// btnComments.setEnabled(true);
|
||||
// btnComments.setOnClickListener(commentClickListener);
|
||||
// }
|
||||
// }
|
||||
// playerView.setPlayer(player);
|
||||
// btnMute = itemView.findViewById(R.id.btnMute);
|
||||
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
||||
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
|
||||
// player.setVolume(vol);
|
||||
//
|
||||
// if (btnMute != null) {
|
||||
// btnMute.setVisibility(View.VISIBLE);
|
||||
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
|
||||
// btnMute.setOnClickListener(muteClickListener);
|
||||
// }
|
||||
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
|
||||
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
|
||||
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
|
||||
//
|
||||
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
// player.prepare(mediaSource);
|
||||
// player.setVolume(vol);
|
||||
//
|
||||
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
|
||||
//
|
||||
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
|
||||
// }
|
||||
//
|
||||
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// // Log.d("AWAISKING_APP", "release: " + itemPos);
|
||||
// // if (player != null) {
|
||||
// // player.stop(true);
|
||||
// // player.release();
|
||||
// // }
|
||||
// // player = null;
|
||||
// }
|
||||
//
|
||||
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// // if (player != null) {
|
||||
// // final int playbackState = player.getPlaybackState();
|
||||
// // if (!player.isPlaying()
|
||||
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
|
||||
// // ) {
|
||||
// // player.setPlayWhenReady(true);
|
||||
// // }
|
||||
// // }
|
||||
// // if (player != null) {
|
||||
// // player.setPlayWhenReady(true);
|
||||
// // player.getPlaybackState();
|
||||
// // }
|
||||
// }
|
||||
//
|
||||
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// if (player != null) {
|
||||
// player.setPlayWhenReady(false);
|
||||
// player.getPlaybackState();
|
||||
// }
|
||||
// }
|
||||
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// synchronized (LOCK) {
|
||||
// if (recyclerView != null) {
|
||||
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
|
||||
// if (adapter instanceof FeedAdapter) {
|
||||
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
|
||||
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
|
||||
// }
|
||||
// }
|
||||
// if (itemView == null) {
|
||||
// return;
|
||||
// }
|
||||
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
|
||||
// final FeedModel feedModel = feedModels.get(itemPos);
|
||||
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
|
||||
// final PlayerView playerView = itemView.findViewById(R.id.playerView);
|
||||
// if (playerView == null) {
|
||||
// return;
|
||||
// }
|
||||
// if (player != null) {
|
||||
// player.stop(true);
|
||||
// player.release();
|
||||
// player = null;
|
||||
// }
|
||||
//
|
||||
// player = new SimpleExoPlayer.Builder(context)
|
||||
// .setUseLazyPreparation(!shouldAutoplay)
|
||||
// .build();
|
||||
// player.setPlayWhenReady(shouldAutoplay);
|
||||
//
|
||||
// final View btnComments = itemView.findViewById(R.id.btnComments);
|
||||
// if (btnComments != null) {
|
||||
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
|
||||
// else {
|
||||
// btnComments.setTag(feedModel);
|
||||
// btnComments.setEnabled(true);
|
||||
// btnComments.setOnClickListener(commentClickListener);
|
||||
// }
|
||||
// }
|
||||
// playerView.setPlayer(player);
|
||||
// btnMute = itemView.findViewById(R.id.btnMute);
|
||||
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
||||
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
|
||||
// player.setVolume(vol);
|
||||
//
|
||||
// if (btnMute != null) {
|
||||
// btnMute.setVisibility(View.VISIBLE);
|
||||
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
|
||||
// btnMute.setOnClickListener(muteClickListener);
|
||||
// }
|
||||
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
|
||||
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
|
||||
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
|
||||
//
|
||||
// player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
// player.prepare(mediaSource);
|
||||
// player.setVolume(vol);
|
||||
//
|
||||
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
|
||||
//
|
||||
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
|
||||
// }
|
||||
//
|
||||
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// // Log.d("AWAISKING_APP", "release: " + itemPos);
|
||||
// // if (player != null) {
|
||||
// // player.stop(true);
|
||||
// // player.release();
|
||||
// // }
|
||||
// // player = null;
|
||||
// }
|
||||
//
|
||||
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// // if (player != null) {
|
||||
// // final int playbackState = player.getPlaybackState();
|
||||
// // if (!player.isPlaying()
|
||||
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
|
||||
// // ) {
|
||||
// // player.setPlayWhenReady(true);
|
||||
// // }
|
||||
// // }
|
||||
// // if (player != null) {
|
||||
// // player.setPlayWhenReady(true);
|
||||
// // player.getPlaybackState();
|
||||
// // }
|
||||
// }
|
||||
//
|
||||
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
|
||||
// if (player != null) {
|
||||
// player.setPlayWhenReady(false);
|
||||
// player.getPlaybackState();
|
||||
// }
|
||||
// }
|
||||
|
||||
public interface VideoChangeCallback {
|
||||
void playerChanged(final int itemPos, final SimpleExoPlayer player);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
package awais.instagrabber.fragments.main;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@ -20,47 +20,25 @@ import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.facebook.common.executors.UiThreadImmediateExecutorService;
|
||||
import com.facebook.datasource.BaseDataSubscriber;
|
||||
import com.facebook.datasource.DataSource;
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.activities.MainActivity;
|
||||
import awais.instagrabber.adapters.FeedAdapter;
|
||||
import awais.instagrabber.adapters.FeedStoriesAdapter;
|
||||
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
|
||||
import awais.instagrabber.asyncs.FeedFetcher;
|
||||
import awais.instagrabber.customviews.RamboTextView;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
||||
import awais.instagrabber.asyncs.FeedPostFetchService;
|
||||
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
|
||||
import awais.instagrabber.databinding.FragmentFeedBinding;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
|
||||
import awais.instagrabber.fragments.PostViewV2Fragment;
|
||||
import awais.instagrabber.interfaces.MentionClickListener;
|
||||
import awais.instagrabber.models.BasePostModel;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.FeedStoryModel;
|
||||
import awais.instagrabber.models.PostModel;
|
||||
import awais.instagrabber.models.ProfileModel;
|
||||
import awais.instagrabber.models.ViewerPostModel;
|
||||
import awais.instagrabber.models.enums.DownloadMethod;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.DownloadUtils;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
|
||||
import awais.instagrabber.viewmodels.FeedViewModel;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
import awais.instagrabber.webservices.StoriesService;
|
||||
|
||||
@ -77,98 +55,101 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
private StoriesService storiesService;
|
||||
private boolean feedHasNextPage = false;
|
||||
private String feedEndCursor = null;
|
||||
private FeedViewModel feedViewModel;
|
||||
// private FeedViewModel feedViewModel;
|
||||
private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
|
||||
private boolean shouldRefresh = true;
|
||||
private boolean isPullToRefresh;
|
||||
private FeedStoriesViewModel feedStoriesViewModel;
|
||||
private StaggeredGridLayoutManager gridLayoutManager;
|
||||
private boolean storiesFetching;
|
||||
|
||||
private final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
|
||||
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
|
||||
@Override
|
||||
public void doBefore() {
|
||||
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(final FeedModel[] result) {
|
||||
if (result == null || result.length <= 0) {
|
||||
binding.feedSwipeRefreshLayout.setRefreshing(false);
|
||||
return;
|
||||
}
|
||||
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
|
||||
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
|
||||
for (final FeedModel feedModel : result) {
|
||||
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
|
||||
}
|
||||
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
|
||||
int success = 0;
|
||||
int failed = 0;
|
||||
|
||||
@Override
|
||||
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
|
||||
final Map<String, Object> extras = dataSource.getExtras();
|
||||
if (extras == null) return;
|
||||
final Uri thumbUri = (Uri) extras.get("uri_source");
|
||||
if (thumbUri == null) return;
|
||||
final Integer encodedWidth = (Integer) extras.get("encoded_width");
|
||||
final Integer encodedHeight = (Integer) extras.get("encoded_height");
|
||||
if (encodedWidth == null || encodedHeight == null) return;
|
||||
final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
|
||||
if (feedModel == null) return;
|
||||
int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
int resultingHeight = NumberUtils
|
||||
.getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
|
||||
if (feedModel
|
||||
.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
|
||||
// If its a video and the height is too large, need to reduce the height,
|
||||
// so that entire video fits on screen
|
||||
resultingHeight = RESIZED_VIDEO_HEIGHT;
|
||||
requiredWidth = NumberUtils.getResultingWidth(RESIZED_VIDEO_HEIGHT,
|
||||
resultingHeight,
|
||||
requiredWidth);
|
||||
}
|
||||
feedModel.setImageWidth(requiredWidth);
|
||||
feedModel.setImageHeight(resultingHeight);
|
||||
success++;
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
|
||||
failed++;
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
public void updateAdapter() {
|
||||
if (failed + success != result.length) return;
|
||||
List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
|
||||
? new ArrayList<>()
|
||||
: new ArrayList<>(currentFeedModelList);
|
||||
final List<FeedModel> resultList = Arrays.asList(result);
|
||||
if (isPullToRefresh) {
|
||||
finalList = resultList;
|
||||
isPullToRefresh = false;
|
||||
} else {
|
||||
finalList.addAll(resultList);
|
||||
}
|
||||
feedViewModel.getList().postValue(finalList);
|
||||
final PostModel feedPostModel = result[result.length - 1];
|
||||
if (feedPostModel != null) {
|
||||
feedEndCursor = feedPostModel.getEndCursor();
|
||||
feedHasNextPage = feedPostModel.hasNextPage();
|
||||
feedPostModel.setPageCursor(false, null);
|
||||
}
|
||||
binding.feedSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
for (final FeedModel feedModel : result) {
|
||||
final DataSource<Void> ds = Fresco.getImagePipeline()
|
||||
.prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
|
||||
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
|
||||
}
|
||||
}
|
||||
};
|
||||
// private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
|
||||
// @Override
|
||||
// public void doBefore() {
|
||||
// binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true));
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onResult(final FeedModel[] result) {
|
||||
// if (result == null || result.length <= 0) {
|
||||
// binding.feedSwipeRefreshLayout.setRefreshing(false);
|
||||
// return;
|
||||
// }
|
||||
// final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue();
|
||||
// final Map<String, FeedModel> thumbToFeedMap = new HashMap<>();
|
||||
// for (final FeedModel feedModel : result) {
|
||||
// thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel);
|
||||
// }
|
||||
// final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
|
||||
// int success = 0;
|
||||
// int failed = 0;
|
||||
//
|
||||
// @Override
|
||||
// protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) {
|
||||
// final Map<String, Object> extras = dataSource.getExtras();
|
||||
// if (extras == null) return;
|
||||
// final Uri thumbUri = (Uri) extras.get("uri_source");
|
||||
// if (thumbUri == null) return;
|
||||
// final Integer encodedWidth = (Integer) extras.get("encoded_width");
|
||||
// final Integer encodedHeight = (Integer) extras.get("encoded_height");
|
||||
// if (encodedWidth == null || encodedHeight == null) return;
|
||||
// final FeedModel feedModel = thumbToFeedMap.get(thumbUri.toString());
|
||||
// if (feedModel == null) return;
|
||||
// int requiredWidth = Utils.displayMetrics.widthPixels;
|
||||
// int resultingHeight = NumberUtils
|
||||
// .getResultingHeight(requiredWidth, encodedHeight, encodedWidth);
|
||||
// if (feedModel
|
||||
// .getItemType() == MediaItemType.MEDIA_TYPE_VIDEO && resultingHeight >= MAX_VIDEO_HEIGHT) {
|
||||
// // If its a video and the height is too large, need to reduce the height,
|
||||
// // so that entire video fits on screen
|
||||
// resultingHeight = RESIZED_VIDEO_HEIGHT;
|
||||
// requiredWidth = NumberUtils.getResultingWidth(RESIZED_VIDEO_HEIGHT,
|
||||
// resultingHeight,
|
||||
// requiredWidth);
|
||||
// }
|
||||
// feedModel.setImageWidth(requiredWidth);
|
||||
// feedModel.setImageHeight(resultingHeight);
|
||||
// success++;
|
||||
// updateAdapter();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) {
|
||||
// failed++;
|
||||
// updateAdapter();
|
||||
// }
|
||||
//
|
||||
// public void updateAdapter() {
|
||||
// if (failed + success != result.length) return;
|
||||
//
|
||||
// }
|
||||
// };
|
||||
// for (final FeedModel feedModel : result) {
|
||||
// final DataSource<Void> ds = Fresco.getImagePipeline()
|
||||
// .prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null);
|
||||
// ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance());
|
||||
// }
|
||||
// List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty()
|
||||
// ? new ArrayList<>()
|
||||
// : new ArrayList<>(currentFeedModelList);
|
||||
// final List<FeedModel> resultList = Arrays.asList(result);
|
||||
// if (isPullToRefresh) {
|
||||
// finalList = resultList;
|
||||
// isPullToRefresh = false;
|
||||
// } else {
|
||||
// finalList.addAll(resultList);
|
||||
// }
|
||||
// // feedViewModel.getList().postValue(finalList);
|
||||
// final PostModel feedPostModel = result[result.length - 1];
|
||||
// if (feedPostModel != null) {
|
||||
// feedEndCursor = feedPostModel.getEndCursor();
|
||||
// feedHasNextPage = feedPostModel.hasNextPage();
|
||||
// feedPostModel.setPageCursor(false, null);
|
||||
// }
|
||||
// binding.feedSwipeRefreshLayout.setRefreshing(false);
|
||||
// }
|
||||
// };
|
||||
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
|
||||
if (isHashtag) {
|
||||
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(text);
|
||||
@ -184,105 +165,105 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
NavHostFragment.findNavController(this).navigate(action);
|
||||
};
|
||||
private final View.OnClickListener postViewClickListener = v -> {
|
||||
final Object tag = v.getTag();
|
||||
if (!(tag instanceof FeedModel)) return;
|
||||
|
||||
final FeedModel feedModel = (FeedModel) tag;
|
||||
if (v instanceof RamboTextView) {
|
||||
if (feedModel.isMentionClicked()) feedModel.toggleCaption();
|
||||
feedModel.setMentionClicked(false);
|
||||
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
|
||||
feedModel.toggleCaption();
|
||||
return;
|
||||
}
|
||||
|
||||
final int id = v.getId();
|
||||
switch (id) {
|
||||
case R.id.btnComments:
|
||||
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
|
||||
feedModel.getShortCode(),
|
||||
feedModel.getPostId(),
|
||||
feedModel.getProfileModel().getId()
|
||||
);
|
||||
NavHostFragment.findNavController(this).navigate(commentsAction);
|
||||
break;
|
||||
case R.id.viewStoryPost:
|
||||
final List<FeedModel> feedModels = feedViewModel.getList().getValue();
|
||||
if (feedModels == null || feedModels.size() == 0) return;
|
||||
if (feedModels.get(0) == null) return;
|
||||
final String postId = feedModels.get(0).getPostId();
|
||||
final boolean isId = postId != null;
|
||||
final String[] idsOrShortCodes = new String[feedModels.size()];
|
||||
for (int i = 0; i < feedModels.size(); i++) {
|
||||
idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
|
||||
: feedModels.get(i).getShortCode();
|
||||
}
|
||||
final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
|
||||
feedModel.getPosition(),
|
||||
idsOrShortCodes,
|
||||
isId);
|
||||
NavHostFragment.findNavController(this).navigate(action);
|
||||
break;
|
||||
|
||||
case R.id.btnDownload:
|
||||
ProfileModel profileModel = feedModel.getProfileModel();
|
||||
final String username = profileModel != null ? profileModel.getUsername() : null;
|
||||
|
||||
final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
|
||||
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
if (feedModel
|
||||
.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
|
||||
DownloadUtils.batchDownload(context,
|
||||
username,
|
||||
DownloadMethod.DOWNLOAD_FEED,
|
||||
Collections.singletonList(feedModel));
|
||||
else {
|
||||
final ArrayList<BasePostModel> postModels = new ArrayList<>();
|
||||
final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
|
||||
postModels.clear();
|
||||
|
||||
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
|
||||
|
||||
for (final ViewerPostModel sliderItem : sliderItems) {
|
||||
if (sliderItem != null) {
|
||||
if (!breakWhenFoundSelected) postModels.add(sliderItem);
|
||||
else if (sliderItem.isSelected()) {
|
||||
postModels.add(sliderItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
|
||||
if (breakWhenFoundSelected && postModels.size() == 0) {
|
||||
postModels.add(sliderItems[0]);
|
||||
}
|
||||
if (postModels.size() > 0) {
|
||||
DownloadUtils.batchDownload(context,
|
||||
username,
|
||||
DownloadMethod.DOWNLOAD_FEED,
|
||||
postModels);
|
||||
}
|
||||
};
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.post_viewer_download_dialog_title).setPositiveButton(
|
||||
R.string.post_viewer_download_current,
|
||||
clickListener1)
|
||||
.setNegativeButton(R.string.post_viewer_download_album, clickListener1)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.ivProfilePic:
|
||||
profileModel = feedModel.getProfileModel();
|
||||
if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
|
||||
break;
|
||||
}
|
||||
// gridLayoutManager.setSpanCount(1);
|
||||
// final Object tag = v.getTag();
|
||||
// if (!(tag instanceof FeedModel)) return;
|
||||
//
|
||||
// final FeedModel feedModel = (FeedModel) tag;
|
||||
// if (v instanceof RamboTextView) {
|
||||
// if (feedModel.isMentionClicked()) feedModel.toggleCaption();
|
||||
// feedModel.setMentionClicked(false);
|
||||
// if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption()))
|
||||
// feedModel.toggleCaption();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// final int id = v.getId();
|
||||
// switch (id) {
|
||||
// case R.id.btnComments:
|
||||
// final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
|
||||
// feedModel.getShortCode(),
|
||||
// feedModel.getPostId(),
|
||||
// feedModel.getProfileModel().getId()
|
||||
// );
|
||||
// NavHostFragment.findNavController(this).navigate(commentsAction);
|
||||
// break;
|
||||
// // case R.id.viewStoryPost:
|
||||
// // final List<FeedModel> feedModels = feedViewModel.getList().getValue();
|
||||
// // if (feedModels == null || feedModels.size() == 0) return;
|
||||
// // if (feedModels.get(0) == null) return;
|
||||
// // final String postId = feedModels.get(0).getPostId();
|
||||
// // final boolean isId = postId != null;
|
||||
// // final String[] idsOrShortCodes = new String[feedModels.size()];
|
||||
// // for (int i = 0; i < feedModels.size(); i++) {
|
||||
// // idsOrShortCodes[i] = isId ? feedModels.get(i).getPostId()
|
||||
// // : feedModels.get(i).getShortCode();
|
||||
// // }
|
||||
// // final NavDirections action = FeedFragmentDirections.actionGlobalPostViewFragment(
|
||||
// // feedModel.getPosition(),
|
||||
// // idsOrShortCodes,
|
||||
// // isId);
|
||||
// // NavHostFragment.findNavController(this).navigate(action);
|
||||
// // break;
|
||||
// case R.id.btnDownload:
|
||||
// ProfileModel profileModel = feedModel.getProfileModel();
|
||||
// final String username = profileModel != null ? profileModel.getUsername() : null;
|
||||
//
|
||||
// final ViewerPostModel[] sliderItems = feedModel.getSliderItems();
|
||||
//
|
||||
// final Context context = getContext();
|
||||
// if (context == null) return;
|
||||
// if (feedModel
|
||||
// .getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1)
|
||||
// DownloadUtils.batchDownload(context,
|
||||
// username,
|
||||
// DownloadMethod.DOWNLOAD_FEED,
|
||||
// Collections.singletonList(feedModel));
|
||||
// else {
|
||||
// final ArrayList<BasePostModel> postModels = new ArrayList<>();
|
||||
// final DialogInterface.OnClickListener clickListener1 = (dialog, which) -> {
|
||||
// postModels.clear();
|
||||
//
|
||||
// final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE;
|
||||
//
|
||||
// for (final ViewerPostModel sliderItem : sliderItems) {
|
||||
// if (sliderItem != null) {
|
||||
// if (!breakWhenFoundSelected) postModels.add(sliderItem);
|
||||
// else if (sliderItem.isSelected()) {
|
||||
// postModels.add(sliderItem);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet
|
||||
// if (breakWhenFoundSelected && postModels.size() == 0) {
|
||||
// postModels.add(sliderItems[0]);
|
||||
// }
|
||||
// if (postModels.size() > 0) {
|
||||
// DownloadUtils.batchDownload(context,
|
||||
// username,
|
||||
// DownloadMethod.DOWNLOAD_FEED,
|
||||
// postModels);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// new AlertDialog.Builder(context)
|
||||
// .setTitle(R.string.post_viewer_download_dialog_title)
|
||||
// .setPositiveButton(R.string.post_viewer_download_current, clickListener1)
|
||||
// .setNegativeButton(R.string.post_viewer_download_album, clickListener1)
|
||||
// .show();
|
||||
// }
|
||||
// break;
|
||||
//
|
||||
// case R.id.ivProfilePic:
|
||||
// profileModel = feedModel.getProfileModel();
|
||||
// if (profileModel != null) mentionClickListener.onClick(null, profileModel.getUsername(), false, false);
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
};
|
||||
private FeedStoriesViewModel feedStoriesViewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
@ -290,6 +271,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
fragmentActivity = (MainActivity) requireActivity();
|
||||
storiesService = StoriesService.getInstance();
|
||||
// feedService = FeedService.getInstance();
|
||||
// final TransitionInflater inflater = TransitionInflater.from(getContext());
|
||||
// setExitTransition(inflater.inflateTransition(android.R.transition.move));
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -312,6 +296,21 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
setupFeedStories();
|
||||
setupFeed();
|
||||
shouldRefresh = false;
|
||||
// showPostsLayoutPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.feed_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
if (item.getItemId() == R.id.layout) {
|
||||
showPostsLayoutPreferences();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -335,37 +334,60 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
public void onRefresh() {
|
||||
isPullToRefresh = true;
|
||||
feedEndCursor = null;
|
||||
fetchFeed();
|
||||
binding.feedRecyclerView.refresh();
|
||||
fetchStories();
|
||||
}
|
||||
|
||||
private void setupFeed() {
|
||||
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
|
||||
// feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class);
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
binding.feedRecyclerView.setLayoutManager(layoutManager);
|
||||
binding.feedRecyclerView.setHasFixedSize(true);
|
||||
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener);
|
||||
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||
binding.feedRecyclerView.setAdapter(feedAdapter);
|
||||
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
|
||||
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
||||
if (feedHasNextPage) {
|
||||
fetchFeed();
|
||||
}
|
||||
});
|
||||
if (shouldAutoPlay) {
|
||||
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
|
||||
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
|
||||
}
|
||||
binding.feedRecyclerView.addOnScrollListener(lazyLoader);
|
||||
fetchFeed();
|
||||
// final PostFetcher.PostFetchService feedPostsFetchService = new PostFetcher.PostFetchService() {
|
||||
// @Override
|
||||
// public void fetch(final int page, final FetchListener<List<FeedModel>> fetchListener) {
|
||||
// new FeedFetcher(feedEndCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
// }
|
||||
// };
|
||||
binding.feedRecyclerView.setViewModelStoreOwner(this)
|
||||
.setLifeCycleOwner(this)
|
||||
.setPostFetchService(new FeedPostFetchService())
|
||||
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT)))
|
||||
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
|
||||
.setOnPostClickListener((feedModel, profilePicView, mainPostImage) -> {
|
||||
final PostViewV2Fragment fragment = PostViewV2Fragment
|
||||
.builder(feedModel)
|
||||
.setSharedProfilePicElement(profilePicView)
|
||||
.setSharedMainPostElement(mainPostImage)
|
||||
.build();
|
||||
fragment.show(getChildFragmentManager(), "post_view");
|
||||
})
|
||||
.init();
|
||||
binding.feedSwipeRefreshLayout.setRefreshing(true);
|
||||
// final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
// binding.feedRecyclerView.setLayoutManager(gridLayoutManager);
|
||||
// feedAdapter = new FeedAdapterV2(spanCount[0], postViewClickListener, mentionClickListener, feedModel -> {
|
||||
// final ChangeBounds transition = new ChangeBounds();
|
||||
// transition.setDuration(200);
|
||||
// TransitionManager.beginDelayedTransition(binding.feedRecyclerView, transition);
|
||||
// spanCount[0] = spanCount[0] - 1;
|
||||
// if (spanCount[0] == 0) {
|
||||
// spanCount[0] = 3;
|
||||
// }
|
||||
// gridLayoutManager.setSpanCount(spanCount[0]);
|
||||
// });
|
||||
// feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||
// binding.feedRecyclerView.setAdapter(feedAdapter);
|
||||
// feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList);
|
||||
// if (shouldAutoPlay) {
|
||||
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
|
||||
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
|
||||
// }
|
||||
// fetchFeed();
|
||||
|
||||
}
|
||||
|
||||
private void fetchFeed() {
|
||||
binding.feedSwipeRefreshLayout.setRefreshing(true);
|
||||
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
private void updateSwipeRefreshState() {
|
||||
binding.feedSwipeRefreshLayout.setRefreshing(binding.feedRecyclerView.isFetching() || storiesFetching);
|
||||
}
|
||||
|
||||
private void setupFeedStories() {
|
||||
@ -383,16 +405,28 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
||||
}
|
||||
|
||||
private void fetchStories() {
|
||||
storiesFetching = true;
|
||||
updateSwipeRefreshState();
|
||||
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
|
||||
@Override
|
||||
public void onSuccess(final List<FeedStoryModel> result) {
|
||||
feedStoriesViewModel.getList().postValue(result);
|
||||
storiesFetching = false;
|
||||
updateSwipeRefreshState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "failed", t);
|
||||
storiesFetching = false;
|
||||
updateSwipeRefreshState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showPostsLayoutPreferences() {
|
||||
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(preferences -> new Handler()
|
||||
.postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
|
||||
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package awais.instagrabber.fragments.main;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@ -39,6 +40,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.facebook.drawee.controller.BaseControllerListener;
|
||||
import com.facebook.drawee.controller.ControllerListener;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
@ -170,8 +174,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
binding.privatePage2.setText(R.string.empty_acc);
|
||||
binding.privatePage.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
binding.privatePage.setVisibility(View.GONE);
|
||||
}
|
||||
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
|
||||
@ -352,6 +355,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
postsAdapter = null;
|
||||
if (usernameSettingHandler != null) {
|
||||
usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
|
||||
}
|
||||
@ -446,31 +450,31 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
final String myId = CookieUtils.getUserIdFromCookie(cookie);
|
||||
if (isLoggedIn) {
|
||||
storiesService.getUserStory(profileId,
|
||||
profileModel.getUsername(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new ServiceCallback<List<StoryModel>>() {
|
||||
@Override
|
||||
public void onSuccess(final List<StoryModel> storyModels) {
|
||||
if (storyModels != null && !storyModels.isEmpty()) {
|
||||
binding.mainProfileImage.setStoriesBorder();
|
||||
hasStories = true;
|
||||
}
|
||||
}
|
||||
profileModel.getUsername(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
new ServiceCallback<List<StoryModel>>() {
|
||||
@Override
|
||||
public void onSuccess(final List<StoryModel> storyModels) {
|
||||
if (storyModels != null && !storyModels.isEmpty()) {
|
||||
binding.mainProfileImage.setStoriesBorder();
|
||||
hasStories = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "Error", t);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
Log.e(TAG, "Error", t);
|
||||
}
|
||||
});
|
||||
new HighlightsFetcher(profileId,
|
||||
result -> {
|
||||
if (result != null) {
|
||||
binding.highlightsList.setVisibility(View.VISIBLE);
|
||||
highlightsViewModel.getList().postValue(result);
|
||||
} else binding.highlightsList.setVisibility(View.GONE);
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
result -> {
|
||||
if (result != null) {
|
||||
binding.highlightsList.setVisibility(View.VISIBLE);
|
||||
highlightsViewModel.getList().postValue(result);
|
||||
} else binding.highlightsList.setVisibility(View.GONE);
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
if (profileId.equals(myId)) {
|
||||
binding.btnTagged.setVisibility(View.VISIBLE);
|
||||
binding.btnSaved.setVisibility(View.VISIBLE);
|
||||
@ -529,7 +533,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
} else {
|
||||
binding.favCb.setVisibility(View.GONE);
|
||||
}
|
||||
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic());
|
||||
final ControllerListener<ImageInfo> listener = new BaseControllerListener<ImageInfo>() {
|
||||
@Override
|
||||
public void onFinalImageSet(final String id, final ImageInfo imageInfo, final Animatable animatable) {
|
||||
startPostponedEnterTransition();
|
||||
}
|
||||
};
|
||||
binding.mainProfileImage.setImageURI(profileModel.getHdProfilePic());
|
||||
|
||||
final long followersCount = profileModel.getFollowersCount();
|
||||
final long followingCount = profileModel.getFollowingCount();
|
||||
@ -562,8 +572,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
: profileModel.getName());
|
||||
|
||||
CharSequence biography = profileModel.getBiography();
|
||||
binding.mainBiography.setCaptionIsExpandable(true);
|
||||
binding.mainBiography.setCaptionIsExpanded(true);
|
||||
// binding.mainBiography.setCaptionIsExpandable(true);
|
||||
// binding.mainBiography.setCaptionIsExpanded(true);
|
||||
if (TextUtils.hasMentions(biography)) {
|
||||
biography = TextUtils.getMentionText(biography);
|
||||
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
|
||||
|
@ -2,5 +2,8 @@ package awais.instagrabber.interfaces;
|
||||
|
||||
public interface FetchListener<T> {
|
||||
void onResult(T result);
|
||||
default void doBefore() { }
|
||||
|
||||
default void doBefore() {}
|
||||
|
||||
default void onFailure(Throwable t) {}
|
||||
}
|
@ -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() {
|
||||
|
@ -1,47 +1,174 @@
|
||||
package awais.instagrabber.models;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
|
||||
public final class FeedModel extends PostModel {
|
||||
private final ProfileModel profileModel;
|
||||
private final long commentsCount;
|
||||
private long likesCount;
|
||||
private final long viewCount;
|
||||
private boolean captionExpanded = false;
|
||||
private boolean mentionClicked = false;
|
||||
private ViewerPostModel[] sliderItems;
|
||||
private List<PostChild> sliderItems;
|
||||
private int imageWidth;
|
||||
private int imageHeight;
|
||||
private String locationName;
|
||||
private String locationId;
|
||||
|
||||
public FeedModel(final ProfileModel profileModel,
|
||||
final MediaItemType itemType,
|
||||
final long viewCount,
|
||||
final String postId,
|
||||
final String displayUrl,
|
||||
final String thumbnailUrl,
|
||||
final String shortCode,
|
||||
final String postCaption,
|
||||
final long commentsCount,
|
||||
final long timestamp,
|
||||
final boolean liked,
|
||||
final boolean bookmarked,
|
||||
final long likes,
|
||||
final String locationName,
|
||||
final String locationId) {
|
||||
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked, likes);
|
||||
public static class Builder {
|
||||
|
||||
private ProfileModel profileModel;
|
||||
private MediaItemType itemType;
|
||||
private long viewCount;
|
||||
private String postId;
|
||||
private String displayUrl;
|
||||
private String thumbnailUrl;
|
||||
private String shortCode;
|
||||
private String postCaption;
|
||||
private long commentsCount;
|
||||
private long timestamp;
|
||||
private boolean liked;
|
||||
private boolean bookmarked;
|
||||
private long likesCount;
|
||||
private String locationName;
|
||||
private String locationId;
|
||||
private List<PostChild> sliderItems;
|
||||
private int imageWidth;
|
||||
private int imageHeight;
|
||||
|
||||
public Builder setProfileModel(final ProfileModel profileModel) {
|
||||
this.profileModel = profileModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setItemType(final MediaItemType itemType) {
|
||||
this.itemType = itemType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setViewCount(final long viewCount) {
|
||||
this.viewCount = viewCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPostId(final String postId) {
|
||||
this.postId = postId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDisplayUrl(final String displayUrl) {
|
||||
this.displayUrl = displayUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setThumbnailUrl(final String thumbnailUrl) {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setShortCode(final String shortCode) {
|
||||
this.shortCode = shortCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPostCaption(final String postCaption) {
|
||||
this.postCaption = postCaption;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCommentsCount(final long commentsCount) {
|
||||
this.commentsCount = commentsCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTimestamp(final long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLiked(final boolean liked) {
|
||||
this.liked = liked;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBookmarked(final boolean bookmarked) {
|
||||
this.bookmarked = bookmarked;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLikesCount(final long likesCount) {
|
||||
this.likesCount = likesCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLocationName(final String locationName) {
|
||||
this.locationName = locationName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLocationId(final String locationId) {
|
||||
this.locationId = locationId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSliderItems(final List<PostChild> sliderItems) {
|
||||
this.sliderItems = sliderItems;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setImageHeight(final int imageHeight) {
|
||||
this.imageHeight = imageHeight;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setImageWidth(final int imageWidth) {
|
||||
this.imageWidth = imageWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FeedModel build() {
|
||||
return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, commentsCount,
|
||||
timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private FeedModel(final ProfileModel profileModel,
|
||||
final MediaItemType itemType,
|
||||
final long viewCount,
|
||||
final String postId,
|
||||
final String displayUrl,
|
||||
final String thumbnailUrl,
|
||||
final String shortCode,
|
||||
final String postCaption,
|
||||
final long commentsCount,
|
||||
final long timestamp,
|
||||
final boolean liked,
|
||||
final boolean bookmarked,
|
||||
final long likesCount,
|
||||
final String locationName,
|
||||
final String locationId,
|
||||
final List<PostChild> sliderItems,
|
||||
final int imageHeight,
|
||||
final int imageWidth) {
|
||||
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked);
|
||||
this.profileModel = profileModel;
|
||||
this.commentsCount = commentsCount;
|
||||
this.likesCount = likesCount;
|
||||
this.viewCount = viewCount;
|
||||
this.locationName = locationName;
|
||||
this.locationId = locationId;
|
||||
this.sliderItems = sliderItems;
|
||||
this.imageHeight = imageHeight;
|
||||
this.imageWidth = imageWidth;
|
||||
}
|
||||
|
||||
public ProfileModel getProfileModel() {
|
||||
return profileModel;
|
||||
}
|
||||
|
||||
public ViewerPostModel[] getSliderItems() {
|
||||
public List<PostChild> getSliderItems() {
|
||||
return sliderItems;
|
||||
}
|
||||
|
||||
@ -53,6 +180,10 @@ public final class FeedModel extends PostModel {
|
||||
return commentsCount;
|
||||
}
|
||||
|
||||
public long getLikesCount() {
|
||||
return likesCount;
|
||||
}
|
||||
|
||||
public boolean isCaptionExpanded() {
|
||||
return captionExpanded;
|
||||
}
|
||||
@ -61,14 +192,14 @@ public final class FeedModel extends PostModel {
|
||||
return !mentionClicked;
|
||||
}
|
||||
|
||||
public void setMentionClicked(final boolean mentionClicked) {
|
||||
this.mentionClicked = mentionClicked;
|
||||
}
|
||||
|
||||
public void setSliderItems(final ViewerPostModel[] sliderItems) {
|
||||
this.sliderItems = sliderItems;
|
||||
setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
|
||||
}
|
||||
// public void setMentionClicked(final boolean mentionClicked) {
|
||||
// this.mentionClicked = mentionClicked;
|
||||
// }
|
||||
//
|
||||
// public void setSliderItems(final ViewerPostModel[] sliderItems) {
|
||||
// this.sliderItems = sliderItems;
|
||||
// setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
|
||||
// }
|
||||
|
||||
public void toggleCaption() {
|
||||
captionExpanded = !captionExpanded;
|
||||
@ -78,13 +209,13 @@ public final class FeedModel extends PostModel {
|
||||
return imageWidth;
|
||||
}
|
||||
|
||||
public void setImageWidth(final int imageWidth) {
|
||||
this.imageWidth = imageWidth;
|
||||
}
|
||||
// public void setImageWidth(final int imageWidth) {
|
||||
// this.imageWidth = imageWidth;
|
||||
// }
|
||||
|
||||
public void setImageHeight(final int imageHeight) {
|
||||
this.imageHeight = imageHeight;
|
||||
}
|
||||
// public void setImageHeight(final int imageHeight) {
|
||||
// this.imageHeight = imageHeight;
|
||||
// }
|
||||
|
||||
public int getImageHeight() {
|
||||
return imageHeight;
|
||||
@ -94,15 +225,45 @@ public final class FeedModel extends PostModel {
|
||||
return locationName;
|
||||
}
|
||||
|
||||
public void setLocationName(final String locationName) {
|
||||
this.locationName = locationName;
|
||||
}
|
||||
// public void setLocationName(final String locationName) {
|
||||
// this.locationName = locationName;
|
||||
// }
|
||||
|
||||
public String getLocationId() {
|
||||
return locationId;
|
||||
}
|
||||
|
||||
public void setLocationId(final String locationId) {
|
||||
this.locationId = locationId;
|
||||
// public void setLocationId(final String locationId) {
|
||||
// this.locationId = locationId;
|
||||
// }
|
||||
|
||||
public void setLiked(final boolean liked) {
|
||||
this.liked = liked;
|
||||
}
|
||||
|
||||
public void setLikesCount(final long count) {
|
||||
this.likesCount = count;
|
||||
}
|
||||
|
||||
public void setSaved(final boolean saved) {
|
||||
this.saved = saved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FeedModel{" +
|
||||
"type=" + itemType +
|
||||
", displayUrl=" + displayUrl +
|
||||
", thumbnailUrl=" + thumbnailUrl +
|
||||
", commentsCount=" + commentsCount +
|
||||
", viewCount=" + viewCount +
|
||||
", captionExpanded=" + captionExpanded +
|
||||
", mentionClicked=" + mentionClicked +
|
||||
// ", sliderItems=" + sliderItems +
|
||||
", imageWidth=" + imageWidth +
|
||||
", imageHeight=" + imageHeight +
|
||||
", locationName='" + locationName + '\'' +
|
||||
", locationId='" + locationId + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
121
app/src/main/java/awais/instagrabber/models/PostChild.java
Normal file
121
app/src/main/java/awais/instagrabber/models/PostChild.java
Normal 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package awais.instagrabber.repositories;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface FeedRepository {
|
||||
@GET("/graphql/query/")
|
||||
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package awais.instagrabber.repositories.responses;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
|
||||
public class FeedFetchResponse {
|
||||
private List<FeedModel> feedModels;
|
||||
private boolean hasNextPage;
|
||||
private String nextCursor;
|
||||
|
||||
public FeedFetchResponse(final List<FeedModel> feedModels, final boolean hasNextPage, final String nextCursor) {
|
||||
this.feedModels = feedModels;
|
||||
this.hasNextPage = hasNextPage;
|
||||
this.nextCursor = nextCursor;
|
||||
}
|
||||
|
||||
public List<FeedModel> getFeedModels() {
|
||||
return feedModels;
|
||||
}
|
||||
|
||||
public boolean hasNextPage() {
|
||||
return hasNextPage;
|
||||
}
|
||||
|
||||
public String getNextCursor() {
|
||||
return nextCursor;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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";
|
||||
}
|
@ -8,16 +8,29 @@ import android.os.AsyncTask;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.WorkRequest;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
@ -25,17 +38,29 @@ import awais.instagrabber.R;
|
||||
import awais.instagrabber.asyncs.DownloadAsync;
|
||||
import awais.instagrabber.asyncs.PostFetcher;
|
||||
import awais.instagrabber.models.BasePostModel;
|
||||
import awais.instagrabber.models.StoryModel;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.models.PostChild;
|
||||
import awais.instagrabber.models.direct_messages.DirectItemModel;
|
||||
import awais.instagrabber.models.enums.DownloadMethod;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.workers.DownloadWorker;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
|
||||
public final class DownloadUtils {
|
||||
public static final String WRITE_PERMISSION = Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
private static int lastNotificationId = UUID.randomUUID().hashCode();
|
||||
|
||||
public synchronized static int getNextDownloadNotificationId(@NonNull final Context context) {
|
||||
lastNotificationId = lastNotificationId + 1;
|
||||
if (lastNotificationId == Integer.MAX_VALUE) {
|
||||
lastNotificationId = UUID.randomUUID().hashCode();
|
||||
}
|
||||
return lastNotificationId;
|
||||
}
|
||||
|
||||
public static void batchDownload(@NonNull final Context context,
|
||||
@Nullable String username,
|
||||
@ -57,20 +82,8 @@ public final class DownloadUtils {
|
||||
@Nullable final String username,
|
||||
final DownloadMethod method,
|
||||
final List<? extends BasePostModel> itemsToDownload) {
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
||||
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
|
||||
}
|
||||
|
||||
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username))
|
||||
dir = new File(dir, username);
|
||||
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final File dir = getDownloadDir(context, username);
|
||||
if (dir == null) return;
|
||||
boolean checkEachPost = false;
|
||||
switch (method) {
|
||||
case DOWNLOAD_SAVED:
|
||||
@ -86,7 +99,11 @@ public final class DownloadUtils {
|
||||
final BasePostModel selectedItem = itemsToDownload.get(i);
|
||||
if (!checkEachPost) {
|
||||
final boolean isSlider = itemsToDownloadSize > 1;
|
||||
final File saveFile = getDownloadSaveFile(dir, selectedItem, isSlider ? "_slide_" + (i + 1) : "");
|
||||
final File saveFile = getDownloadSaveFile(dir,
|
||||
selectedItem.getPostId(),
|
||||
isSlider ? "_slide_" + (i + 1) : "",
|
||||
selectedItem.getDisplayUrl()
|
||||
);
|
||||
new DownloadAsync(context,
|
||||
selectedItem.getDisplayUrl(),
|
||||
saveFile,
|
||||
@ -95,25 +112,64 @@ public final class DownloadUtils {
|
||||
} else {
|
||||
final File finalDir = dir;
|
||||
new PostFetcher(selectedItem.getShortCode(), result -> {
|
||||
if (result != null) {
|
||||
final int resultsSize = result.length;
|
||||
final boolean multiResult = resultsSize > 1;
|
||||
for (int j = 0; j < resultsSize; j++) {
|
||||
final BasePostModel model = result[j];
|
||||
final File saveFile = getDownloadSaveFile(finalDir, model, multiResult ? "_slide_" + (j + 1) : "");
|
||||
if (result == null) return;
|
||||
final boolean isSlider = result.getItemType() == MediaItemType.MEDIA_TYPE_SLIDER;
|
||||
if (isSlider) {
|
||||
for (int j = 0; j < result.getSliderItems().size(); j++) {
|
||||
final PostChild model = result.getSliderItems().get(j);
|
||||
final File saveFile = getDownloadSaveFile(
|
||||
finalDir,
|
||||
model.getPostId(),
|
||||
"_slide_" + (j + 1),
|
||||
model.getDisplayUrl()
|
||||
);
|
||||
new DownloadAsync(context,
|
||||
model.getDisplayUrl(),
|
||||
saveFile,
|
||||
file -> model.setDownloaded(true))
|
||||
file -> {}/*model.setDownloaded(true)*/)
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
} else {
|
||||
final File saveFile = getDownloadSaveFile(
|
||||
finalDir,
|
||||
result.getPostId(),
|
||||
result.getDisplayUrl()
|
||||
);
|
||||
new DownloadAsync(context,
|
||||
result.getDisplayUrl(),
|
||||
saveFile,
|
||||
file -> result.setDownloaded(true))
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void dmDownload(@NonNull final Context context, @Nullable final String username, final DownloadMethod method,
|
||||
@Nullable
|
||||
private static File getDownloadDir(@NonNull final Context context, @Nullable final String username) {
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH);
|
||||
if (!TextUtils.isEmpty(customPath)) dir = new File(customPath);
|
||||
}
|
||||
|
||||
if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !TextUtils.isEmpty(username)) {
|
||||
final String finaleUsername = username.startsWith("@") ? username : "@" + username;
|
||||
dir = new File(dir, finaleUsername);
|
||||
}
|
||||
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
||||
return null;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
public static void dmDownload(@NonNull final Context context,
|
||||
@Nullable final String username,
|
||||
final DownloadMethod method,
|
||||
final DirectItemModel.DirectItemMediaModel itemsToDownload) {
|
||||
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);
|
||||
|
||||
@ -125,8 +181,10 @@ public final class DownloadUtils {
|
||||
ActivityCompat.requestPermissions((Activity) context, PERMS, 8020);
|
||||
}
|
||||
|
||||
private static void dmDownloadImpl(@NonNull final Context context, @Nullable final String username,
|
||||
final DownloadMethod method, final DirectItemModel.DirectItemMediaModel selectedItem) {
|
||||
private static void dmDownloadImpl(@NonNull final Context context,
|
||||
@Nullable final String username,
|
||||
final DownloadMethod method,
|
||||
final DirectItemModel.DirectItemMediaModel selectedItem) {
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), "Download");
|
||||
|
||||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) {
|
||||
@ -140,46 +198,83 @@ public final class DownloadUtils {
|
||||
if (dir.exists() || dir.mkdirs()) {
|
||||
new DownloadAsync(context,
|
||||
selectedItem.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO ? selectedItem.getVideoUrl() : selectedItem.getThumbUrl(),
|
||||
getDownloadSaveFileDm(dir, selectedItem, ""),
|
||||
null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
getDownloadSaveFileDm(dir, selectedItem),
|
||||
null)
|
||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else
|
||||
Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static File getDownloadSaveFile(final File finalDir, @NonNull final BasePostModel model, final String sliderPrefix) {
|
||||
final String displayUrl = model.getDisplayUrl();
|
||||
return new File(finalDir, model.getPostId() + '_' + model.getPosition() + sliderPrefix +
|
||||
getExtensionFromModel(displayUrl, model));
|
||||
private static File getDownloadSaveFile(final File finalDir,
|
||||
final String postId,
|
||||
final String displayUrl) {
|
||||
return getDownloadSaveFile(finalDir, postId, "", displayUrl);
|
||||
}
|
||||
|
||||
private static File getDownloadChildSaveFile(final File downloadDir,
|
||||
final String postId,
|
||||
final int childPosition,
|
||||
final String url) {
|
||||
final String sliderPostfix = "_slide_" + childPosition;
|
||||
return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static File getDownloadSaveFile(final File finalDir,
|
||||
final String postId,
|
||||
final String sliderPostfix,
|
||||
final String displayUrl) {
|
||||
final String fileName = postId + sliderPostfix + "." + getFileExtensionFromUrl(displayUrl);
|
||||
return new File(finalDir, fileName);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static File getDownloadSaveFileDm(final File finalDir,
|
||||
@NonNull final DirectItemModel.DirectItemMediaModel model,
|
||||
final String sliderPrefix) {
|
||||
final String displayUrl = model.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO ? model.getVideoUrl() : model.getThumbUrl();
|
||||
return new File(finalDir, model.getId() + sliderPrefix +
|
||||
getExtensionFromModel(displayUrl, model));
|
||||
@NonNull final DirectItemModel.DirectItemMediaModel model) {
|
||||
final boolean isVideo = model.getMediaType() == MediaItemType.MEDIA_TYPE_VIDEO;
|
||||
final String displayUrl = isVideo ? model.getVideoUrl() : model.getThumbUrl();
|
||||
return new File(finalDir, model.getId() + getFileExtensionFromUrl(displayUrl));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getExtensionFromModel(@NonNull final String url, final Object model) {
|
||||
final String extension;
|
||||
final int index = url.indexOf('?');
|
||||
/**
|
||||
* Copied from {@link MimeTypeMap#getFileExtensionFromUrl(String)})
|
||||
* <p>
|
||||
* Returns the file extension or an empty string if there is no
|
||||
* extension. This method is a convenience method for obtaining the
|
||||
* extension of a url and has undefined results for other Strings.
|
||||
*
|
||||
* @param url URL
|
||||
* @return The file extension of the given url.
|
||||
*/
|
||||
public static String getFileExtensionFromUrl(String url) {
|
||||
if (!TextUtils.isEmpty(url)) {
|
||||
int fragment = url.lastIndexOf('#');
|
||||
if (fragment > 0) {
|
||||
url = url.substring(0, fragment);
|
||||
}
|
||||
|
||||
if (index != -1) extension = url.substring(index - 4, index);
|
||||
else {
|
||||
final boolean isVideo;
|
||||
if (model instanceof StoryModel)
|
||||
isVideo = ((StoryModel) model).getItemType() == MediaItemType.MEDIA_TYPE_VIDEO;
|
||||
else if (model instanceof BasePostModel)
|
||||
isVideo = ((BasePostModel) model).getItemType() == MediaItemType.MEDIA_TYPE_VIDEO;
|
||||
else
|
||||
isVideo = false;
|
||||
extension = isVideo || url.contains(".mp4") ? ".mp4" : ".jpg";
|
||||
int query = url.lastIndexOf('?');
|
||||
if (query > 0) {
|
||||
url = url.substring(0, query);
|
||||
}
|
||||
|
||||
int filenamePos = url.lastIndexOf('/');
|
||||
String filename =
|
||||
0 <= filenamePos ? url.substring(filenamePos + 1) : url;
|
||||
|
||||
// if the filename contains special characters, we don't
|
||||
// consider it valid for our matching purposes:
|
||||
if (!filename.isEmpty() &&
|
||||
Pattern.matches("[a-zA-Z_0-9.\\-()%]+", filename)) {
|
||||
int dotPos = filename.lastIndexOf('.');
|
||||
if (0 <= dotPos) {
|
||||
return filename.substring(dotPos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extension;
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void checkExistence(final File downloadDir, final File customDir, final boolean isSlider,
|
||||
@ -198,7 +293,7 @@ public final class DownloadUtils {
|
||||
final String fileWithoutPrefix = fileName + '0' + extension;
|
||||
exists = new File(downloadDir, fileWithoutPrefix).exists();
|
||||
if (!exists) {
|
||||
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\" + extension + ")";
|
||||
final String fileWithPrefix = fileName + "[\\d]+(|_slide_[\\d]+)(\\.mp4|\\\\" + extension + ")";
|
||||
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name);
|
||||
|
||||
File[] files = downloadDir.listFiles(filenameFilter);
|
||||
@ -217,4 +312,66 @@ public final class DownloadUtils {
|
||||
|
||||
model.setDownloaded(exists);
|
||||
}
|
||||
|
||||
public static void download(@NonNull final Context context,
|
||||
@NonNull final FeedModel feedModel) {
|
||||
download(context, feedModel, -1);
|
||||
}
|
||||
|
||||
public static void download(@NonNull final Context context,
|
||||
@NonNull final FeedModel feedModel,
|
||||
final int position) {
|
||||
final File downloadDir = getDownloadDir(context, "@" + feedModel.getProfileModel().getUsername());
|
||||
if (downloadDir == null) return;
|
||||
switch (feedModel.getItemType()) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
case MEDIA_TYPE_VIDEO: {
|
||||
final String url = feedModel.getDisplayUrl();
|
||||
final File file = getDownloadSaveFile(downloadDir, feedModel.getPostId(), url);
|
||||
download(context, url, file.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
case MEDIA_TYPE_SLIDER:
|
||||
final List<PostChild> sliderItems = feedModel.getSliderItems();
|
||||
final Map<String, String> map = new HashMap<>();
|
||||
for (int i = 0; i < sliderItems.size(); i++) {
|
||||
final PostChild child = sliderItems.get(i);
|
||||
final String url = child.getDisplayUrl();
|
||||
final File file = getDownloadChildSaveFile(downloadDir, feedModel.getPostId(), i + 1, url);
|
||||
map.put(url, file.getAbsolutePath());
|
||||
}
|
||||
download(context, map);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void download(final Context context,
|
||||
final String url,
|
||||
final String filePath) {
|
||||
if (context == null || url == null || filePath == null) return;
|
||||
download(context, Collections.singletonMap(url, filePath));
|
||||
}
|
||||
|
||||
private static void download(final Context context, final Map<String, String> urlFilePathMap) {
|
||||
final Constraints constraints = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
final DownloadWorker.DownloadRequest request = DownloadWorker.DownloadRequest.builder()
|
||||
.setUrlToFilePathMap(urlFilePathMap)
|
||||
.build();
|
||||
final WorkRequest downloadWorkRequest = new OneTimeWorkRequest.Builder(DownloadWorker.class)
|
||||
.setInputData(
|
||||
new Data.Builder()
|
||||
.putString(DownloadWorker.KEY_DOWNLOAD_REQUEST_JSON,
|
||||
new Gson().toJson(request))
|
||||
.build()
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.addTag("download")
|
||||
.build();
|
||||
WorkManager.getInstance(context)
|
||||
.enqueue(downloadWorkRequest);
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,7 @@ public class NavigationExtensions {
|
||||
}
|
||||
final NavHostFragment navHostFragment = NavHostFragment.create(navGraphId);
|
||||
fragmentManager.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.add(containerId, navHostFragment, fragmentTag)
|
||||
.commitNow();
|
||||
return navHostFragment;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package awais.instagrabber.webservices;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -8,6 +9,11 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -30,6 +36,7 @@ import retrofit2.Retrofit;
|
||||
|
||||
public class StoriesService extends BaseService {
|
||||
private static final String TAG = "StoriesService";
|
||||
private static final boolean loadFromMock = false;
|
||||
|
||||
private final StoriesRepository repository;
|
||||
|
||||
@ -50,6 +57,30 @@ public class StoriesService extends BaseService {
|
||||
}
|
||||
|
||||
public void getFeedStories(final ServiceCallback<List<FeedStoryModel>> callback) {
|
||||
if (loadFromMock) {
|
||||
final Handler handler = new Handler();
|
||||
handler.postDelayed(() -> {
|
||||
final ClassLoader classLoader = getClass().getClassLoader();
|
||||
if (classLoader == null) {
|
||||
Log.e(TAG, "getFeedStories: classLoader is null!");
|
||||
return;
|
||||
}
|
||||
try (InputStream resourceAsStream = classLoader.getResourceAsStream("stories_response.json");
|
||||
Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) {
|
||||
final int bufferSize = 1024;
|
||||
final char[] buffer = new char[bufferSize];
|
||||
final StringBuilder out = new StringBuilder();
|
||||
int charsRead;
|
||||
while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) {
|
||||
out.append(buffer, 0, charsRead);
|
||||
}
|
||||
parseStoriesBody(out.toString(), callback);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "getFeedStories: ", e);
|
||||
}
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
final Map<String, String> queryMap = new HashMap<>();
|
||||
queryMap.put("query_hash", "b7b84d884400bc5aa7cfe12ae843a091");
|
||||
queryMap.put("variables", "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}");
|
||||
@ -62,31 +93,7 @@ public class StoriesService extends BaseService {
|
||||
Log.e(TAG, "getFeedStories: body is empty");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
|
||||
final JSONArray feedStoriesReel = new JSONObject(body)
|
||||
.getJSONObject("data")
|
||||
.getJSONObject(Constants.EXTRAS_USER)
|
||||
.getJSONObject("feed_reels_tray")
|
||||
.getJSONObject("edge_reels_tray_to_reel")
|
||||
.getJSONArray("edges");
|
||||
for (int i = 0; i < feedStoriesReel.length(); ++i) {
|
||||
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
|
||||
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
|
||||
final ProfileModel profileModel = new ProfileModel(false, false, false,
|
||||
user.getString("id"),
|
||||
user.getString("username"),
|
||||
null, null, null,
|
||||
user.getString("profile_pic_url"),
|
||||
null, 0, 0, 0, false, false, false, false);
|
||||
final String id = node.getString("id");
|
||||
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
|
||||
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead));
|
||||
}
|
||||
callback.onSuccess(feedStoryModels);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Error parsing json", e);
|
||||
}
|
||||
parseStoriesBody(body, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -96,6 +103,34 @@ public class StoriesService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
private void parseStoriesBody(final String body, final ServiceCallback<List<FeedStoryModel>> callback) {
|
||||
try {
|
||||
final List<FeedStoryModel> feedStoryModels = new ArrayList<>();
|
||||
final JSONArray feedStoriesReel = new JSONObject(body)
|
||||
.getJSONObject("data")
|
||||
.getJSONObject(Constants.EXTRAS_USER)
|
||||
.getJSONObject("feed_reels_tray")
|
||||
.getJSONObject("edge_reels_tray_to_reel")
|
||||
.getJSONArray("edges");
|
||||
for (int i = 0; i < feedStoriesReel.length(); ++i) {
|
||||
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node");
|
||||
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner");
|
||||
final ProfileModel profileModel = new ProfileModel(false, false, false,
|
||||
user.getString("id"),
|
||||
user.getString("username"),
|
||||
null, null, null,
|
||||
user.getString("profile_pic_url"),
|
||||
null, 0, 0, 0, false, false, false, false);
|
||||
final String id = node.getString("id");
|
||||
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media");
|
||||
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead));
|
||||
}
|
||||
callback.onSuccess(feedStoryModels);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Error parsing json", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void getUserStory(final String id,
|
||||
final String username,
|
||||
final boolean isLoc,
|
||||
@ -138,12 +173,15 @@ public class StoriesService extends BaseService {
|
||||
data = media.getJSONObject(i);
|
||||
final boolean isVideo = data.has("video_duration");
|
||||
final StoryModel model = new StoryModel(data.getString("id"),
|
||||
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0).getString("url"),
|
||||
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
|
||||
data.optLong("taken_at", 0),
|
||||
(isLoc || isHashtag) ? data.getJSONObject("user").getString("username") : localUsername,
|
||||
data.getJSONObject("user").getString("pk"),
|
||||
data.getBoolean("can_reply"));
|
||||
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
|
||||
.getString("url"),
|
||||
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
|
||||
data.optLong("taken_at", 0),
|
||||
(isLoc || isHashtag)
|
||||
? data.getJSONObject("user").getString("username")
|
||||
: localUsername,
|
||||
data.getJSONObject("user").getString("pk"),
|
||||
data.getBoolean("can_reply"));
|
||||
|
||||
final JSONArray videoResources = data.optJSONArray("video_versions");
|
||||
if (isVideo && videoResources != null)
|
||||
@ -174,7 +212,8 @@ public class StoriesService extends BaseService {
|
||||
));
|
||||
}
|
||||
if (data.has("story_questions")) {
|
||||
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0).optJSONObject("question_sticker");
|
||||
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0)
|
||||
.optJSONObject("question_sticker");
|
||||
if (tappableObject != null && !tappableObject.getString("question_type").equals("music"))
|
||||
model.setQuestion(new QuestionModel(
|
||||
String.valueOf(tappableObject.getLong("question_id")),
|
||||
@ -229,8 +268,7 @@ public class StoriesService extends BaseService {
|
||||
models.add(model);
|
||||
}
|
||||
callback.onSuccess(models);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
callback.onSuccess(null);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
@ -251,14 +289,11 @@ public class StoriesService extends BaseService {
|
||||
builder.append("https://i.instagram.com/api/v1/");
|
||||
if (isLoc) {
|
||||
builder.append("locations/");
|
||||
}
|
||||
else if (isHashtag) {
|
||||
} else if (isHashtag) {
|
||||
builder.append("tags/");
|
||||
}
|
||||
else if (highlight) {
|
||||
} else if (highlight) {
|
||||
builder.append("feed/reels_media/?user_ids=");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
builder.append("feed/user/");
|
||||
}
|
||||
builder.append(userId);
|
||||
|
382
app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
Normal file
382
app/src/main/java/awais/instagrabber/workers/DownloadWorker.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
18
app/src/main/res/anim/dialog_anim_in.xml
Normal file
18
app/src/main/res/anim/dialog_anim_in.xml
Normal 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>
|
19
app/src/main/res/anim/dialog_anim_out.xml
Normal file
19
app/src/main/res/anim/dialog_anim_out.xml
Normal 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>
|
3
app/src/main/res/drawable/background_grey_ripple.xml
Normal file
3
app/src/main/res/drawable/background_grey_ripple.xml
Normal 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" />
|
10
app/src/main/res/drawable/ic_border_style_flipped_24.xml
Normal file
10
app/src/main/res/drawable/ic_border_style_flipped_24.xml
Normal 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>
|
11
app/src/main/res/drawable/ic_checkbox_multiple_blank.xml
Normal file
11
app/src/main/res/drawable/ic_checkbox_multiple_blank.xml
Normal 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>
|
@ -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>
|
10
app/src/main/res/drawable/ic_class_24.xml
Normal file
10
app/src/main/res/drawable/ic_class_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_dashboard_24.xml
Normal file
10
app/src/main/res/drawable/ic_dashboard_24.xml
Normal 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>
|
13
app/src/main/res/drawable/ic_forward_5_24.xml
Normal file
13
app/src/main/res/drawable/ic_forward_5_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_notes_24.xml
Normal file
10
app/src/main/res/drawable/ic_notes_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_pause_24.xml
Normal file
10
app/src/main/res/drawable/ic_pause_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_play_arrow_24.xml
Normal file
10
app/src/main/res/drawable/ic_play_arrow_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_play_circle_outline_24.xml
Normal file
10
app/src/main/res/drawable/ic_play_circle_outline_24.xml
Normal 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>
|
0
app/src/main/res/drawable/ic_profile.xml → app/src/main/res/drawable/ic_profile_24.xml
Executable file → Normal file
0
app/src/main/res/drawable/ic_profile.xml → app/src/main/res/drawable/ic_profile_24.xml
Executable file → Normal file
10
app/src/main/res/drawable/ic_profile_40.xml
Normal file
10
app/src/main/res/drawable/ic_profile_40.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_profile_48.xml
Normal file
10
app/src/main/res/drawable/ic_profile_48.xml
Normal 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>
|
13
app/src/main/res/drawable/ic_replay_5_24.xml
Normal file
13
app/src/main/res/drawable/ic_replay_5_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_rounded_corner_24.xml
Normal file
10
app/src/main/res/drawable/ic_rounded_corner_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_view_agenda_24.xml
Normal file
10
app/src/main/res/drawable/ic_view_agenda_24.xml
Normal 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>
|
11
app/src/main/res/drawable/ic_view_grid_24.xml
Normal file
11
app/src/main/res/drawable/ic_view_grid_24.xml
Normal 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>
|
8
app/src/main/res/drawable/popup_background_exoplayer.xml
Normal file
8
app/src/main/res/drawable/popup_background_exoplayer.xml
Normal 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>
|
5
app/src/main/res/drawable/rounder_corner_bg.xml
Normal file
5
app/src/main/res/drawable/rounder_corner_bg.xml
Normal 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
Loading…
Reference in New Issue
Block a user