Add Posts view to Hashtag fragment
This commit is contained in:
parent
6d9dadc0cd
commit
2931f2d3ab
@ -0,0 +1,56 @@
|
|||||||
|
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.HashtagModel;
|
||||||
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import awais.instagrabber.webservices.TagsService;
|
||||||
|
import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse;
|
||||||
|
|
||||||
|
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
||||||
|
private final TagsService tagsService;
|
||||||
|
private final HashtagModel hashtagModel;
|
||||||
|
private String nextMaxId;
|
||||||
|
private boolean moreAvailable;
|
||||||
|
|
||||||
|
public HashtagPostFetchService(final HashtagModel hashtagModel) {
|
||||||
|
this.hashtagModel = hashtagModel;
|
||||||
|
tagsService = TagsService.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
|
||||||
|
tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback<TagPostsFetchResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final TagPostsFetchResponse result) {
|
||||||
|
if (result == null) return;
|
||||||
|
nextMaxId = result.getNextMaxId();
|
||||||
|
moreAvailable = result.isMoreAvailable();
|
||||||
|
if (fetchListener != null) {
|
||||||
|
fetchListener.onResult(result.getItems());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
// Log.e(TAG, "onFailure: ", t);
|
||||||
|
if (fetchListener != null) {
|
||||||
|
fetchListener.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
nextMaxId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNextPage() {
|
||||||
|
return moreAvailable;
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,9 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
private GridSpacingItemDecoration gridSpacingItemDecoration;
|
private GridSpacingItemDecoration gridSpacingItemDecoration;
|
||||||
private RecyclerLazyLoaderAtBottom lazyLoader;
|
private RecyclerLazyLoaderAtBottom lazyLoader;
|
||||||
private FeedAdapterV2.FeedItemCallback feedItemCallback;
|
private FeedAdapterV2.FeedItemCallback feedItemCallback;
|
||||||
|
private boolean shouldScrollToTop;
|
||||||
|
|
||||||
|
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
|
||||||
|
|
||||||
private final FetchListener<List<FeedModel>> fetchListener = new FetchListener<List<FeedModel>>() {
|
private final FetchListener<List<FeedModel>> fetchListener = new FetchListener<List<FeedModel>>() {
|
||||||
@Override
|
@Override
|
||||||
@ -51,6 +54,7 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
final int currentPage = lazyLoader.getCurrentPage();
|
final int currentPage = lazyLoader.getCurrentPage();
|
||||||
if (currentPage == 0) {
|
if (currentPage == 0) {
|
||||||
feedViewModel.getList().postValue(result);
|
feedViewModel.getList().postValue(result);
|
||||||
|
shouldScrollToTop = true;
|
||||||
dispatchFetchStatus();
|
dispatchFetchStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -66,7 +70,6 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
Log.e(TAG, "onFailure: ", t);
|
Log.e(TAG, "onFailure: ", t);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
|
|
||||||
|
|
||||||
public PostsRecyclerView(@NonNull final Context context) {
|
public PostsRecyclerView(@NonNull final Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -158,7 +161,11 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
|
|
||||||
private void initSelf() {
|
private void initSelf() {
|
||||||
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
|
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
|
||||||
feedViewModel.getList().observe(lifeCycleOwner, feedAdapter::submitList);
|
feedViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
|
||||||
|
if (!shouldScrollToTop) return;
|
||||||
|
smoothScrollToPosition(0);
|
||||||
|
shouldScrollToTop = false;
|
||||||
|
}));
|
||||||
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
||||||
if (layoutPreferences.getHasGap()) {
|
if (layoutPreferences.getHasGap()) {
|
||||||
addItemDecoration(gridSpacingItemDecoration);
|
addItemDecoration(gridSpacingItemDecoration);
|
||||||
|
@ -4,24 +4,27 @@ import android.content.Context;
|
|||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
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.core.content.PermissionChecker;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
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.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
@ -29,73 +32,67 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|||||||
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.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.activities.MainActivity;
|
import awais.instagrabber.activities.MainActivity;
|
||||||
import awais.instagrabber.adapters.PostsAdapter;
|
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||||
import awais.instagrabber.asyncs.HashtagFetcher;
|
import awais.instagrabber.asyncs.HashtagFetcher;
|
||||||
import awais.instagrabber.asyncs.PostsFetcher;
|
import awais.instagrabber.asyncs.HashtagPostFetchService;
|
||||||
import awais.instagrabber.customviews.PrimaryActionModeCallback;
|
import awais.instagrabber.customviews.PrimaryActionModeCallback;
|
||||||
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.FragmentHashtagBinding;
|
import awais.instagrabber.databinding.FragmentHashtagBinding;
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
|
||||||
|
import awais.instagrabber.fragments.main.FeedFragmentDirections;
|
||||||
|
import awais.instagrabber.models.FeedModel;
|
||||||
import awais.instagrabber.models.HashtagModel;
|
import awais.instagrabber.models.HashtagModel;
|
||||||
import awais.instagrabber.models.PostModel;
|
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||||
import awais.instagrabber.models.StoryModel;
|
import awais.instagrabber.models.StoryModel;
|
||||||
import awais.instagrabber.models.enums.DownloadMethod;
|
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
import awais.instagrabber.models.enums.PostItemType;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.DataBox;
|
import awais.instagrabber.utils.DataBox;
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
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.PostsViewModel;
|
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
import awais.instagrabber.webservices.StoriesService;
|
import awais.instagrabber.webservices.StoriesService;
|
||||||
import awais.instagrabber.webservices.TagsService;
|
import awais.instagrabber.webservices.TagsService;
|
||||||
import awaisomereport.LogCollector;
|
import awaisomereport.LogCollector;
|
||||||
|
|
||||||
|
import static androidx.core.content.PermissionChecker.checkSelfPermission;
|
||||||
|
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
|
||||||
import static awais.instagrabber.utils.Utils.logCollector;
|
import static awais.instagrabber.utils.Utils.logCollector;
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||||
private static final String TAG = "HashTagFragment";
|
private static final String TAG = "HashTagFragment";
|
||||||
|
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
||||||
|
|
||||||
public static final String ARG_HASHTAG = "hashtag";
|
public static final String ARG_HASHTAG = "hashtag";
|
||||||
|
|
||||||
private MainActivity fragmentActivity;
|
private MainActivity fragmentActivity;
|
||||||
private FragmentHashtagBinding binding;
|
private FragmentHashtagBinding binding;
|
||||||
private NestedCoordinatorLayout root;
|
private NestedCoordinatorLayout root;
|
||||||
private boolean shouldRefresh = true, hasStories = false;
|
private boolean shouldRefresh = true;
|
||||||
|
private boolean hasStories = false;
|
||||||
private String hashtag;
|
private String hashtag;
|
||||||
private HashtagModel hashtagModel;
|
private HashtagModel hashtagModel;
|
||||||
private PostsViewModel postsViewModel;
|
|
||||||
private PostsAdapter postsAdapter;
|
|
||||||
private ActionMode actionMode;
|
private ActionMode actionMode;
|
||||||
private StoriesService storiesService;
|
private StoriesService storiesService;
|
||||||
private boolean hasNextPage;
|
|
||||||
private String endCursor;
|
|
||||||
private AsyncTask<?, ?, ?> currentlyExecuting;
|
private AsyncTask<?, ?, ?> currentlyExecuting;
|
||||||
private boolean isLoggedIn;
|
private boolean isLoggedIn;
|
||||||
private TagsService tagsService;
|
private TagsService tagsService;
|
||||||
private boolean isPullToRefresh;
|
private boolean storiesFetching;
|
||||||
|
|
||||||
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
||||||
@Override
|
@Override
|
||||||
public void handleOnBackPressed() {
|
public void handleOnBackPressed() {
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
remove();
|
remove();
|
||||||
if (postsAdapter == null) return;
|
// if (postsAdapter == null) return;
|
||||||
postsAdapter.clearSelection();
|
// postsAdapter.clearSelection();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
|
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
|
||||||
@ -109,47 +106,132 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
|
||||||
if (item.getItemId() == R.id.action_download) {
|
if (item.getItemId() == R.id.action_download) {
|
||||||
if (postsAdapter == null || hashtag == null) {
|
// if (postsAdapter == null || hashtag == null) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
final Context context = getContext();
|
// final Context context = getContext();
|
||||||
if (context == null) return false;
|
// if (context == null) return false;
|
||||||
DownloadUtils.batchDownload(context,
|
// DownloadUtils.batchDownload(context,
|
||||||
hashtag,
|
// hashtag,
|
||||||
DownloadMethod.DOWNLOAD_MAIN,
|
// DownloadMethod.DOWNLOAD_MAIN,
|
||||||
postsAdapter.getSelectedModels());
|
// postsAdapter.getSelectedModels());
|
||||||
checkAndResetAction();
|
// checkAndResetAction();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
|
// 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) return;
|
||||||
|
// 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);
|
||||||
|
// }
|
||||||
|
// finalList.addAll(result);
|
||||||
|
// postsViewModel.getList().postValue(finalList);
|
||||||
|
// PostModel model = null;
|
||||||
|
// if (!result.isEmpty()) {
|
||||||
|
// model = result.get(result.size() - 1);
|
||||||
|
// }
|
||||||
|
// if (model == null) return;
|
||||||
|
// endCursor = model.getEndCursor();
|
||||||
|
// hasNextPage = model.hasNextPage();
|
||||||
|
// model.setPageCursor(false, null);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(final List<PostModel> result) {
|
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
|
||||||
if (result == null) return;
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
finalList.addAll(result);
|
|
||||||
postsViewModel.getList().postValue(finalList);
|
@Override
|
||||||
PostModel model = null;
|
public void onSliderClick(final FeedModel feedModel, final int position) {
|
||||||
if (!result.isEmpty()) {
|
openPostDialog(feedModel, null, null, position);
|
||||||
model = result.get(result.size() - 1);
|
|
||||||
}
|
}
|
||||||
if (model == null) return;
|
|
||||||
endCursor = model.getEndCursor();
|
@Override
|
||||||
hasNextPage = model.hasNextPage();
|
public void onCommentsClick(final FeedModel feedModel) {
|
||||||
model.setPageCursor(false, null);
|
final NavDirections commentsAction = HashTagFragmentDirections.actionGlobalCommentsViewerFragment(
|
||||||
|
feedModel.getShortCode(),
|
||||||
|
feedModel.getPostId(),
|
||||||
|
feedModel.getProfileModel().getId()
|
||||||
|
);
|
||||||
|
NavHostFragment.findNavController(HashTagFragment.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(HashTagFragment.this).navigate(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationClick(final FeedModel feedModel) {
|
||||||
|
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
|
||||||
|
NavHostFragment.findNavController(HashTagFragment.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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,6 +241,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
fragmentActivity = (MainActivity) requireActivity();
|
fragmentActivity = (MainActivity) requireActivity();
|
||||||
tagsService = TagsService.getInstance();
|
tagsService = TagsService.getInstance();
|
||||||
storiesService = StoriesService.getInstance();
|
storiesService = StoriesService.getInstance();
|
||||||
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -183,9 +266,8 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh() {
|
public void onRefresh() {
|
||||||
isPullToRefresh = true;
|
binding.posts.refresh();
|
||||||
endCursor = null;
|
fetchStories();
|
||||||
fetchHashtagModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -195,11 +277,17 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
|
||||||
super.onDestroy();
|
inflater.inflate(R.menu.topic_posts_menu, menu);
|
||||||
if (postsViewModel != null) {
|
|
||||||
postsViewModel.getList().postValue(Collections.emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.layout) {
|
||||||
|
showPostsLayoutPreferences();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
@ -208,114 +296,96 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
|
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
|
||||||
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
|
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
|
||||||
hashtag = fragmentArgs.getHashtag();
|
hashtag = fragmentArgs.getHashtag();
|
||||||
// setTitle();
|
|
||||||
setupPosts();
|
|
||||||
fetchHashtagModel();
|
fetchHashtagModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPosts() {
|
|
||||||
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
|
|
||||||
final Context context = getContext();
|
|
||||||
if (context == null) return;
|
|
||||||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
|
|
||||||
binding.mainPosts.setLayoutManager(layoutManager);
|
|
||||||
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
|
|
||||||
postsAdapter = new PostsAdapter((postModel, position) -> {
|
|
||||||
if (postsAdapter.isSelecting()) {
|
|
||||||
if (actionMode == null) return;
|
|
||||||
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
|
|
||||||
actionMode.setTitle(title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (checkAndResetAction()) return;
|
|
||||||
final List<PostModel> postModels = postsViewModel.getList().getValue();
|
|
||||||
if (postModels == null || postModels.size() == 0) return;
|
|
||||||
if (postModels.get(0) == null) return;
|
|
||||||
final String postId = postModels.get(0).getPostId();
|
|
||||||
final boolean isId = postId != null && isLoggedIn;
|
|
||||||
final String[] idsOrShortCodes = new String[postModels.size()];
|
|
||||||
for (int i = 0; i < postModels.size(); i++) {
|
|
||||||
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
|
|
||||||
: postModels.get(i).getShortCode();
|
|
||||||
}
|
|
||||||
final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment(
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
|
|
||||||
binding.mainPosts.setAdapter(postsAdapter);
|
|
||||||
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
|
||||||
if (!hasNextPage || getContext() == null) return;
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
fetchPosts();
|
|
||||||
endCursor = null;
|
|
||||||
});
|
|
||||||
binding.mainPosts.addOnScrollListener(lazyLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchHashtagModel() {
|
private void fetchHashtagModel() {
|
||||||
stopCurrentExecutor();
|
stopCurrentExecutor();
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
binding.swipeRefreshLayout.setRefreshing(true);
|
||||||
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> {
|
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> {
|
||||||
final Context context = getContext();
|
|
||||||
if (context == null) return;
|
|
||||||
hashtagModel = result;
|
hashtagModel = result;
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
binding.swipeRefreshLayout.setRefreshing(false);
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
if (hashtagModel == null) {
|
if (hashtagModel == null) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
setTitle();
|
setTitle();
|
||||||
fetchPosts();
|
setHashtagDetails();
|
||||||
|
setupPosts();
|
||||||
|
fetchStories();
|
||||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchPosts() {
|
private void setupPosts() {
|
||||||
stopCurrentExecutor();
|
binding.posts.setViewModelStoreOwner(this)
|
||||||
|
.setLifeCycleOwner(this)
|
||||||
|
.setPostFetchService(new HashtagPostFetchService(hashtagModel))
|
||||||
|
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_HASHTAG_POSTS_LAYOUT)))
|
||||||
|
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
|
||||||
|
.setFeedItemCallback(feedItemCallback)
|
||||||
|
.init();
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
binding.swipeRefreshLayout.setRefreshing(true);
|
||||||
if (TextUtils.isEmpty(hashtag)) return;
|
// postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
|
||||||
currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener)
|
// final Context context = getContext();
|
||||||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
// if (context == null) return;
|
||||||
final Context context = getContext();
|
// final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
|
||||||
if (context == null) return;
|
// binding.mainPosts.setLayoutManager(layoutManager);
|
||||||
if (isLoggedIn) {
|
// binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
|
||||||
storiesService.getUserStory(hashtagModel.getName(),
|
// postsAdapter = new PostsAdapter((postModel, position) -> {
|
||||||
null,
|
// if (postsAdapter.isSelecting()) {
|
||||||
false,
|
// if (actionMode == null) return;
|
||||||
true,
|
// final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
|
||||||
false,
|
// actionMode.setTitle(title);
|
||||||
new ServiceCallback<List<StoryModel>>() {
|
// return;
|
||||||
@Override
|
// }
|
||||||
public void onSuccess(final List<StoryModel> storyModels) {
|
// if (checkAndResetAction()) return;
|
||||||
if (storyModels != null && !storyModels.isEmpty()) {
|
// final List<PostModel> postModels = postsViewModel.getList().getValue();
|
||||||
binding.mainHashtagImage.setStoriesBorder();
|
// if (postModels == null || postModels.size() == 0) return;
|
||||||
hasStories = true;
|
// if (postModels.get(0) == null) return;
|
||||||
}
|
// final String postId = postModels.get(0).getPostId();
|
||||||
|
// final boolean isId = postId != null && isLoggedIn;
|
||||||
|
// final String[] idsOrShortCodes = new String[postModels.size()];
|
||||||
|
// for (int i = 0; i < postModels.size(); i++) {
|
||||||
|
// idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
|
||||||
|
// : postModels.get(i).getShortCode();
|
||||||
|
// }
|
||||||
|
// final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment(
|
||||||
|
// 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;
|
||||||
|
// });
|
||||||
|
// postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList);
|
||||||
|
// binding.mainPosts.setAdapter(postsAdapter);
|
||||||
|
// final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
||||||
|
// if (!hasNextPage || getContext() == null) return;
|
||||||
|
// binding.swipeRefreshLayout.setRefreshing(true);
|
||||||
|
// fetchPosts();
|
||||||
|
// endCursor = null;
|
||||||
|
// });
|
||||||
|
// binding.mainPosts.addOnScrollListener(lazyLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setHashtagDetails() {
|
||||||
public void onFailure(final Throwable t) {
|
if (isLoggedIn) {
|
||||||
Log.e(TAG, "Error", t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
binding.btnFollowTag.setVisibility(View.VISIBLE);
|
binding.btnFollowTag.setVisibility(View.VISIBLE);
|
||||||
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
|
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
|
||||||
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
|
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
|
||||||
@ -422,11 +492,39 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
binding.mainTagPostCount.setText(span);
|
binding.mainTagPostCount.setText(span);
|
||||||
binding.mainTagPostCount.setVisibility(View.VISIBLE);
|
binding.mainTagPostCount.setVisibility(View.VISIBLE);
|
||||||
binding.mainHashtagImage.setOnClickListener(v -> {
|
binding.mainHashtagImage.setOnClickListener(v -> {
|
||||||
if (hasStories) {
|
if (!hasStories) return;
|
||||||
// show stories
|
// show stories
|
||||||
final NavDirections action = HashTagFragmentDirections
|
final NavDirections action = HashTagFragmentDirections
|
||||||
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
|
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
|
||||||
NavHostFragment.findNavController(this).navigate(action);
|
NavHostFragment.findNavController(this).navigate(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchStories() {
|
||||||
|
if (!isLoggedIn) return;
|
||||||
|
storiesFetching = true;
|
||||||
|
storiesService.getUserStory(
|
||||||
|
hashtagModel.getName(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
new ServiceCallback<List<StoryModel>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final List<StoryModel> storyModels) {
|
||||||
|
if (storyModels != null && !storyModels.isEmpty()) {
|
||||||
|
binding.mainHashtagImage.setStoriesBorder();
|
||||||
|
hasStories = true;
|
||||||
|
} else {
|
||||||
|
hasStories = false;
|
||||||
|
}
|
||||||
|
storiesFetching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
Log.e(TAG, "Error", t);
|
||||||
|
storiesFetching = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -453,6 +551,59 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateSwipeRefreshState() {
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching() || storiesFetching);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 boolean checkAndResetAction() {
|
private boolean checkAndResetAction() {
|
||||||
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
|
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -467,4 +618,11 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPostsLayoutPreferences() {
|
||||||
|
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
|
||||||
|
Constants.PREF_HASHTAG_POSTS_LAYOUT,
|
||||||
|
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
|
||||||
|
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
|||||||
.setFeedItemCallback(feedItemCallback)
|
.setFeedItemCallback(feedItemCallback)
|
||||||
.init();
|
.init();
|
||||||
binding.feedSwipeRefreshLayout.setRefreshing(true);
|
binding.feedSwipeRefreshLayout.setRefreshing(true);
|
||||||
// feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
|
||||||
// if (shouldAutoPlay) {
|
// if (shouldAutoPlay) {
|
||||||
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
|
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
|
||||||
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
|
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
|
||||||
|
@ -5,7 +5,9 @@ import java.io.Serializable;
|
|||||||
public final class HashtagModel implements Serializable {
|
public final class HashtagModel implements Serializable {
|
||||||
private final boolean following;
|
private final boolean following;
|
||||||
private final long postCount;
|
private final long postCount;
|
||||||
private final String id, name, sdProfilePic;
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
private final String sdProfilePic;
|
||||||
|
|
||||||
public HashtagModel(final String id, final String name, final String sdProfilePic, final long postCount, final boolean following) {
|
public HashtagModel(final String id, final String name, final String sdProfilePic, final long postCount, final boolean following) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -9,17 +9,24 @@ import awais.instagrabber.models.stickers.QuizModel;
|
|||||||
import awais.instagrabber.models.stickers.SwipeUpModel;
|
import awais.instagrabber.models.stickers.SwipeUpModel;
|
||||||
|
|
||||||
public final class StoryModel implements Serializable {
|
public final class StoryModel implements Serializable {
|
||||||
private final String storyMediaId, storyUrl, username, userId;
|
private final String storyMediaId;
|
||||||
|
private final String storyUrl;
|
||||||
|
private final String username;
|
||||||
|
private final String userId;
|
||||||
private final MediaItemType itemType;
|
private final MediaItemType itemType;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
private String videoUrl, tappableShortCode, tappableId, spotify;
|
private String videoUrl;
|
||||||
|
private String tappableShortCode;
|
||||||
|
private String tappableId;
|
||||||
|
private String spotify;
|
||||||
private PollModel poll;
|
private PollModel poll;
|
||||||
private QuestionModel question;
|
private QuestionModel question;
|
||||||
private QuizModel quiz;
|
private QuizModel quiz;
|
||||||
private SwipeUpModel swipeUp;
|
private SwipeUpModel swipeUp;
|
||||||
private String[] mentions;
|
private String[] mentions;
|
||||||
private int position;
|
private int position;
|
||||||
private boolean isCurrentSlide = false, canReply = false;
|
private boolean isCurrentSlide = false;
|
||||||
|
private boolean canReply = false;
|
||||||
|
|
||||||
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType,
|
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType,
|
||||||
final long timestamp, final String username, final String userId, final boolean canReply) {
|
final long timestamp, final String username, final String userId, final boolean canReply) {
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
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.Header;
|
import retrofit2.http.Header;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.QueryMap;
|
||||||
|
|
||||||
public interface TagsRepository {
|
public interface TagsRepository {
|
||||||
|
|
||||||
@ -16,4 +20,8 @@ public interface TagsRepository {
|
|||||||
Call<String> unfollow(@Header("User-Agent") String userAgent,
|
Call<String> unfollow(@Header("User-Agent") String userAgent,
|
||||||
@Header("x-csrftoken") String csrfToken,
|
@Header("x-csrftoken") String csrfToken,
|
||||||
@Path("tag") String tag);
|
@Path("tag") String tag);
|
||||||
|
|
||||||
|
@GET("/api/v1/feed/tag/{tag}/")
|
||||||
|
Call<String> fetchPosts(@Path("tag") final String tag,
|
||||||
|
@QueryMap Map<String, String> queryParams);
|
||||||
}
|
}
|
||||||
|
@ -90,4 +90,5 @@ public final class Constants {
|
|||||||
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";
|
public static final String PREF_PROFILE_POSTS_LAYOUT = "profile_posts_layout";
|
||||||
public static final String PREF_TOPIC_POSTS_LAYOUT = "topic_posts_layout";
|
public static final String PREF_TOPIC_POSTS_LAYOUT = "topic_posts_layout";
|
||||||
|
public static final String PREF_HASHTAG_POSTS_LAYOUT = "hashtag_posts_layout";
|
||||||
}
|
}
|
@ -7,11 +7,16 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
import awais.instagrabber.BuildConfig;
|
||||||
|
import awais.instagrabber.models.FeedModel;
|
||||||
|
import awais.instagrabber.models.PostChild;
|
||||||
import awais.instagrabber.models.ProfileModel;
|
import awais.instagrabber.models.ProfileModel;
|
||||||
import awais.instagrabber.models.direct_messages.DirectItemModel;
|
import awais.instagrabber.models.direct_messages.DirectItemModel;
|
||||||
import awais.instagrabber.models.direct_messages.InboxThreadModel;
|
import awais.instagrabber.models.direct_messages.InboxThreadModel;
|
||||||
@ -580,4 +585,122 @@ public final class ResponseBodyUtils {
|
|||||||
//if ("raven_unknown".equals(type)) [default?]
|
//if ("raven_unknown".equals(type)) [default?]
|
||||||
return RavenExpiringMediaType.RAVEN_UNKNOWN;
|
return RavenExpiringMediaType.RAVEN_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FeedModel parseItem(final JSONObject itemJson) throws JSONException {
|
||||||
|
if (itemJson == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ProfileModel profileModel = null;
|
||||||
|
if (itemJson.has("user")) {
|
||||||
|
final JSONObject user = itemJson.getJSONObject("user");
|
||||||
|
final JSONObject friendshipStatus = user.optJSONObject("friendship_status");
|
||||||
|
boolean following = false;
|
||||||
|
boolean restricted = false;
|
||||||
|
boolean requested = false;
|
||||||
|
if (friendshipStatus != null) {
|
||||||
|
following = friendshipStatus.optBoolean("following");
|
||||||
|
requested = friendshipStatus.optBoolean("outgoing_request");
|
||||||
|
restricted = friendshipStatus.optBoolean("is_restricted");
|
||||||
|
}
|
||||||
|
profileModel = new ProfileModel(
|
||||||
|
user.optBoolean("is_private"),
|
||||||
|
false, // if you can see it then you def follow
|
||||||
|
user.optBoolean("is_verified"),
|
||||||
|
user.getString("pk"),
|
||||||
|
user.getString(Constants.EXTRAS_USERNAME),
|
||||||
|
user.optString("full_name"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
user.getString("profile_pic_url"),
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
following,
|
||||||
|
restricted,
|
||||||
|
false,
|
||||||
|
requested);
|
||||||
|
}
|
||||||
|
final JSONObject captionJson = itemJson.optJSONObject("caption");
|
||||||
|
final JSONObject locationJson = itemJson.optJSONObject("location");
|
||||||
|
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(itemJson.optInt("media_type"));
|
||||||
|
if (mediaType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
|
||||||
|
.setItemType(mediaType)
|
||||||
|
.setProfileModel(profileModel)
|
||||||
|
.setPostId(itemJson.getString(Constants.EXTRAS_ID))
|
||||||
|
.setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(itemJson) : null)
|
||||||
|
.setShortCode(itemJson.getString("code"))
|
||||||
|
.setPostCaption(captionJson != null ? captionJson.optString("text") : null)
|
||||||
|
.setCommentsCount(itemJson.optInt("comment_count"))
|
||||||
|
.setTimestamp(itemJson.optLong("taken_at", -1))
|
||||||
|
.setLiked(itemJson.optBoolean("has_liked"))
|
||||||
|
// .setBookmarked()
|
||||||
|
.setLikesCount(itemJson.optInt("like_count"))
|
||||||
|
.setLocationName(locationJson != null ? locationJson.optString("name") : null)
|
||||||
|
.setLocationId(locationJson != null ? String.valueOf(locationJson.optLong("pk")) : null)
|
||||||
|
.setImageHeight(itemJson.optInt("original_height"))
|
||||||
|
.setImageWidth(itemJson.optInt("original_width"));
|
||||||
|
switch (mediaType) {
|
||||||
|
case MEDIA_TYPE_VIDEO:
|
||||||
|
final long videoViews = itemJson.optLong("view_count", 0);
|
||||||
|
feedModelBuilder.setViewCount(videoViews)
|
||||||
|
.setDisplayUrl(ResponseBodyUtils.getVideoUrl(itemJson));
|
||||||
|
break;
|
||||||
|
case MEDIA_TYPE_IMAGE:
|
||||||
|
feedModelBuilder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(itemJson));
|
||||||
|
break;
|
||||||
|
case MEDIA_TYPE_SLIDER:
|
||||||
|
final List<PostChild> childPosts = getChildPosts(itemJson);
|
||||||
|
feedModelBuilder.setSliderItems(childPosts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return feedModelBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<PostChild> getChildPosts(final JSONObject mediaJson) throws JSONException {
|
||||||
|
if (mediaJson == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
final JSONArray carouselMedia = mediaJson.optJSONArray("carousel_media");
|
||||||
|
if (carouselMedia == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
final List<PostChild> children = new ArrayList<>();
|
||||||
|
for (int i = 0; i < carouselMedia.length(); i++) {
|
||||||
|
final JSONObject childJson = carouselMedia.optJSONObject(i);
|
||||||
|
final PostChild childPost = getChildPost(childJson);
|
||||||
|
if (childPost != null) {
|
||||||
|
children.add(childPost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PostChild getChildPost(final JSONObject childJson) throws JSONException {
|
||||||
|
if (childJson == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(childJson.optInt("media_type"));
|
||||||
|
if (mediaType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final PostChild.Builder builder = new PostChild.Builder();
|
||||||
|
switch (mediaType) {
|
||||||
|
case MEDIA_TYPE_VIDEO:
|
||||||
|
builder.setDisplayUrl(ResponseBodyUtils.getVideoUrl(childJson));
|
||||||
|
break;
|
||||||
|
case MEDIA_TYPE_IMAGE:
|
||||||
|
builder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(childJson));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return builder.setItemType(mediaType)
|
||||||
|
.setPostId(childJson.getString("id"))
|
||||||
|
.setThumbnailUrl(ResponseBodyUtils.getLowQualityImage(childJson))
|
||||||
|
.setHeight(childJson.optInt("original_height"))
|
||||||
|
.setWidth(childJson.optInt("original_width"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import static awais.instagrabber.utils.Constants.INSTADP;
|
|||||||
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
|
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
|
||||||
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
|
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_HASHTAG_POSTS_LAYOUT;
|
||||||
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.PREF_PROFILE_POSTS_LAYOUT;
|
||||||
@ -117,7 +118,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, PREF_PROFILE_POSTS_LAYOUT,
|
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT,
|
||||||
PREF_TOPIC_POSTS_LAYOUT})
|
PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_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,
|
||||||
|
@ -2,7 +2,7 @@ package awais.instagrabber.webservices;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableBiMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -14,7 +14,6 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import awais.instagrabber.models.FeedModel;
|
import awais.instagrabber.models.FeedModel;
|
||||||
import awais.instagrabber.models.PostChild;
|
|
||||||
import awais.instagrabber.models.ProfileModel;
|
import awais.instagrabber.models.ProfileModel;
|
||||||
import awais.instagrabber.models.TopicCluster;
|
import awais.instagrabber.models.TopicCluster;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
@ -51,7 +50,7 @@ public class DiscoverService extends BaseService {
|
|||||||
|
|
||||||
public void topicalExplore(@NonNull final TopicalExploreRequest request,
|
public void topicalExplore(@NonNull final TopicalExploreRequest request,
|
||||||
final ServiceCallback<TopicalExploreResponse> callback) {
|
final ServiceCallback<TopicalExploreResponse> callback) {
|
||||||
final ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.<String, String>builder()
|
final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder()
|
||||||
.put("module", "explore_popular");
|
.put("module", "explore_popular");
|
||||||
if (!TextUtils.isEmpty(request.getModule())) {
|
if (!TextUtils.isEmpty(request.getModule())) {
|
||||||
builder.put("module", request.getModule());
|
builder.put("module", request.getModule());
|
||||||
@ -204,7 +203,7 @@ public class DiscoverService extends BaseService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final JSONObject mediaJson = itemJson.optJSONObject("media");
|
final JSONObject mediaJson = itemJson.optJSONObject("media");
|
||||||
final FeedModel feedModel = parseClusterItemMedia(mediaJson);
|
final FeedModel feedModel = ResponseBodyUtils.parseItem(mediaJson);
|
||||||
if (feedModel != null) {
|
if (feedModel != null) {
|
||||||
feedModels.add(feedModel);
|
feedModels.add(feedModel);
|
||||||
}
|
}
|
||||||
@ -212,118 +211,6 @@ public class DiscoverService extends BaseService {
|
|||||||
return feedModels;
|
return feedModels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FeedModel parseClusterItemMedia(final JSONObject mediaJson) throws JSONException {
|
|
||||||
if (mediaJson == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ProfileModel profileModel = null;
|
|
||||||
if (mediaJson.has("user")) {
|
|
||||||
final JSONObject user = mediaJson.getJSONObject("user");
|
|
||||||
final JSONObject friendshipStatus = user.optJSONObject("friendship_status");
|
|
||||||
boolean following = false;
|
|
||||||
boolean restricted = false;
|
|
||||||
boolean requested = false;
|
|
||||||
if (friendshipStatus != null) {
|
|
||||||
following = friendshipStatus.optBoolean("following");
|
|
||||||
requested = friendshipStatus.optBoolean("outgoing_request");
|
|
||||||
restricted = friendshipStatus.optBoolean("is_restricted");
|
|
||||||
}
|
|
||||||
profileModel = new ProfileModel(
|
|
||||||
user.optBoolean("is_private"),
|
|
||||||
false, // if you can see it then you def follow
|
|
||||||
user.optBoolean("is_verified"),
|
|
||||||
user.getString("pk"),
|
|
||||||
user.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
user.optString("full_name"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
user.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
following,
|
|
||||||
restricted,
|
|
||||||
false,
|
|
||||||
requested);
|
|
||||||
}
|
|
||||||
final JSONObject captionJson = mediaJson.optJSONObject("caption");
|
|
||||||
final JSONObject locationJson = mediaJson.optJSONObject("location");
|
|
||||||
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(mediaJson.optInt("media_type"));
|
|
||||||
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
|
|
||||||
.setItemType(mediaType)
|
|
||||||
.setProfileModel(profileModel)
|
|
||||||
.setPostId(mediaJson.getString(Constants.EXTRAS_ID))
|
|
||||||
.setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(mediaJson) : null)
|
|
||||||
.setShortCode(mediaJson.getString("code"))
|
|
||||||
.setPostCaption(captionJson != null ? captionJson.optString("text") : null)
|
|
||||||
.setCommentsCount(mediaJson.optInt("comment_count"))
|
|
||||||
.setTimestamp(mediaJson.optLong("taken_at", -1))
|
|
||||||
.setLiked(mediaJson.optBoolean("has_liked"))
|
|
||||||
// .setBookmarked()
|
|
||||||
.setLikesCount(mediaJson.optInt("like_count"))
|
|
||||||
.setLocationName(locationJson != null ? locationJson.optString("name") : null)
|
|
||||||
.setLocationId(locationJson != null ? String.valueOf(locationJson.optInt("pk")) : null)
|
|
||||||
.setImageHeight(mediaJson.optInt("original_height"))
|
|
||||||
.setImageWidth(mediaJson.optInt("original_width"));
|
|
||||||
switch (mediaType) {
|
|
||||||
case MEDIA_TYPE_VIDEO:
|
|
||||||
final long videoViews = mediaJson.optLong("view_count", 0);
|
|
||||||
feedModelBuilder.setViewCount(videoViews)
|
|
||||||
.setDisplayUrl(ResponseBodyUtils.getVideoUrl(mediaJson));
|
|
||||||
break;
|
|
||||||
case MEDIA_TYPE_IMAGE:
|
|
||||||
feedModelBuilder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(mediaJson));
|
|
||||||
break;
|
|
||||||
case MEDIA_TYPE_SLIDER:
|
|
||||||
final List<PostChild> childPosts = getChildPosts(mediaJson);
|
|
||||||
feedModelBuilder.setSliderItems(childPosts);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return feedModelBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PostChild> getChildPosts(final JSONObject mediaJson) throws JSONException {
|
|
||||||
if (mediaJson == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
final JSONArray carouselMedia = mediaJson.optJSONArray("carousel_media");
|
|
||||||
if (carouselMedia == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
final List<PostChild> children = new ArrayList<>();
|
|
||||||
for (int i = 0; i < carouselMedia.length(); i++) {
|
|
||||||
final JSONObject childJson = carouselMedia.optJSONObject(i);
|
|
||||||
final PostChild childPost = getChildPost(childJson);
|
|
||||||
if (childPost != null) {
|
|
||||||
children.add(childPost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PostChild getChildPost(final JSONObject childJson) throws JSONException {
|
|
||||||
if (childJson == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final MediaItemType mediaType = ResponseBodyUtils.getMediaItemType(childJson.optInt("media_type"));
|
|
||||||
final PostChild.Builder builder = new PostChild.Builder();
|
|
||||||
switch (mediaType) {
|
|
||||||
case MEDIA_TYPE_VIDEO:
|
|
||||||
builder.setDisplayUrl(ResponseBodyUtils.getVideoUrl(childJson));
|
|
||||||
break;
|
|
||||||
case MEDIA_TYPE_IMAGE:
|
|
||||||
builder.setDisplayUrl(ResponseBodyUtils.getHighQualityImage(childJson));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return builder.setItemType(mediaType)
|
|
||||||
.setPostId(childJson.getString("id"))
|
|
||||||
.setThumbnailUrl(ResponseBodyUtils.getLowQualityImage(childJson))
|
|
||||||
.setHeight(childJson.optInt("original_height"))
|
|
||||||
.setWidth(childJson.optInt("original_width"))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TopicalExploreRequest {
|
public static class TopicalExploreRequest {
|
||||||
|
|
||||||
private String module;
|
private String module;
|
||||||
|
@ -4,11 +4,22 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
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.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import awais.instagrabber.models.FeedModel;
|
||||||
import awais.instagrabber.repositories.TagsRepository;
|
import awais.instagrabber.repositories.TagsRepository;
|
||||||
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;
|
||||||
@ -18,16 +29,20 @@ public class TagsService extends BaseService {
|
|||||||
|
|
||||||
private static final String TAG = "TagsService";
|
private static final String TAG = "TagsService";
|
||||||
|
|
||||||
// web for www.instagram.com
|
|
||||||
private final TagsRepository webRepository;
|
|
||||||
|
|
||||||
private static TagsService instance;
|
private static TagsService instance;
|
||||||
|
|
||||||
|
private final TagsRepository webRepository;
|
||||||
|
private final TagsRepository repository;
|
||||||
|
|
||||||
private TagsService() {
|
private TagsService() {
|
||||||
final Retrofit webRetrofit = getRetrofitBuilder()
|
final Retrofit webRetrofit = getRetrofitBuilder()
|
||||||
.baseUrl("https://www.instagram.com/")
|
.baseUrl("https://www.instagram.com/")
|
||||||
.build();
|
.build();
|
||||||
webRepository = webRetrofit.create(TagsRepository.class);
|
webRepository = webRetrofit.create(TagsRepository.class);
|
||||||
|
final Retrofit retrofit = getRetrofitBuilder()
|
||||||
|
.baseUrl("https://i.instagram.com/")
|
||||||
|
.build();
|
||||||
|
repository = retrofit.create(TagsRepository.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TagsService getInstance() {
|
public static TagsService getInstance() {
|
||||||
@ -98,4 +113,169 @@ public class TagsService extends BaseService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fetchPosts(@NonNull final String tag,
|
||||||
|
final String maxId,
|
||||||
|
final ServiceCallback<TagPostsFetchResponse> callback) {
|
||||||
|
final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder();
|
||||||
|
if (!TextUtils.isEmpty(maxId)) {
|
||||||
|
builder.put("max_id", maxId);
|
||||||
|
}
|
||||||
|
final Call<String> request = repository.fetchPosts(tag, builder.build());
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
try {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String body = response.body();
|
||||||
|
if (TextUtils.isEmpty(body)) {
|
||||||
|
callback.onSuccess(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final TagPostsFetchResponse tagPostsFetchResponse = parseResponse(body);
|
||||||
|
callback.onSuccess(tagPostsFetchResponse);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "onResponse", e);
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private TagPostsFetchResponse parseResponse(@NonNull final String body) throws JSONException {
|
||||||
|
final JSONObject root = new JSONObject(body);
|
||||||
|
final boolean moreAvailable = root.optBoolean("more_available");
|
||||||
|
final String nextMaxId = root.optString("next_max_id");
|
||||||
|
final int numResults = root.optInt("num_results");
|
||||||
|
final String status = root.optString("status");
|
||||||
|
final JSONArray itemsJson = root.optJSONArray("items");
|
||||||
|
final List<FeedModel> items = parseItems(itemsJson);
|
||||||
|
return new TagPostsFetchResponse(
|
||||||
|
moreAvailable,
|
||||||
|
nextMaxId,
|
||||||
|
numResults,
|
||||||
|
status,
|
||||||
|
items
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeedModel> parseItems(final JSONArray items) throws JSONException {
|
||||||
|
if (items == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
final List<FeedModel> feedModels = new ArrayList<>();
|
||||||
|
for (int i = 0; i < items.length(); i++) {
|
||||||
|
final JSONObject itemJson = items.optJSONObject(i);
|
||||||
|
if (itemJson == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson);
|
||||||
|
if (feedModel != null) {
|
||||||
|
feedModels.add(feedModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return feedModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TagPostsFetchResponse {
|
||||||
|
private boolean moreAvailable;
|
||||||
|
private String nextMaxId;
|
||||||
|
private int numResults;
|
||||||
|
private String status;
|
||||||
|
private List<FeedModel> items;
|
||||||
|
|
||||||
|
public TagPostsFetchResponse(final boolean moreAvailable,
|
||||||
|
final String nextMaxId,
|
||||||
|
final int numResults,
|
||||||
|
final String status,
|
||||||
|
final List<FeedModel> items) {
|
||||||
|
this.moreAvailable = moreAvailable;
|
||||||
|
this.nextMaxId = nextMaxId;
|
||||||
|
this.numResults = numResults;
|
||||||
|
this.status = status;
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMoreAvailable() {
|
||||||
|
return moreAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
|
||||||
|
this.moreAvailable = moreAvailable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNextMaxId() {
|
||||||
|
return nextMaxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagPostsFetchResponse setNextMaxId(final String nextMaxId) {
|
||||||
|
this.nextMaxId = nextMaxId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumResults() {
|
||||||
|
return numResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagPostsFetchResponse setNumResults(final int numResults) {
|
||||||
|
this.numResults = numResults;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagPostsFetchResponse setStatus(final String status) {
|
||||||
|
this.status = status;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FeedModel> getItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagPostsFetchResponse setItems(final List<FeedModel> items) {
|
||||||
|
this.items = items;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final TagPostsFetchResponse that = (TagPostsFetchResponse) o;
|
||||||
|
return moreAvailable == that.moreAvailable &&
|
||||||
|
numResults == that.numResults &&
|
||||||
|
Objects.equals(nextMaxId, that.nextMaxId) &&
|
||||||
|
Objects.equals(status, that.status) &&
|
||||||
|
Objects.equals(items, that.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TagPostsFetchResponse{" +
|
||||||
|
"moreAvailable=" + moreAvailable +
|
||||||
|
", nextMaxId='" + nextMaxId + '\'' +
|
||||||
|
", numResults=" + numResults +
|
||||||
|
", status='" + status + '\'' +
|
||||||
|
", items=" + items +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:text="@string/follow"
|
android:text="@string/follow"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:chipBackgroundColor="@null"
|
app:chipBackgroundColor="@null"
|
||||||
@ -67,7 +66,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:text="@string/add_to_favorites"
|
android:text="@string/add_to_favorites"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:chipBackgroundColor="@null"
|
app:chipBackgroundColor="@null"
|
||||||
@ -77,74 +75,20 @@
|
|||||||
app:layout_constraintStart_toEndOf="@id/btnFollowTag"
|
app:layout_constraintStart_toEndOf="@id/btnFollowTag"
|
||||||
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
|
app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"
|
||||||
app:rippleColor="@color/yellow_400" />
|
app:rippleColor="@color/yellow_400" />
|
||||||
|
|
||||||
<!--<com.google.android.material.chip.Chip-->
|
|
||||||
<!-- android:layout_width="wrap_content"-->
|
|
||||||
<!-- android:layout_height="wrap_content"-->
|
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount" />-->
|
|
||||||
|
|
||||||
<!--<com.google.android.material.button.MaterialButton-->
|
|
||||||
<!-- android:id="@+id/btnFollowTag"-->
|
|
||||||
<!-- style="@style/Widget.MaterialComponents.Button.TextButton"-->
|
|
||||||
<!-- android:layout_width="0dp"-->
|
|
||||||
<!-- android:layout_height="0dp"-->
|
|
||||||
<!-- android:text="@string/follow"-->
|
|
||||||
<!-- android:textColor="@color/deep_purple_200"-->
|
|
||||||
<!-- android:visibility="gone"-->
|
|
||||||
<!-- app:icon="@drawable/ic_outline_person_add_24"-->
|
|
||||||
<!-- app:iconGravity="top"-->
|
|
||||||
<!-- app:iconTint="@color/deep_purple_200"-->
|
|
||||||
<!-- app:layout_constraintBottom_toTopOf="@id/fav_cb"-->
|
|
||||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
|
||||||
<!-- app:layout_constraintStart_toEndOf="@id/mainTagPostCount"-->
|
|
||||||
<!-- app:layout_constraintTop_toTopOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- tools:visibility="visible" />-->
|
|
||||||
|
|
||||||
<!--<CheckBox-->
|
|
||||||
<!-- android:id="@+id/fav_cb"-->
|
|
||||||
<!-- android:layout_width="0dp"-->
|
|
||||||
<!-- android:layout_height="wrap_content"-->
|
|
||||||
<!-- android:button="@drawable/sl_favourite_24"-->
|
|
||||||
<!-- android:paddingStart="8dp"-->
|
|
||||||
<!-- android:paddingEnd="8dp"-->
|
|
||||||
<!-- android:text="Add to favorites"-->
|
|
||||||
<!-- android:visibility="gone"-->
|
|
||||||
<!-- app:buttonTint="@color/yellow_800"-->
|
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- app:layout_constraintEnd_toEndOf="parent"-->
|
|
||||||
<!-- app:layout_constraintStart_toEndOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- app:layout_constraintTop_toBottomOf="@id/btnFollowTag"-->
|
|
||||||
<!-- tools:visibility="gone" />-->
|
|
||||||
|
|
||||||
<!--<ProgressBar-->
|
|
||||||
<!-- android:id="@+id/fav_progress"-->
|
|
||||||
<!-- style="@style/Widget.MaterialComponents.ProgressIndicator.Circular.Indeterminate"-->
|
|
||||||
<!-- android:layout_width="24dp"-->
|
|
||||||
<!-- android:layout_height="24dp"-->
|
|
||||||
<!-- android:paddingStart="8dp"-->
|
|
||||||
<!-- android:paddingEnd="8dp"-->
|
|
||||||
<!-- android:visibility="gone"-->
|
|
||||||
<!-- app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage"-->
|
|
||||||
<!-- app:layout_constraintStart_toStartOf="@id/fav_cb"-->
|
|
||||||
<!-- app:layout_constraintTop_toBottomOf="@id/mainTagPostCount"-->
|
|
||||||
<!-- tools:visibility="gone" />-->
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
</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"
|
||||||
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" />
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>
|
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout>
|
9
app/src/main/res/menu/hashtag_menu.xml
Normal file
9
app/src/main/res/menu/hashtag_menu.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:title="@string/layout"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
@ -5,7 +5,7 @@
|
|||||||
android:id="@+id/comments_nav_graph"
|
android:id="@+id/comments_nav_graph"
|
||||||
app:startDestination="@id/commentsViewerFragment">
|
app:startDestination="@id/commentsViewerFragment">
|
||||||
|
|
||||||
<include app:graph="@navigation/hashtag_nav_graph" />
|
<!--<include app:graph="@navigation/hashtag_nav_graph" />-->
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_hashTagFragment"
|
android:id="@+id/action_global_hashTagFragment"
|
||||||
|
@ -19,13 +19,32 @@
|
|||||||
app:argType="boolean" />
|
app:argType="boolean" />
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<include app:graph="@navigation/comments_nav_graph" />
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_commentsViewerFragment"
|
||||||
|
app:destination="@id/comments_nav_graph">
|
||||||
|
<argument
|
||||||
|
android:name="shortCode"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
<argument
|
||||||
|
android:name="postId"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
<argument
|
||||||
|
android:name="postUserId"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
</action>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_profileFragment"
|
android:id="@+id/action_global_profileFragment"
|
||||||
app:destination="@id/profile_nav_graph">
|
app:destination="@id/profile_nav_graph">
|
||||||
<argument
|
<argument
|
||||||
android:name="username"
|
android:name="username"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="false" />
|
app:nullable="true" />
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
Loading…
Reference in New Issue
Block a user