Add Posts view to Location fragment

This commit is contained in:
Ammar Githam 2020-11-01 21:56:04 +09:00
parent 2931f2d3ab
commit d9c30c99b7
12 changed files with 674 additions and 164 deletions

View File

@ -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.LocationModel;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService;
private final LocationModel locationModel;
private String nextMaxId;
private boolean moreAvailable;
public LocationPostFetchService(final LocationModel locationModel) {
this.locationModel = locationModel;
locationService = LocationService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
locationService.fetchPosts(locationModel.getId(), nextMaxId, new ServiceCallback<LocationPostsFetchResponse>() {
@Override
public void onSuccess(final LocationPostsFetchResponse 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;
}
}

View File

@ -44,7 +44,6 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.databinding.FragmentHashtagBinding; import awais.instagrabber.databinding.FragmentHashtagBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.FeedFragmentDirections;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
@ -183,13 +182,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onHashtagClick(final String hashtag) { public void onHashtagClick(final String hashtag) {
final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag); final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(HashTagFragment.this).navigate(action); NavHostFragment.findNavController(HashTagFragment.this).navigate(action);
} }
@Override @Override
public void onLocationClick(final FeedModel feedModel) { public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId()); final NavDirections action = HashTagFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(HashTagFragment.this).navigate(action); NavHostFragment.findNavController(HashTagFragment.this).navigate(action);
} }

View File

@ -6,12 +6,15 @@ import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
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;
@ -19,12 +22,12 @@ 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.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;
@ -32,71 +35,64 @@ 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.LocationFetcher; import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.LocationPostFetchService;
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.FragmentLocationBinding; import awais.instagrabber.databinding.FragmentLocationBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.LocationModel;
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 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 LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment"; private static final String TAG = "LocationFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private FragmentLocationBinding binding; private FragmentLocationBinding binding;
private NestedCoordinatorLayout root; private NestedCoordinatorLayout root;
private boolean shouldRefresh = true, hasStories = false; private boolean shouldRefresh = true;
private boolean hasStories = false;
private String locationId; private String locationId;
private LocationModel locationModel; private LocationModel locationModel;
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 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() {
if (postsAdapter == null) { // if (postsAdapter == null) {
setEnabled(false); // setEnabled(false);
remove(); // remove();
return; // return;
} // }
postsAdapter.clearSelection(); // postsAdapter.clearSelection();
setEnabled(false); setEnabled(false);
remove(); remove();
} }
@ -112,45 +108,130 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
public boolean onActionItemClicked(final ActionMode mode, public boolean onActionItemClicked(final ActionMode mode,
final MenuItem item) { final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || locationId == null) { // if (postsAdapter == null || locationId == 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,
locationId, // locationId,
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);
// }
// 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(); @Override
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() public void onSliderClick(final FeedModel feedModel, final int position) {
: new ArrayList<>(postModels); openPostDialog(feedModel, null, null, position);
if (isPullToRefresh) { }
finalList = result;
isPullToRefresh = false; @Override
} else { public void onCommentsClick(final FeedModel feedModel) {
finalList.addAll(result); final NavDirections commentsAction = LocationFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(LocationFragment.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;
} }
postsViewModel.getList().postValue(finalList); requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
PostModel model = null; }
if (!result.isEmpty()) {
model = result.get(result.size() - 1); @Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = LocationFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(LocationFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = LocationFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(LocationFragment.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);
} }
if (model == null) return; final PostViewV2Fragment fragment = builder
endCursor = model.getEndCursor(); .setSharedProfilePicElement(profilePicView)
hasNextPage = model.hasNextPage(); .setSharedMainPostElement(mainPostImage)
model.setPageCursor(false, null); .build();
fragment.show(getChildFragmentManager(), "post_view");
} }
}; };
@ -159,6 +240,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance(); storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
} }
@Nullable @Nullable
@ -185,9 +267,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true; binding.posts.refresh();
endCursor = null; fetchStories();
fetchLocationModel();
} }
@Override @Override
@ -197,13 +278,27 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
@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);
}
// @Override
// public void onDestroy() {
// super.onDestroy();
// if (postsViewModel != null) {
// postsViewModel.getList().postValue(Collections.emptyList());
// }
// }
private void init() { private void init() {
if (getArguments() == null) return; if (getArguments() == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
@ -213,66 +308,73 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
binding.favChip.setVisibility(View.GONE); binding.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE); binding.btnMap.setVisibility(View.GONE);
setTitle(); setTitle();
setupPosts();
fetchLocationModel(); fetchLocationModel();
} }
private void setupPosts() { private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); binding.posts.setViewModelStoreOwner(this)
final Context context = getContext(); .setLifeCycleOwner(this)
if (context == null) return; .setPostFetchService(new LocationPostFetchService(locationModel))
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_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; // postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size()); // final Context context = getContext();
actionMode.setTitle(title); // if (context == null) return;
return; // final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110));
} // binding.mainPosts.setLayoutManager(layoutManager);
if (checkAndResetAction()) return; // binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
final List<PostModel> postModels = postsViewModel.getList().getValue(); // postsAdapter = new PostsAdapter((postModel, position) -> {
if (postModels == null || postModels.size() == 0) return; // if (postsAdapter.isSelecting()) {
if (postModels.get(0) == null) return; // if (actionMode == null) return;
final String postId = postModels.get(0).getPostId(); // final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
final boolean isId = postId != null && isLoggedIn; // actionMode.setTitle(title);
final String[] idsOrShortCodes = new String[postModels.size()]; // return;
for (int i = 0; i < postModels.size(); i++) { // }
idsOrShortCodes[i] = isId ? postModels.get(i).getPostId() // if (checkAndResetAction()) return;
: postModels.get(i).getShortCode(); // final List<PostModel> postModels = postsViewModel.getList().getValue();
} // if (postModels == null || postModels.size() == 0) return;
final NavDirections action = LocationFragmentDirections.actionGlobalPostViewFragment( // if (postModels.get(0) == null) return;
position, // final String postId = postModels.get(0).getPostId();
idsOrShortCodes, // final boolean isId = postId != null && isLoggedIn;
isId); // final String[] idsOrShortCodes = new String[postModels.size()];
NavHostFragment.findNavController(this).navigate(action); // for (int i = 0; i < postModels.size(); i++) {
}, (model, position) -> { // idsOrShortCodes[i] = isId ? postModels.get(i).getPostId()
if (!postsAdapter.isSelecting()) { // : postModels.get(i).getShortCode();
checkAndResetAction(); // }
return true; // final NavDirections action = LocationFragmentDirections.actionGlobalPostViewFragment(
} // position,
if (onBackPressedCallback.isEnabled()) { // idsOrShortCodes,
return true; // isId);
} // NavHostFragment.findNavController(this).navigate(action);
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity // }, (model, position) -> {
.getOnBackPressedDispatcher(); // if (!postsAdapter.isSelecting()) {
onBackPressedCallback.setEnabled(true); // checkAndResetAction();
actionMode = fragmentActivity.startActionMode(multiSelectAction); // return true;
final String title = getString(R.string.number_selected, 1); // }
actionMode.setTitle(title); // if (onBackPressedCallback.isEnabled()) {
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); // return true;
return true; // }
}); // final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList); // .getOnBackPressedDispatcher();
binding.mainPosts.setAdapter(postsAdapter); // onBackPressedCallback.setEnabled(true);
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { // actionMode = fragmentActivity.startActionMode(multiSelectAction);
if (!hasNextPage) return; // final String title = getString(R.string.number_selected, 1);
binding.swipeRefreshLayout.setRefreshing(true); // actionMode.setTitle(title);
fetchPosts(); // onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
endCursor = null; // return true;
}); // });
binding.mainPosts.addOnScrollListener(lazyLoader); // 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 fetchLocationModel() { private void fetchLocationModel() {
@ -289,34 +391,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
setTitle(); setTitle();
setupLocationDetails(); setupLocationDetails();
fetchPosts(); setupPosts();
fetchStories();
// fetchPosts();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void setupLocationDetails() { private void setupLocationDetails() {
final String locationId = locationModel.getId(); final String locationId = locationModel.getId();
binding.swipeRefreshLayout.setRefreshing(true); // binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) {
storiesService.getUserStory(locationId,
null,
true,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainLocationImage.setStoriesBorder();
hasStories = true;
}
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
}
});
}
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount()); final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline,
@ -405,18 +488,44 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final NavDirections action = LocationFragmentDirections final NavDirections action = LocationFragmentDirections
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName()); .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName());
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
return;
} }
}); });
} }
private void fetchPosts() { private void fetchStories() {
stopCurrentExecutor(); if (isLoggedIn) {
currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener) storiesFetching = true;
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); storiesService.getUserStory(locationId,
null,
true,
false,
false,
new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
if (storyModels != null && !storyModels.isEmpty()) {
binding.mainLocationImage.setStoriesBorder();
hasStories = true;
}
storiesFetching = false;
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error", t);
storiesFetching = false;
}
});
}
} }
public void stopCurrentExecutor() { // private void fetchPosts() {
// stopCurrentExecutor();
// currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener)
// .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// }
private void stopCurrentExecutor() {
if (currentlyExecuting != null) { if (currentlyExecuting != null) {
try { try {
currentlyExecuting.cancel(true); currentlyExecuting.cancel(true);
@ -435,6 +544,59 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
} }
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;
@ -449,4 +611,11 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
return true; return true;
} }
private void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
Constants.PREF_LOCATION_POSTS_LAYOUT,
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
} }

View File

@ -197,6 +197,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override
public void onResume() {
super.onResume();
fragmentActivity.setToolbar(binding.toolbar);
}
@Override @Override
public void onRefresh() { public void onRefresh() {
binding.posts.refresh(); binding.posts.refresh();

View File

@ -0,0 +1,15 @@
package awais.instagrabber.repositories;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface LocationRepository {
@GET("/api/v1/feed/location/{location}/")
Call<String> fetchPosts(@Path("location") final String locationId,
@QueryMap Map<String, String> queryParams);
}

View File

@ -91,4 +91,5 @@ public final class Constants {
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"; public static final String PREF_HASHTAG_POSTS_LAYOUT = "hashtag_posts_layout";
public static final String PREF_LOCATION_POSTS_LAYOUT = "location_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_HASHTAG_POSTS_LAYOUT; 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_LOCATION_POSTS_LAYOUT;
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;
import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT; import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT;
@ -118,7 +119,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_HASHTAG_POSTS_LAYOUT}) PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_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

@ -0,0 +1,213 @@
package awais.instagrabber.webservices;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import org.json.JSONArray;
import org.json.JSONException;
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.LocationRepository;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class LocationService extends BaseService {
private static final String TAG = "LocationService";
private final LocationRepository repository;
private static LocationService instance;
private LocationService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(LocationRepository.class);
}
public static LocationService getInstance() {
if (instance == null) {
instance = new LocationService();
}
return instance;
}
public void fetchPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> 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(locationId, 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 LocationPostsFetchResponse 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 LocationPostsFetchResponse 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 LocationPostsFetchResponse(
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 LocationPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public LocationPostsFetchResponse(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 LocationPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public LocationPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public LocationPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public LocationPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public LocationPostsFetchResponse 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 LocationPostsFetchResponse that = (LocationPostsFetchResponse) 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);
}
@NonNull
@Override
public String toString() {
return "LocationPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
}

View File

@ -56,7 +56,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/map" android:text="@string/map"
app:chipBackgroundColor="@null" app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_map_24" app:chipIcon="@drawable/ic_outline_map_24"
@ -72,7 +71,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"
app:chipBackgroundColor="@null" app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_star_plus_24" app:chipIcon="@drawable/ic_outline_star_plus_24"
@ -143,16 +141,15 @@
</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>

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

View File

@ -47,6 +47,15 @@
app:nullable="true" /> app:nullable="true" />
</action> </action>
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/hashTagFragment" android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment" android:name="awais.instagrabber.fragments.HashTagFragment"

View File

@ -5,8 +5,6 @@
android:id="@+id/location_nav_graph" android:id="@+id/location_nav_graph"
app:startDestination="@id/locationFragment"> app:startDestination="@id/locationFragment">
<!--<include app:graph="@navigation/post_view_nav_graph" />-->
<action <action
android:id="@+id/action_global_postViewFragment" android:id="@+id/action_global_postViewFragment"
app:destination="@id/post_view_nav_graph"> app:destination="@id/post_view_nav_graph">
@ -21,6 +19,43 @@
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
android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph">
<argument
android:name="username"
app:argType="string"
app:nullable="true" />
</action>
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<fragment <fragment
android:id="@+id/locationFragment" android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment" android:name="awais.instagrabber.fragments.LocationFragment"