1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 22:57:29 +00:00

Add PostsRecyclerView to ProfileFragment

This commit is contained in:
Ammar Githam 2020-10-27 20:33:21 +09:00
parent 9b83c5e832
commit 0a67e859e0
19 changed files with 662 additions and 265 deletions

View File

@ -5,7 +5,7 @@ import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher; import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.responses.FeedFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.FeedService; import awais.instagrabber.webservices.FeedService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
@ -21,9 +21,9 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService {
@Override @Override
public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, cursor, new ServiceCallback<FeedFetchResponse>() { feedService.fetch(25, cursor, new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final FeedFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextCursor = result.getNextCursor(); nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage(); hasNextPage = result.hasNextPage();

View File

@ -0,0 +1,57 @@
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.models.ProfileModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "ProfilePostFetchService";
private final ProfileService profileService;
private final ProfileModel profileModel;
private String nextCursor;
private boolean hasNextPage;
public ProfilePostFetchService(final ProfileModel profileModel) {
this.profileModel = profileModel;
profileService = ProfileService.getInstance();
}
@Override
public void fetch(final String cursor, final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, cursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
}
@Override
public String getNextCursor() {
return nextCursor;
}
@Override
public boolean hasNextPage() {
return hasNextPage;
}
}

View File

@ -25,7 +25,6 @@ import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtBottom;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedViewModel; import awais.instagrabber.viewmodels.FeedViewModel;
@ -130,16 +129,8 @@ public class PostsRecyclerView extends RecyclerView {
throw new IllegalArgumentException("PostFetchService cannot be null"); throw new IllegalArgumentException("PostFetchService cannot be null");
} }
if (layoutPreferences == null) { if (layoutPreferences == null) {
layoutPreferences = PostsLayoutPreferences.builder() layoutPreferences = PostsLayoutPreferences.builder().build();
.setType(PostsLayoutPreferences.PostsLayoutType.GRID) // Utils.settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, layoutPreferences.getJson());
.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)); gridSpacingItemDecoration = new GridSpacingItemDecoration(Utils.convertDpToPx(2));
initTransition(); initTransition();
@ -156,9 +147,6 @@ public class PostsRecyclerView extends RecyclerView {
private void initLayoutManager() { private void initLayoutManager() {
layoutManager = new StaggeredGridLayoutManager(layoutPreferences.getColCount(), StaggeredGridLayoutManager.VERTICAL); layoutManager = new StaggeredGridLayoutManager(layoutPreferences.getColCount(), StaggeredGridLayoutManager.VERTICAL);
if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration);
}
setLayoutManager(layoutManager); setLayoutManager(layoutManager);
} }
@ -172,7 +160,9 @@ public class PostsRecyclerView extends RecyclerView {
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class); feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
feedViewModel.getList().observe(lifeCycleOwner, feedAdapter::submitList); feedViewModel.getList().observe(lifeCycleOwner, feedAdapter::submitList);
postFetcher = new PostFetcher(postFetchService, fetchListener); postFetcher = new PostFetcher(postFetchService, fetchListener);
addItemDecoration(gridSpacingItemDecoration); if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration);
}
setHasFixedSize(true); setHasFixedSize(true);
setNestedScrollingEnabled(true); setNestedScrollingEnabled(true);
lazyLoader = new RecyclerLazyLoaderAtBottom(layoutManager, (page) -> { lazyLoader = new RecyclerLazyLoaderAtBottom(layoutManager, (page) -> {

View File

@ -16,20 +16,21 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding; import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Constants;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostsLayoutPreferencesDialogFragment extends DialogFragment { public class PostsLayoutPreferencesDialogFragment extends DialogFragment {
private final PostsLayoutPreferences.Builder preferencesBuilder;
@NonNull
private final OnApplyListener onApplyListener; private final OnApplyListener onApplyListener;
private final PostsLayoutPreferences.Builder preferencesBuilder;
private final String layoutPreferenceKey;
private DialogPostLayoutPreferencesBinding binding; private DialogPostLayoutPreferencesBinding binding;
private Context context; private Context context;
public PostsLayoutPreferencesDialogFragment(@NonNull final OnApplyListener onApplyListener) { public PostsLayoutPreferencesDialogFragment(final String layoutPreferenceKey,
final PostsLayoutPreferences preferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_POSTS_LAYOUT)); @NonNull final OnApplyListener onApplyListener) {
this.layoutPreferenceKey = layoutPreferenceKey;
final PostsLayoutPreferences preferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(layoutPreferenceKey));
this.preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences); this.preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences);
this.onApplyListener = onApplyListener; this.onApplyListener = onApplyListener;
} }
@ -50,7 +51,7 @@ public class PostsLayoutPreferencesDialogFragment extends DialogFragment {
.setPositiveButton(R.string.apply, (dialog, which) -> { .setPositiveButton(R.string.apply, (dialog, which) -> {
final PostsLayoutPreferences preferences = preferencesBuilder.build(); final PostsLayoutPreferences preferences = preferencesBuilder.build();
final String json = preferences.getJson(); final String json = preferences.getJson();
settingsHelper.putString(Constants.PREF_POSTS_LAYOUT, json); settingsHelper.putString(layoutPreferenceKey, json);
onApplyListener.onApply(preferences); onApplyListener.onApply(preferences);
}) })
.create(); .create();

View File

@ -417,7 +417,9 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
wasPaused = true; wasPaused = true;
captionState = bottomSheetBehavior.getState(); if (bottomSheetBehavior != null) {
captionState = bottomSheetBehavior.getState();
}
} }
@Override @Override
@ -494,7 +496,9 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
binding.postImage.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, binding.postImage.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)); ViewGroup.LayoutParams.MATCH_PARENT));
binding.postImage.requestLayout(); binding.postImage.requestLayout();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); if (bottomSheetBehavior != null) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
return; return;
} }
if (destView == binding.sliderParent) { if (destView == binding.sliderParent) {
@ -778,6 +782,9 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
private void setupCaption() { private void setupCaption() {
final CharSequence postCaption = feedModel.getPostCaption(); final CharSequence postCaption = feedModel.getPostCaption();
if (TextUtils.isEmpty(postCaption)) {
return;
}
binding.caption.addOnHashtagListener(autoLinkItem -> { binding.caption.addOnHashtagListener(autoLinkItem -> {
final NavController navController = NavHostFragment.findNavController(this); final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle();

View File

@ -331,7 +331,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
private void showPostsLayoutPreferences() { private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(preferences -> new Handler() final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(Constants.PREF_POSTS_LAYOUT, preferences -> new Handler()
.postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200)); .postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences"); fragment.show(getChildFragmentManager(), "posts_layout_preferences");
} }

View File

@ -24,16 +24,17 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -46,7 +47,6 @@ import com.facebook.imagepipeline.image.ImageInfo;
import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -54,24 +54,25 @@ import java.util.List;
import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter; import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher; import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher; import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentProfileBinding; import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.DownloadMethod;
@ -86,17 +87,17 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.HighlightsViewModel; import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.FriendshipService; import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "ProfileFragment"; private static final String TAG = "ProfileFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private CoordinatorLayout root;
@ -105,21 +106,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private String cookie; private String cookie;
private String username; private String username;
private ProfileModel profileModel; private ProfileModel profileModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter; private PostsAdapter postsAdapter;
private ActionMode actionMode; private ActionMode actionMode;
private Handler usernameSettingHandler; private Handler usernameSettingHandler;
private FriendshipService friendshipService; private FriendshipService friendshipService;
private StoriesService storiesService; private StoriesService storiesService;
private boolean shouldRefresh = true, hasStories = false; private boolean shouldRefresh = true;
private boolean hasNextPage; private boolean hasStories = false;
private String endCursor;
private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter; private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel; private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem; private MenuItem blockMenuItem;
private MenuItem restrictMenuItem; private MenuItem restrictMenuItem;
private boolean highlightsFetching;
private final Runnable usernameSettingRunnable = () -> { private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -165,36 +163,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return false; return false;
} }
}); });
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
@Override
public void onResult(final List<PostModel> result) {
binding.swipeRefreshLayout.setRefreshing(false);
if (result == null || result.isEmpty()) {
binding.privatePage1.setImageResource(R.drawable.ic_cancel);
binding.privatePage2.setText(R.string.empty_acc);
binding.privatePage.setVisibility(View.VISIBLE);
return;
} else {
binding.privatePage.setVisibility(View.GONE);
}
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue();
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>()
: new ArrayList<>(postModels);
if (isPullToRefresh) {
finalList = result;
isPullToRefresh = false;
} else {
finalList.addAll(result);
}
postsViewModel.getList().postValue(finalList);
final PostModel lastPostModel = result.get(result.size() - 1);
if (lastPostModel == null) return;
endCursor = lastPostModel.getEndCursor();
hasNextPage = lastPostModel.hasNextPage();
lastPostModel.setPageCursor(false, null);
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
Log.d(TAG, "action..."); Log.d(TAG, "action...");
if (isHashtag) { if (isHashtag) {
@ -213,6 +181,92 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
action.setUsername("@" + text); action.setUsername("@" + text);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}; };
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
@Override
public void onSliderClick(final FeedModel feedModel, final int position) {
openPostDialog(feedModel, null, null, position);
}
@Override
public void onCommentsClick(final FeedModel feedModel) {
final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(ProfileFragment.this).navigate(commentsAction);
}
@Override
public void onDownloadClick(final FeedModel feedModel) {
final Context context = getContext();
if (context == null) return;
if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
showDownloadDialog(feedModel);
return;
}
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(ProfileFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(ProfileFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private boolean postsSetupDone = false;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -267,7 +321,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu); inflater.inflate(R.menu.profile_menu, menu);
// favMenuItem = menu.findItem(R.id.favourites);
blockMenuItem = menu.findItem(R.id.block); blockMenuItem = menu.findItem(R.id.block);
if (blockMenuItem != null) { if (blockMenuItem != null) {
blockMenuItem.setVisible(false); blockMenuItem.setVisible(false);
@ -280,6 +333,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
if (item.getItemId() == R.id.restrict) { if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false; if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
@ -346,10 +403,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchProfileDetails(); fetchProfileDetails();
fetchPosts();
} }
@Override @Override
@ -359,9 +413,9 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (usernameSettingHandler != null) { if (usernameSettingHandler != null) {
usernameSettingHandler.removeCallbacks(usernameSettingRunnable); usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
} }
if (postsViewModel != null) { // if (postsViewModel != null) {
postsViewModel.getList().postValue(Collections.emptyList()); // postsViewModel.getList().postValue(Collections.emptyList());
} // }
if (highlightsViewModel != null) { if (highlightsViewModel != null) {
highlightsViewModel.getList().postValue(Collections.emptyList()); highlightsViewModel.getList().postValue(Collections.emptyList());
} }
@ -385,7 +439,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return; return;
} }
binding.swipeRefreshLayout.setEnabled(true); binding.swipeRefreshLayout.setEnabled(true);
setupPosts();
setupHighlights(); setupHighlights();
setupCommonListeners(); setupCommonListeners();
fetchUsername(); fetchUsername();
@ -444,87 +497,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return; return;
} }
if (!postsSetupDone) {
setupPosts();
} else {
binding.postsRecyclerView.refresh();
}
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId(); final String profileId = profileModel.getId();
final String myId = CookieUtils.getUserIdFromCookie(cookie); final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) { if (isLoggedIn) {
storiesService.getUserStory(profileId, fetchStoryAndHighlights(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;
}
}
@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);
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved);
} else {
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
}
} else {
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
} }
setupButtons(profileId, myId);
if (!profileId.equals(myId)) { if (!profileId.equals(myId)) {
binding.favCb.setVisibility(View.VISIBLE); binding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null; final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null;
@ -572,8 +556,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
: profileModel.getName()); : profileModel.getName());
CharSequence biography = profileModel.getBiography(); CharSequence biography = profileModel.getBiography();
// binding.mainBiography.setCaptionIsExpandable(true);
// binding.mainBiography.setCaptionIsExpanded(true);
if (TextUtils.hasMentions(biography)) { if (TextUtils.hasMentions(biography)) {
biography = TextUtils.getMentionText(biography); biography = TextUtils.getMentionText(biography);
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE); binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
@ -618,7 +600,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
binding.mainPosts.setVisibility(View.VISIBLE); binding.postsRecyclerView.setVisibility(View.VISIBLE);
fetchPosts(); fetchPosts();
} else { } else {
binding.mainFollowers.setClickable(false); binding.mainFollowers.setClickable(false);
@ -628,10 +610,94 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock); binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc); binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
binding.mainPosts.setVisibility(View.GONE); binding.postsRecyclerView.setVisibility(View.GONE);
} }
} }
private void setupButtons(final String profileId, final String myId) {
if (isLoggedIn) {
if (profileId.equals(myId)) {
binding.btnTagged.setVisibility(View.VISIBLE);
binding.btnSaved.setVisibility(View.VISIBLE);
binding.btnLiked.setVisibility(View.VISIBLE);
binding.btnDM.setVisibility(View.GONE);
binding.btnSaved.setText(R.string.saved);
return;
}
binding.btnTagged.setVisibility(View.GONE);
binding.btnSaved.setVisibility(View.GONE);
binding.btnLiked.setVisibility(View.GONE);
binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
binding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
binding.btnFollow.setText(R.string.unfollow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
binding.btnFollow.setText(R.string.cancel);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
binding.btnFollow.setText(R.string.follow);
binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24);
}
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
}
}
return;
}
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
}
private void fetchStoryAndHighlights(final String profileId) {
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;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
new HighlightsFetcher(profileId,
result -> {
highlightsFetching = false;
if (result != null) {
binding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else binding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setupCommonListeners() { private void setupCommonListeners() {
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
@ -781,62 +847,60 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void setupPosts() { private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); binding.postsRecyclerView.setViewModelStoreOwner(this)
final Context context = getContext(); .setLifeCycleOwner(this)
if (context == null) return; .setPostFetchService(new ProfilePostFetchService(profileModel))
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT)))
binding.mainPosts.setLayoutManager(layoutManager); .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); .setFeedItemCallback(feedItemCallback)
postsAdapter = new PostsAdapter((postModel, position) -> { .init();
if (postsAdapter.isSelecting()) { binding.swipeRefreshLayout.setRefreshing(true);
if (actionMode == null) return; postsSetupDone = true;
final String title = getString(R.string.number_selected, // postsAdapter = new PostsAdapter((postModel, position) -> {
postsAdapter.getSelectedModels().size()); // if (postsAdapter.isSelecting()) {
actionMode.setTitle(title); // if (actionMode == null) return;
return; // final String title = getString(R.string.number_selected,
} // postsAdapter.getSelectedModels().size());
if (checkAndResetAction()) return; // actionMode.setTitle(title);
final List<PostModel> postModels = postsViewModel.getList().getValue(); // return;
if (postModels == null || postModels.size() == 0) return; // }
if (postModels.get(0) == null) return; // if (checkAndResetAction()) return;
final String postId = isLoggedIn ? postModels.get(0).getPostId() : postModels.get(0).getShortCode(); // final List<PostModel> postModels = postsViewModel.getList().getValue();
final boolean isId = isLoggedIn && postId != null; // if (postModels == null || postModels.size() == 0) return;
final String[] idsOrShortCodes = new String[postModels.size()]; // if (postModels.get(0) == null) return;
for (int i = 0; i < postModels.size(); i++) { // final String postId = isLoggedIn ? postModels.get(0).getPostId() : postModels.get(0).getShortCode();
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId() // final boolean isId = isLoggedIn && postId != null;
: postModels.get(i).getShortCode(); // final String[] idsOrShortCodes = new String[postModels.size()];
} // for (int i = 0; i < postModels.size(); i++) {
final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment( // idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
position, // : postModels.get(i).getShortCode();
idsOrShortCodes, // }
isId); // final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
NavHostFragment.findNavController(this).navigate(action); // position,
// idsOrShortCodes,
// isId);
// NavHostFragment.findNavController(this).navigate(action);
//
// }, (model, position) -> {
// if (!postsAdapter.isSelecting()) {
// checkAndResetAction();
// return true;
// }
// if (onBackPressedCallback.isEnabled()) {
// return true;
// }
// final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
// onBackPressedCallback.setEnabled(true);
// actionMode = fragmentActivity.startActionMode(multiSelectAction);
// final String title = getString(R.string.number_selected, 1);
// actionMode.setTitle(title);
// onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
// return true;
// });
}
}, (model, position) -> { private void updateSwipeRefreshState() {
if (!postsAdapter.isSelecting()) { binding.swipeRefreshLayout.setRefreshing(binding.postsRecyclerView.isFetching() || highlightsFetching);
checkAndResetAction();
return true;
}
if (onBackPressedCallback.isEnabled()) {
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedCallback.setEnabled(true);
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
binding.mainPosts.setAdapter(postsAdapter);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!hasNextPage) return;
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
});
binding.mainPosts.addOnScrollListener(lazyLoader);
} }
private void setupHighlights() { private void setupHighlights() {
@ -855,22 +919,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); // stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener) // currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener)
.setUsername(profileModel.getUsername()) // .setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
currentlyExecuting.cancel(true);
} catch (final Exception e) {
if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
Log.e(TAG, "", e);
}
}
} }
private boolean checkAndResetAction() { private boolean checkAndResetAction() {
@ -887,4 +940,60 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
return true; return true;
} }
private void navigateToProfile(final String username) {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("username", username);
navController.navigate(R.id.action_global_profileFragment, bundle);
}
private void showDownloadDialog(final FeedModel feedModel) {
final Context context = getContext();
if (context == null) return;
DownloadUtils.download(context, feedModel);
// switch (feedModel.getItemType()) {
// case MEDIA_TYPE_IMAGE:
// case MEDIA_TYPE_VIDEO:
// break;
// case MEDIA_TYPE_SLIDER:
// break;
// }
// final List<ViewerPostModel> postModelsToDownload = new ArrayList<>();
// // if (!session) {
// 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)));
}
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_PROFILE_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.postsRecyclerView.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
} }

View File

@ -15,10 +15,10 @@ public final class PostsLayoutPreferences {
public static class Builder { public static class Builder {
private PostsLayoutType type = PostsLayoutType.GRID; private PostsLayoutType type = PostsLayoutType.GRID;
private int colCount = 2; private int colCount = 3;
private boolean isAvatarVisible = false; private boolean isAvatarVisible = true;
private boolean isNameVisible = false; private boolean isNameVisible = false;
private ProfilePicSize profilePicSize = ProfilePicSize.REGULAR; private ProfilePicSize profilePicSize = ProfilePicSize.SMALL;
private boolean hasRoundedCorners = true; private boolean hasRoundedCorners = true;
private boolean hasGap = true; private boolean hasGap = true;
@ -87,6 +87,9 @@ public final class PostsLayoutPreferences {
} }
public Builder mergeFrom(final PostsLayoutPreferences preferences) { public Builder mergeFrom(final PostsLayoutPreferences preferences) {
if (preferences == null) {
return this;
}
setColCount(preferences.getColCount()); setColCount(preferences.getColCount());
setAvatarVisible(preferences.isAvatarVisible()); setAvatarVisible(preferences.isAvatarVisible());
setNameVisible(preferences.isNameVisible()); setNameVisible(preferences.isNameVisible());

View File

@ -1,11 +1,17 @@
package awais.instagrabber.repositories; package awais.instagrabber.repositories;
import java.util.Map;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.Path; import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface ProfileRepository { public interface ProfileRepository {
@GET("api/v1/users/{uid}/info/") @GET("api/v1/users/{uid}/info/")
Call<String> getUserInfo(@Path("uid") final String uid); Call<String> getUserInfo(@Path("uid") final String uid);
@GET("/graphql/query/")
Call<String> fetch(@QueryMap Map<String, String> queryMap);
} }

View File

@ -4,12 +4,12 @@ import java.util.List;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
public class FeedFetchResponse { public class PostsFetchResponse {
private List<FeedModel> feedModels; private List<FeedModel> feedModels;
private boolean hasNextPage; private boolean hasNextPage;
private String nextCursor; private String nextCursor;
public FeedFetchResponse(final List<FeedModel> feedModels, final boolean hasNextPage, final String nextCursor) { public PostsFetchResponse(final List<FeedModel> feedModels, final boolean hasNextPage, final String nextCursor) {
this.feedModels = feedModels; this.feedModels = feedModels;
this.hasNextPage = hasNextPage; this.hasNextPage = hasNextPage;
this.nextCursor = nextCursor; this.nextCursor = nextCursor;

View File

@ -88,4 +88,5 @@ public final class Constants {
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 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 SHARED_PREFERENCES_NAME = "settings";
public static final String PREF_POSTS_LAYOUT = "posts_layout"; public static final String PREF_POSTS_LAYOUT = "posts_layout";
public static final String PREF_PROFILE_POSTS_LAYOUT = "profile_posts_layout";
} }

View File

@ -31,6 +31,7 @@ import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.PREF_DARK_THEME; 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_LIGHT_THEME;
import static awais.instagrabber.utils.Constants.PREF_POSTS_LAYOUT; import static awais.instagrabber.utils.Constants.PREF_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_PROFILE_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION; 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.SHOW_QUICK_ACCESS_DIALOG;
import static awais.instagrabber.utils.Constants.SKIPPED_VERSION; import static awais.instagrabber.utils.Constants.SKIPPED_VERSION;
@ -114,7 +115,7 @@ public final class SettingsHelper {
@StringDef( @StringDef(
{APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT, {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, PREF_POSTS_LAYOUT}) DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT})
public @interface StringSettings {} public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,

View File

@ -25,7 +25,7 @@ import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.FeedRepository; import awais.instagrabber.repositories.FeedRepository;
import awais.instagrabber.repositories.responses.FeedFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
@ -58,7 +58,7 @@ public class FeedService extends BaseService {
public void fetch(final int maxItemsToLoad, public void fetch(final int maxItemsToLoad,
final String cursor, final String cursor,
final ServiceCallback<FeedFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
if (loadFromMock) { if (loadFromMock) {
final Handler handler = new Handler(); final Handler handler = new Handler();
handler.postDelayed(() -> { handler.postDelayed(() -> {
@ -96,9 +96,9 @@ public class FeedService extends BaseService {
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try { try {
// Log.d(TAG, "onResponse: body: " + response.body()); // Log.d(TAG, "onResponse: body: " + response.body());
final FeedFetchResponse feedFetchResponse = parseResponse(response); final PostsFetchResponse postsFetchResponse = parseResponse(response);
if (callback != null) { if (callback != null) {
callback.onSuccess(feedFetchResponse); callback.onSuccess(postsFetchResponse);
} }
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
@ -119,16 +119,16 @@ public class FeedService extends BaseService {
} }
@NonNull @NonNull
private FeedFetchResponse parseResponse(@NonNull final Response<String> response) throws JSONException { private PostsFetchResponse parseResponse(@NonNull final Response<String> response) throws JSONException {
if (TextUtils.isEmpty(response.body())) { if (TextUtils.isEmpty(response.body())) {
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code()); Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
return new FeedFetchResponse(Collections.emptyList(), false, null); return new PostsFetchResponse(Collections.emptyList(), false, null);
} }
return parseResponseBody(response.body()); return parseResponseBody(response.body());
} }
@NonNull @NonNull
private FeedFetchResponse parseResponseBody(@NonNull final String body) private PostsFetchResponse parseResponseBody(@NonNull final String body)
throws JSONException { throws JSONException {
final List<FeedModel> feedModels = new ArrayList<>(); final List<FeedModel> feedModels = new ArrayList<>();
final JSONObject timelineFeed = new JSONObject(body) final JSONObject timelineFeed = new JSONObject(body)
@ -161,7 +161,6 @@ public class FeedService extends BaseService {
final String displayUrl = feedItem.optString("display_url"); final String displayUrl = feedItem.optString("display_url");
if (TextUtils.isEmpty(displayUrl)) continue; if (TextUtils.isEmpty(displayUrl)) continue;
final String resourceUrl; final String resourceUrl;
if (isVideo) { if (isVideo) {
resourceUrl = feedItem.getString("video_url"); resourceUrl = feedItem.getString("video_url");
} else { } else {
@ -265,7 +264,7 @@ public class FeedService extends BaseService {
final FeedModel feedModel = feedModelBuilder.build(); final FeedModel feedModel = feedModelBuilder.build();
feedModels.add(feedModel); feedModels.add(feedModel);
} }
return new FeedFetchResponse(feedModels, hasNextPage, endCursor); return new PostsFetchResponse(feedModels, hasNextPage, endCursor);
} }
@NonNull @NonNull

View File

@ -4,12 +4,26 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
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.ProfileRepository; import awais.instagrabber.repositories.ProfileRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.UserInfo; import awais.instagrabber.repositories.responses.UserInfo;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -19,6 +33,7 @@ public class ProfileService extends BaseService {
private static final String TAG = "ProfileService"; private static final String TAG = "ProfileService";
private final ProfileRepository repository; private final ProfileRepository repository;
private final ProfileRepository wwwRepository;
private static ProfileService instance; private static ProfileService instance;
@ -26,7 +41,11 @@ public class ProfileService extends BaseService {
final Retrofit retrofit = getRetrofitBuilder() final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com") .baseUrl("https://i.instagram.com")
.build(); .build();
final Retrofit wwwRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
repository = retrofit.create(ProfileRepository.class); repository = retrofit.create(ProfileRepository.class);
wwwRepository = wwwRetrofit.create(ProfileRepository.class);
} }
public static ProfileService getInstance() { public static ProfileService getInstance() {
@ -66,4 +85,201 @@ public class ProfileService extends BaseService {
} }
}); });
} }
public void fetchPosts(final ProfileModel profileModel,
final int postsPerPage,
final String cursor,
final ServiceCallback<PostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "18a7b935ab438c4514b1f742d8fa07a7");
queryMap.put("variables", "{" +
"\"id\":\"" + profileModel.getId() + "\"," +
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (cursor == null ? "" : cursor) + "\"" +
"}");
final Call<String> request = wwwRepository.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 PostsFetchResponse postsFetchResponse = parseResponse(profileModel, response);
if (callback != null) {
callback.onSuccess(postsFetchResponse);
}
} 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);
}
}
});
}
private PostsFetchResponse parseResponse(final ProfileModel profileModel, 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 PostsFetchResponse(Collections.emptyList(), false, null);
}
return parseResponseBody(profileModel, response.body());
}
private PostsFetchResponse parseResponseBody(final ProfileModel profileModel, final String body) throws JSONException {
// Log.d(TAG, "parseResponseBody: body: " + body);
final List<FeedModel> feedModels = new ArrayList<>();
// return new FeedFetchResponse(feedModels, false, null);
final JSONObject mediaPosts = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("edge_owner_to_timeline_media");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = mediaPosts.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 edges = mediaPosts.getJSONArray("edges");
for (int i = 0; i < edges.length(); ++i) {
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node");
final String mediaType = mediaNode.optString("__typename");
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType))
continue;
final boolean isVideo = mediaNode.getBoolean("is_video");
final long videoViews = mediaNode.optLong("video_view_count", 0);
final String displayUrl = mediaNode.optString("display_url");
if (TextUtils.isEmpty(displayUrl)) continue;
final String resourceUrl;
if (isVideo) {
resourceUrl = mediaNode.getString("video_url");
} else {
resourceUrl = mediaNode.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(mediaNode) : displayUrl;
}
JSONObject tempJsonObject = mediaNode.optJSONObject("edge_media_preview_comment");
final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0;
tempJsonObject = mediaNode.optJSONObject("edge_media_preview_like");
final long likesCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0;
tempJsonObject = mediaNode.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 = mediaNode.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 = mediaNode.optJSONObject("dimensions");
if (dimensions != null) {
height = dimensions.optInt("height");
width = dimensions.optInt("width");
}
String thumbnailUrl = null;
try {
thumbnailUrl = mediaNode.getJSONArray("display_resources")
.getJSONObject(0)
.getString("src");
} catch (JSONException ignored) {}
final FeedModel.Builder builder = new FeedModel.Builder()
.setProfileModel(profileModel)
.setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE)
.setViewCount(videoViews)
.setPostId(mediaNode.getString(Constants.EXTRAS_ID))
.setDisplayUrl(resourceUrl)
.setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl)
.setShortCode(mediaNode.getString(Constants.EXTRAS_SHORTCODE))
.setPostCaption(captionText)
.setCommentsCount(commentsCount)
.setTimestamp(mediaNode.optLong("taken_at_timestamp", -1))
.setLiked(mediaNode.getBoolean("viewer_has_liked"))
.setBookmarked(mediaNode.getBoolean("viewer_has_saved"))
.setLikesCount(likesCount)
.setLocationName(locationName)
.setLocationId(locationId)
.setImageHeight(height)
.setImageWidth(width);
final boolean isSlider = "GraphSidecar".equals(mediaType) && mediaNode.has("edge_sidecar_to_children");
if (isSlider) {
builder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER);
final JSONObject sidecar = mediaNode.optJSONObject("edge_sidecar_to_children");
if (sidecar != null) {
final JSONArray children = sidecar.optJSONArray("edges");
if (children != null) {
final List<PostChild> sliderItems = getSliderItems(children);
builder.setSliderItems(sliderItems);
}
}
}
final FeedModel feedModel = builder.build();
feedModels.add(feedModel);
// DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model);
}
return new PostsFetchResponse(feedModels, hasNextPage, endCursor);
}
@NonNull
private List<PostChild> getSliderItems(final JSONArray children) throws JSONException {
final List<PostChild> sliderItems = new ArrayList<>();
for (int j = 0; j < children.length(); ++j) {
final JSONObject childNode = children.optJSONObject(j).getJSONObject("node");
final boolean isChildVideo = childNode.optBoolean("is_video");
int height = 0;
int width = 0;
final JSONObject dimensions = childNode.optJSONObject("dimensions");
if (dimensions != null) {
height = dimensions.optInt("height");
width = dimensions.optInt("width");
}
String thumbnailUrl = null;
try {
thumbnailUrl = childNode.getJSONArray("display_resources")
.getJSONObject(0)
.getString("src");
} catch (JSONException ignored) {}
final PostChild sliderItem = new PostChild.Builder()
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
: MediaItemType.MEDIA_TYPE_IMAGE)
.setPostId(childNode.getString(Constants.EXTRAS_ID))
.setDisplayUrl(isChildVideo ? childNode.getString("video_url")
: childNode.getString("display_url"))
.setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl
: childNode.getString("display_url"))
.setVideoViews(childNode.optLong("video_view_count", 0))
.setHeight(height)
.setWidth(width)
.build();
// Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem);
sliderItems.add(sliderItem);
}
return sliderItems;
}
} }

View File

@ -37,10 +37,4 @@
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/item_feed_photo" /> tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!--<androidx.fragment.app.FragmentContainerView-->
<!-- android:id="@+id/frag_container"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout> </awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>

View File

@ -272,19 +272,33 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView <awais.instagrabber.customviews.PostsRecyclerView
android:id="@+id/mainPosts" android:id="@+id/posts_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
tools:listitem="@layout/item_post" /> tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!--<androidx.swiperefreshlayout.widget.SwipeRefreshLayout-->
<!-- android:id="@+id/swipeRefreshLayout"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">-->
<!-- <androidx.recyclerview.widget.RecyclerView-->
<!-- android:id="@+id/mainPosts"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:clipToPadding="false"-->
<!-- tools:listitem="@layout/item_post" />-->
<!--</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>-->
<LinearLayout <LinearLayout
android:id="@+id/privatePage" android:id="@+id/privatePage"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -3,6 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/layout" android:id="@+id/layout"
android:title="Layout" android:title="@string/layout"
app:showAsAction="never" /> app:showAsAction="never" />
</menu> </menu>

View File

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<!--<item-->
<!-- android:id="@+id/favourites"--> <item
<!-- android:enabled="true"--> android:id="@+id/layout"
<!-- android:icon="@drawable/ic_star_24"--> android:title="@string/layout"
<!-- android:title="@string/title_favorites"--> app:showAsAction="never" />
<!-- android:visible="false"-->
<!-- app:showAsAction="ifRoom" />-->
<item <item
android:id="@+id/block" android:id="@+id/block"

View File

@ -324,6 +324,7 @@
<string name="downloader_downloading_child">Download item %d of %d</string> <string name="downloader_downloading_child">Download item %d of %d</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="comment">Comment</string> <string name="comment">Comment</string>
<string name="layout">Layout</string>
<plurals name="likes_count"> <plurals name="likes_count">
<item quantity="one">%d like</item> <item quantity="one">%d like</item>
<item quantity="other">%d likes</item> <item quantity="other">%d likes</item>