From d9c30c99b7f4fe9246aaa9a757ad75375ab631f6 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 1 Nov 2020 21:56:04 +0900 Subject: [PATCH] Add Posts view to Location fragment --- .../asyncs/LocationPostFetchService.java | 56 +++ .../fragments/HashTagFragment.java | 5 +- .../fragments/LocationFragment.java | 469 ++++++++++++------ .../fragments/TopicPostsFragment.java | 6 + .../repositories/LocationRepository.java | 15 + .../awais/instagrabber/utils/Constants.java | 1 + .../instagrabber/utils/SettingsHelper.java | 3 +- .../webservices/LocationService.java | 213 ++++++++ app/src/main/res/layout/fragment_location.xml | 13 +- app/src/main/res/menu/location_menu.xml | 9 + .../main/res/navigation/hashtag_nav_graph.xml | 9 + .../res/navigation/location_nav_graph.xml | 39 +- 12 files changed, 674 insertions(+), 164 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/LocationRepository.java create mode 100644 app/src/main/java/awais/instagrabber/webservices/LocationService.java create mode 100644 app/src/main/res/menu/location_menu.xml diff --git a/app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java b/app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java new file mode 100644 index 00000000..124b3f3f --- /dev/null +++ b/app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java @@ -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> fetchListener) { + locationService.fetchPosts(locationModel.getId(), nextMaxId, new ServiceCallback() { + @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; + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index ce1110d7..1e81025d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -44,7 +44,6 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; import awais.instagrabber.databinding.FragmentHashtagBinding; 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.PostsLayoutPreferences; @@ -183,13 +182,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag); + final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(hashtag); NavHostFragment.findNavController(HashTagFragment.this).navigate(action); } @Override 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); } diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index fbace444..a6a18370 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -6,12 +6,15 @@ import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.text.SpannableStringBuilder; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -19,12 +22,12 @@ import android.widget.TextView; import android.widget.Toast; import androidx.activity.OnBackPressedCallback; -import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; 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.Snackbar; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; -import awais.instagrabber.adapters.PostsAdapter; +import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.asyncs.LocationFetcher; -import awais.instagrabber.asyncs.PostsFetcher; +import awais.instagrabber.asyncs.LocationPostFetchService; 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.RecyclerLazyLoader; 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.PostModel; +import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; -import awais.instagrabber.models.enums.DownloadMethod; import awais.instagrabber.models.enums.FavoriteType; -import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; -import awais.instagrabber.viewmodels.PostsViewModel; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; 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.settingsHelper; public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; + private static final int STORAGE_PERM_REQUEST_CODE = 8020; private MainActivity fragmentActivity; private FragmentLocationBinding binding; private NestedCoordinatorLayout root; - private boolean shouldRefresh = true, hasStories = false; + private boolean shouldRefresh = true; + private boolean hasStories = false; private String locationId; private LocationModel locationModel; - private PostsViewModel postsViewModel; - private PostsAdapter postsAdapter; private ActionMode actionMode; private StoriesService storiesService; - private boolean hasNextPage; - private String endCursor; private AsyncTask currentlyExecuting; private boolean isLoggedIn; - private boolean isPullToRefresh; + private boolean storiesFetching; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { - if (postsAdapter == null) { - setEnabled(false); - remove(); - return; - } - postsAdapter.clearSelection(); + // if (postsAdapter == null) { + // setEnabled(false); + // remove(); + // return; + // } + // postsAdapter.clearSelection(); setEnabled(false); remove(); } @@ -112,45 +108,130 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { - if (postsAdapter == null || locationId == null) { - return false; - } - final Context context = getContext(); - if (context == null) return false; - DownloadUtils.batchDownload(context, - locationId, - DownloadMethod.DOWNLOAD_MAIN, - postsAdapter.getSelectedModels()); - checkAndResetAction(); + // if (postsAdapter == null || locationId == null) { + // return false; + // } + // final Context context = getContext(); + // if (context == null) return false; + // DownloadUtils.batchDownload(context, + // locationId, + // DownloadMethod.DOWNLOAD_MAIN, + // postsAdapter.getSelectedModels()); + // checkAndResetAction(); return true; } return false; } }); - private final FetchListener> postsFetchListener = new FetchListener>() { + // private final FetchListener> postsFetchListener = new FetchListener>() { + // @Override + // public void onResult(final List result) { + // binding.swipeRefreshLayout.setRefreshing(false); + // if (result == null) return; + // binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); + // final List postModels = postsViewModel.getList().getValue(); + // List 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 - public void onResult(final List result) { - binding.swipeRefreshLayout.setRefreshing(false); - if (result == null) return; - binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); - final List postModels = postsViewModel.getList().getValue(); - List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() - : new ArrayList<>(postModels); - if (isPullToRefresh) { - finalList = result; - isPullToRefresh = false; - } else { - finalList.addAll(result); + public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) { + openPostDialog(feedModel, profilePicView, mainPostImage, -1); + } + + @Override + public void onSliderClick(final FeedModel feedModel, final int position) { + openPostDialog(feedModel, null, null, position); + } + + @Override + public void onCommentsClick(final FeedModel feedModel) { + final NavDirections commentsAction = 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); - PostModel model = null; - if (!result.isEmpty()) { - model = result.get(result.size() - 1); + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); + } + + @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; - endCursor = model.getEndCursor(); - hasNextPage = model.hasNextPage(); - model.setPageCursor(false, null); + final PostViewV2Fragment fragment = builder + .setSharedProfilePicElement(profilePicView) + .setSharedMainPostElement(mainPostImage) + .build(); + fragment.show(getChildFragmentManager(), "post_view"); } }; @@ -159,6 +240,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); storiesService = StoriesService.getInstance(); + setHasOptionsMenu(true); } @Nullable @@ -185,9 +267,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR @Override public void onRefresh() { - isPullToRefresh = true; - endCursor = null; - fetchLocationModel(); + binding.posts.refresh(); + fetchStories(); } @Override @@ -197,13 +278,27 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } @Override - public void onDestroy() { - super.onDestroy(); - if (postsViewModel != null) { - postsViewModel.getList().postValue(Collections.emptyList()); - } + public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { + inflater.inflate(R.menu.topic_posts_menu, menu); } + @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() { if (getArguments() == null) return; 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.btnMap.setVisibility(View.GONE); setTitle(); - setupPosts(); fetchLocationModel(); } 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 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 = LocationFragmentDirections.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) return; - binding.swipeRefreshLayout.setRefreshing(true); - fetchPosts(); - endCursor = null; - }); - binding.mainPosts.addOnScrollListener(lazyLoader); + binding.posts.setViewModelStoreOwner(this) + .setLifeCycleOwner(this) + .setPostFetchService(new LocationPostFetchService(locationModel)) + .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_POSTS_LAYOUT))) + .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) + .setFeedItemCallback(feedItemCallback) + .init(); + binding.swipeRefreshLayout.setRefreshing(true); + // 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 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 = LocationFragmentDirections.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) return; + // binding.swipeRefreshLayout.setRefreshing(true); + // fetchPosts(); + // endCursor = null; + // }); + // binding.mainPosts.addOnScrollListener(lazyLoader); } private void fetchLocationModel() { @@ -289,34 +391,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } setTitle(); setupLocationDetails(); - fetchPosts(); + setupPosts(); + fetchStories(); + // fetchPosts(); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private void setupLocationDetails() { final String locationId = locationModel.getId(); - binding.swipeRefreshLayout.setRefreshing(true); - if (isLoggedIn) { - storiesService.getUserStory(locationId, - null, - true, - false, - false, - new ServiceCallback>() { - @Override - public void onSuccess(final List storyModels) { - if (storyModels != null && !storyModels.isEmpty()) { - binding.mainLocationImage.setStoriesBorder(); - hasStories = true; - } - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error", t); - } - }); - } + // binding.swipeRefreshLayout.setRefreshing(true); binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); final String postCount = String.valueOf(locationModel.getPostCount()); 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 .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName()); NavHostFragment.findNavController(this).navigate(action); - return; } }); } - private void fetchPosts() { - stopCurrentExecutor(); - currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + private void fetchStories() { + if (isLoggedIn) { + storiesFetching = true; + storiesService.getUserStory(locationId, + null, + true, + false, + false, + new ServiceCallback>() { + @Override + public void onSuccess(final List 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) { try { 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 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() { if (!onBackPressedCallback.isEnabled() && actionMode == null) { return false; @@ -449,4 +611,11 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } 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"); + } } diff --git a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java index 04be0526..c7130647 100644 --- a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java @@ -197,6 +197,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O return super.onOptionsItemSelected(item); } + @Override + public void onResume() { + super.onResume(); + fragmentActivity.setToolbar(binding.toolbar); + } + @Override public void onRefresh() { binding.posts.refresh(); diff --git a/app/src/main/java/awais/instagrabber/repositories/LocationRepository.java b/app/src/main/java/awais/instagrabber/repositories/LocationRepository.java new file mode 100644 index 00000000..7cb5ca41 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/LocationRepository.java @@ -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 fetchPosts(@Path("location") final String locationId, + @QueryMap Map queryParams); +} diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 8ead842b..2122f7f6 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -91,4 +91,5 @@ public final class Constants { 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_HASHTAG_POSTS_LAYOUT = "hashtag_posts_layout"; + public static final String PREF_LOCATION_POSTS_LAYOUT = "location_posts_layout"; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index f9ffdb15..df239ed8 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -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_HASHTAG_POSTS_LAYOUT; 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_PROFILE_POSTS_LAYOUT; import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT; @@ -118,7 +119,7 @@ public final class SettingsHelper { @StringDef( {APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, 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 {} @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, diff --git a/app/src/main/java/awais/instagrabber/webservices/LocationService.java b/app/src/main/java/awais/instagrabber/webservices/LocationService.java new file mode 100644 index 00000000..bb7f21c8 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/LocationService.java @@ -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 callback) { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + if (!TextUtils.isEmpty(maxId)) { + builder.put("max_id", maxId); + } + final Call request = repository.fetchPosts(locationId, builder.build()); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response 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 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 items = parseItems(itemsJson); + return new LocationPostsFetchResponse( + moreAvailable, + nextMaxId, + numResults, + status, + items + ); + } + + private List parseItems(final JSONArray items) throws JSONException { + if (items == null) { + return Collections.emptyList(); + } + final List 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 items; + + public LocationPostsFetchResponse(final boolean moreAvailable, + final String nextMaxId, + final int numResults, + final String status, + final List 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 getItems() { + return items; + } + + public LocationPostsFetchResponse setItems(final List 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 + + '}'; + } + } +} diff --git a/app/src/main/res/layout/fragment_location.xml b/app/src/main/res/layout/fragment_location.xml index 8ae9ef80..669f67d1 100644 --- a/app/src/main/res/layout/fragment_location.xml +++ b/app/src/main/res/layout/fragment_location.xml @@ -56,7 +56,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" android:text="@string/map" app:chipBackgroundColor="@null" app:chipIcon="@drawable/ic_outline_map_24" @@ -72,7 +71,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" android:text="@string/add_to_favorites" app:chipBackgroundColor="@null" app:chipIcon="@drawable/ic_outline_star_plus_24" @@ -143,16 +141,15 @@ + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + android:clipToPadding="false" /> \ No newline at end of file diff --git a/app/src/main/res/menu/location_menu.xml b/app/src/main/res/menu/location_menu.xml new file mode 100644 index 00000000..1ced72b6 --- /dev/null +++ b/app/src/main/res/menu/location_menu.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/hashtag_nav_graph.xml b/app/src/main/res/navigation/hashtag_nav_graph.xml index 33a880e2..d447bf5d 100644 --- a/app/src/main/res/navigation/hashtag_nav_graph.xml +++ b/app/src/main/res/navigation/hashtag_nav_graph.xml @@ -47,6 +47,15 @@ app:nullable="true" /> + + + + - - @@ -21,6 +19,43 @@ app:argType="boolean" /> + + + + + + + + + + + + + + + +