Add Posts view to SavedViewerFragment

This commit is contained in:
Ammar Githam 2020-11-02 22:00:07 +09:00
parent d9c30c99b7
commit ab4306ac0d
14 changed files with 621 additions and 923 deletions

View File

@ -0,0 +1,71 @@
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.enums.PostItemType;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class SavedPostFetchService implements PostFetcher.PostFetchService {
private final ProfileService profileService;
private final String profileId;
private final PostItemType type;
private String nextMaxId;
private boolean moreAvailable;
public SavedPostFetchService(final String profileId, final PostItemType type) {
this.profileId = profileId;
this.type = type;
profileService = ProfileService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() {
@Override
public void onSuccess(final SavedPostsFetchResponse 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);
}
}
};
switch (type) {
case LIKED:
profileService.fetchLiked(nextMaxId, callback);
break;
case TAGGED:
profileService.fetchTagged(profileId, nextMaxId, callback);
break;
case SAVED:
default:
profileService.fetchSaved(nextMaxId, callback);
break;
}
}
@Override
public void reset() {
nextMaxId = null;
}
@Override
public boolean hasNextPage() {
return moreAvailable;
}
}

View File

@ -1,199 +0,0 @@
package awais.instagrabber.customviews;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import awais.instagrabber.R;
public final class RemixDrawerLayout extends MouseDrawer implements MouseDrawer.DrawerListener {
private final FrameLayout frameLayout;
private View drawerView;
private RecyclerView highlightsList, feedPosts, feedStories;
private float startX;
public RemixDrawerLayout(@NonNull final Context context) {
this(context, null);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
this(context, attrs, 0);
}
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
super.setDrawerElevation(getDrawerElevation());
addDrawerListener(this);
frameLayout = new FrameLayout(context);
frameLayout.setPadding(0, 0, 0, 0);
super.addView(frameLayout);
}
@Override
public void addView(@NonNull final View child, final ViewGroup.LayoutParams params) {
child.setLayoutParams(params);
addView(child);
}
@Override
public void addView(@NonNull final View child) {
if (child.getTag() != null) super.addView(child);
else frameLayout.addView(child);
}
@Override
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
// another one of my own weird hack thingies to make this app work
if (feedPosts == null) feedPosts = findViewById(R.id.feedPosts);
if (feedPosts != null) {
for (int i = 0; i < feedPosts.getChildCount(); ++i) {
final View viewHolder = feedPosts.getChildAt(i);
final View mediaList = viewHolder.findViewById(R.id.media_list);
if (mediaList instanceof ViewPager) {
final ViewPager viewPager = (ViewPager) mediaList;
final Rect rect = new Rect();
viewPager.getGlobalVisibleRect(rect);
final boolean touchIsInMediaList = rect.contains((int) x, (int) y);
if (touchIsInMediaList) {
final PagerAdapter adapter = viewPager.getAdapter();
final int count = adapter != null ? adapter.getCount() : 0;
if (count < 1 || viewPager.getCurrentItem() != count - 1) return false;
break;
}
}
}
}
// thanks to Fede @ https://stackoverflow.com/questions/6920137/android-viewpager-and-horizontalscrollview/7258579#7258579
if (highlightsList == null) highlightsList = findViewById(R.id.highlightsList);
if (highlightsList != null) {
final Boolean result = handleHorizontalRecyclerView(ev, highlightsList);
if (result != null) {
return result;
}
}
if (feedStories == null) feedStories = findViewById(R.id.feedStories);
if (feedStories != null) {
final Boolean result = handleHorizontalRecyclerView(ev, feedStories);
if (result != null) {
return result;
}
}
return super.onInterceptTouchEvent(ev);
}
private Boolean handleHorizontalRecyclerView(@NonNull final MotionEvent ev, final RecyclerView view) {
final float x = ev.getX();
final float y = ev.getY();
final boolean touchIsInRecycler = x >= view.getLeft() && x < view.getRight()
&& y >= view.getTop() && view.getBottom() > y;
if (touchIsInRecycler) {
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_CANCEL) return super.onInterceptTouchEvent(ev);
if (action == MotionEvent.ACTION_DOWN) startX = x;
else if (action == MotionEvent.ACTION_MOVE) {
final int scrollRange = view.computeHorizontalScrollRange();
final int scrollOffset = view.computeHorizontalScrollOffset();
final boolean scrollable = scrollRange > view.getWidth();
final boolean draggingFromRight = startX > x;
if (scrollOffset < 1) {
if (!scrollable) return super.onInterceptTouchEvent(ev);
else if (!draggingFromRight) return super.onInterceptTouchEvent(ev);
} else if (scrollable && draggingFromRight && scrollRange - scrollOffset == view.computeHorizontalScrollExtent()) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
}
return null;
}
@Override
public void onDrawerSlide(@NonNull final View view, @EdgeGravity final int gravity, final float slideOffset) {
drawerView = view;
final int absHorizGravity = getDrawerViewAbsoluteGravity(GravityCompat.START);
final int childAbsGravity = getDrawerViewAbsoluteGravity(drawerView);
final Window window = getActivity(getContext()).getWindow();
final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| window.getDecorView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
|| getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
final int drawerViewWidth = drawerView.getWidth();
// for (int i = 0; i < frameLayout.getChildCount(); i++) {
// final View child = frameLayout.getChildAt(i);
//
// final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
// float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
//
// child.setX(width * slideOffset);
// }
final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity);
float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth;
frameLayout.setX(width * (isRtl ? -slideOffset : slideOffset));
}
@Override
public void openDrawer(@NonNull final View drawerView, final boolean animate) {
super.openDrawer(drawerView, animate);
post(() -> onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f));
}
@Override
protected void onConfigurationChanged(final Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (drawerView != null) onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f);
}
private static Activity getActivity(final Context context) {
if (context != null) {
if (context instanceof Activity) return (Activity) context;
if (context instanceof ContextWrapper)
return getActivity(((ContextWrapper) context).getBaseContext());
}
return null;
}
final int getDrawerViewAbsoluteGravity(final int gravity) {
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
}
final int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) {
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return getDrawerViewAbsoluteGravity(gravity);
}
}

View File

@ -2,81 +2,69 @@ package awais.instagrabber.fragments;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.os.Handler;
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;
import android.widget.LinearLayout;
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.appcompat.app.AppCompatActivity;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.i.iLikedFetcher;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.SavedPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentSavedBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.ProfileFragmentDirections;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static AsyncTask<?, ?, ?> currentlyExecuting;
private PostsAdapter postsAdapter;
private boolean hasNextPage;
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private FragmentSavedBinding binding;
private String username;
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private ArrayList<PostModel> selectedItems = new ArrayList<>();
private ActionMode actionMode;
private PostsViewModel postsViewModel;
private LinearLayout root;
private SwipeRefreshLayout root;
private AppCompatActivity fragmentActivity;
private boolean shouldRefresh = true;
private PostItemType type;
private String profileId;
private final ArrayList<PostModel> selectedItems = new ArrayList<>();
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
// if (postsAdapter == null) return;
// postsAdapter.clearSelection();
}
};
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -90,64 +78,112 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) {
return false;
}
final Context context = getContext();
if (context == null) return false;
DownloadUtils.batchDownload(context,
username,
DownloadMethod.DOWNLOAD_SAVED,
postsAdapter.getSelectedModels());
checkAndResetAction();
// if (postsAdapter == null || username == null) {
// return false;
// }
// final Context context = getContext();
// if (context == null) return false;
// DownloadUtils.batchDownload(context,
// username,
// DownloadMethod.DOWNLOAD_SAVED,
// postsAdapter.getSelectedModels());
// checkAndResetAction();
return true;
}
return false;
}
});
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() {
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
public void onResult(final List<PostModel> result) {
final List<PostModel> current = postsViewModel.getList().getValue();
if (result != null && !result.isEmpty()) {
if (current == null) {
postsViewModel.getList().postValue(result);
} else {
final List<PostModel> currentCopy = new ArrayList<>(current);
currentCopy.addAll(result);
postsViewModel.getList().postValue(currentCopy);
}
binding.mainPosts.post(() -> {
binding.mainPosts.setNestedScrollingEnabled(true);
binding.mainPosts.setVisibility(View.VISIBLE);
});
public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
openPostDialog(feedModel, profilePicView, mainPostImage, -1);
}
final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null;
if (model != null) {
endCursor = model.getEndCursor();
hasNextPage = model.hasNextPage();
if (hasNextPage) {
fetchPosts();
} else {
binding.swipeRefreshLayout.setRefreshing(false);
}
model.setPageCursor(false, null);
}
} else if (current == null) {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show();
NavHostFragment.findNavController(SavedViewerFragment.this).popBackStack();
@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 = ProfileFragmentDirections.actionGlobalCommentsViewerFragment(
feedModel.getShortCode(),
feedModel.getPostId(),
feedModel.getProfileModel().getId()
);
NavHostFragment.findNavController(SavedViewerFragment.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;
}
binding.swipeRefreshLayout.setRefreshing(false);
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
}
@Override
public void onHashtagClick(final String hashtag) {
final NavDirections action = ProfileFragmentDirections.actionGlobalHashTagFragment(hashtag);
NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action);
}
@Override
public void onLocationClick(final FeedModel feedModel) {
final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(feedModel.getLocationId());
NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action);
}
@Override
public void onMentionClick(final String mention) {
navigateToProfile(mention.trim());
}
@Override
public void onNameClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onProfilePicClick(final FeedModel feedModel, final View profilePicView) {
navigateToProfile("@" + feedModel.getProfileModel().getUsername());
}
@Override
public void onURLClick(final String url) {
Utils.openURL(getContext(), url);
}
@Override
public void onEmailClick(final String emailId) {
Utils.openEmailAddress(getContext(), emailId);
}
private void openPostDialog(final FeedModel feedModel,
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private Observer<List<PostModel>> listObserver;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity();
setHasOptionsMenu(true);
}
@Override
@ -164,22 +200,34 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init();
shouldRefresh = false;
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.saved_viewer_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 onResume() {
super.onResume();
setTitle();
observeData();
}
private void observeData() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class);
postsViewModel.getList().removeObserver(listObserver);
if (postsAdapter != null) {
postsViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
}
@Override
public void onRefresh() {
binding.posts.refresh();
}
private void init() {
@ -189,122 +237,79 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
username = fragmentArgs.getUsername();
profileId = fragmentArgs.getProfileId();
type = fragmentArgs.getType();
setTitle();
binding.swipeRefreshLayout.setOnRefreshListener(this);
// autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS);
binding.mainPosts.setNestedScrollingEnabled(false);
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;
final String[] idsOrShortCodes = new String[postModels.size()];
for (int i = 0; i < postModels.size(); i++) {
final PostModel tempPostModel = postModels.get(i);
final String tempId = tempPostModel.getPostId();
final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId;
idsOrShortCodes[i] = isId ? finalPostId
: tempPostModel.getShortCode();
}
final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
position,
idsOrShortCodes,
isId);
NavHostFragment.findNavController(this).navigate(action);
}, (model, position) -> {
if (!postsAdapter.isSelecting()) {
checkAndResetAction();
return true;
}
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
if (onBackPressedCallback.isEnabled()) return true;
actionMode = fragmentActivity.startActionMode(multiSelectAction);
final String title = getString(R.string.number_selected, 1);
actionMode.setTitle(title);
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true;
});
binding.mainPosts.setAdapter(postsAdapter);
listObserver = list -> postsAdapter.submitList(list);
observeData();
binding.swipeRefreshLayout.setRefreshing(true);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (hasNextPage) {
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
endCursor = null;
}
});
binding.mainPosts.addOnScrollListener(lazyLoader);
fetchPosts();
setupPosts();
// 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;
// final String[] idsOrShortCodes = new String[postModels.size()];
// for (int i = 0; i < postModels.size(); i++) {
// final PostModel tempPostModel = postModels.get(i);
// final String tempId = tempPostModel.getPostId();
// final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId;
// idsOrShortCodes[i] = isId ? finalPostId
// : tempPostModel.getShortCode();
// }
// final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
// position,
// idsOrShortCodes,
// isId);
// NavHostFragment.findNavController(this).navigate(action);
// }, (model, position) -> {
// if (!postsAdapter.isSelecting()) {
// checkAndResetAction();
// return true;
// }
// final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
// if (onBackPressedCallback.isEnabled()) return true;
// actionMode = fragmentActivity.startActionMode(multiSelectAction);
// final String title = getString(R.string.number_selected, 1);
// actionMode.setTitle(title);
// onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
// return true;
// });
}
private void fetchPosts() {
stopCurrentExecutor();
final AsyncTask<Void, Void, List<PostModel>> asyncTask;
private void setupPosts() {
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new SavedPostFetchService(profileId, type))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(getPostsLayoutPreferenceKey())))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
.init();
binding.swipeRefreshLayout.setRefreshing(true);
}
@NonNull
private String getPostsLayoutPreferenceKey() {
switch (type) {
case LIKED:
asyncTask = new iLikedFetcher(endCursor, postsFetchListener);
break;
case SAVED:
return Constants.PREF_LIKED_POSTS_LAYOUT;
case TAGGED:
if (TextUtils.isEmpty(profileId)) return;
asyncTask = new PostsFetcher(profileId, type, endCursor, postsFetchListener);
break;
return Constants.PREF_TAGGED_POSTS_LAYOUT;
case SAVED:
default:
return;
return Constants.PREF_SAVED_POSTS_LAYOUT;
}
currentlyExecuting = asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRefresh() {
if (lazyLoader != null) lazyLoader.resetState();
stopCurrentExecutor();
endCursor = null;
postsViewModel.getList().postValue(Collections.emptyList());
selectedItems.clear();
if (postsAdapter != null) {
// postsAdapter.isSelecting = false;
postsAdapter.notifyDataSetChanged();
}
binding.swipeRefreshLayout.setRefreshing(true);
fetchPosts();
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED && selectedItems.size() > 0) {
final Context context = getContext();
if (context == null) return;
DownloadUtils.batchDownload(context, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems);
}
}
public static void stopCurrentExecutor() {
if (currentlyExecuting != null) {
try {
currentlyExecuting.cancel(true);
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// final Context context = getContext();
// if (context == null) return;
// DownloadUtils.batchDownload(context, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems);
}
}
@ -313,9 +318,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
if (actionBar == null) return;
final int titleRes;
switch (type) {
case SAVED:
titleRes = R.string.saved;
break;
case LIKED:
titleRes = R.string.liked;
break;
@ -323,12 +325,74 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
titleRes = R.string.tagged;
break;
default:
return; // no other types supported in this view
case SAVED:
titleRes = R.string.saved;
break;
}
actionBar.setTitle(titleRes);
actionBar.setSubtitle(username);
}
private void updateSwipeRefreshState() {
binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching());
}
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 void showPostsLayoutPreferences() {
final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
getPostsLayoutPreferenceKey(),
preferences -> new Handler().postDelayed(() -> binding.posts.setLayoutPreferences(preferences), 200));
fragment.show(getChildFragmentManager(), "posts_layout_preferences");
}
private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false;

View File

@ -14,4 +14,13 @@ public interface ProfileRepository {
@GET("/graphql/query/")
Call<String> fetch(@QueryMap Map<String, String> queryMap);
@GET("/api/v1/feed/saved/")
Call<String> fetchSaved(@QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/liked/")
Call<String> fetchLiked(@QueryMap Map<String, String> queryParams);
@GET("/api/v1/usertags/{profileId}/feed/")
Call<String> fetchTagged(@Path("profileId") final String profileId, @QueryMap Map<String, String> queryParams);
}

View File

@ -92,4 +92,7 @@ public final class Constants {
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";
public static final String PREF_LIKED_POSTS_LAYOUT = "liked_posts_layout";
public static final String PREF_TAGGED_POSTS_LAYOUT = "tagged_posts_layout";
public static final String PREF_SAVED_POSTS_LAYOUT = "saved_posts_layout";
}

View File

@ -31,9 +31,12 @@ 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_LIKED_POSTS_LAYOUT;
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_SAVED_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_TAGGED_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
@ -119,7 +122,8 @@ 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_LOCATION_POSTS_LAYOUT})
PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT,
PREF_SAVED_POSTS_LAYOUT})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,

View File

@ -118,7 +118,7 @@ public class DiscoverService extends BaseService {
final JSONObject clusterJson = clustersJson.getJSONObject(i);
final String id = clusterJson.optString("id");
final String title = clusterJson.optString("title");
if (id == null || title == null) {
if (TextUtils.isEmpty(id) || TextUtils.isEmpty(title)) {
continue;
}
final String type = clusterJson.optString("type");

View File

@ -4,6 +4,8 @@ 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;
@ -13,6 +15,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
@ -282,4 +285,242 @@ public class ProfileService extends BaseService {
}
return sliderItems;
}
public void fetchSaved(final String maxId,
final ServiceCallback<SavedPostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<String> request = repository.fetchSaved(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 SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, true);
callback.onSuccess(savedPostsFetchResponse);
} 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);
}
}
});
}
public void fetchLiked(final String maxId,
final ServiceCallback<SavedPostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<String> request = repository.fetchLiked(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 SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, false);
callback.onSuccess(savedPostsFetchResponse);
} 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);
}
}
});
}
public void fetchTagged(final String profileId,
final String maxId,
final ServiceCallback<SavedPostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<String> request = repository.fetchTagged(profileId, 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 SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, false);
callback.onSuccess(savedPostsFetchResponse);
} 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 SavedPostsFetchResponse parseSavedPostsResponse(final String body, final boolean isInMedia) 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, isInMedia);
return new SavedPostsFetchResponse(
moreAvailable,
nextMaxId,
numResults,
status,
items
);
}
private List<FeedModel> parseItems(final JSONArray items, final boolean isInMedia) 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(isInMedia ? itemJson.optJSONObject("media") : itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
public static class SavedPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public SavedPostsFetchResponse(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 SavedPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public SavedPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public SavedPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public SavedPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public SavedPostsFetchResponse 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 SavedPostsFetchResponse that = (SavedPostsFetchResponse) 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 "SavedPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
}

View File

@ -267,6 +267,7 @@ public class TagsService extends BaseService {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
}
@NonNull
@Override
public String toString() {
return "TagPostsFetchResponse{" +

View File

@ -1,28 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".fragments.SavedViewerFragment">
<!--<include-->
<!-- android:id="@+id/toolbar"-->
<!-- layout="@layout/layout_include_toolbar" />-->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
<awais.instagrabber.customviews.PostsRecyclerView
android:id="@+id/posts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="@android:string/yes">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingStart="8dp"
android:paddingEnd="8dp"
tools:listitem="@layout/item_post" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feedLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:animateLayoutChanges="true"
android:tag="@android:string/yes">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feedStories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingTop="24dp"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:visibility="gone" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/feedSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/feedPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_feed_photo" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,463 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null">
<!-- for users -->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<RelativeLayout
android:id="@+id/infoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@null"
android:orientation="vertical"
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/profileInfoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileInfo"
android:orientation="horizontal"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified"
android:layout_width="18dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:visibility="gone"
app:srcCompat="@drawable/verified" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFullName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
tools:text="Austin Huang" />
</LinearLayout>
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainBiography"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileInfoText"
android:background="?android:selectableItemBackground"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
tools:text="THE GLORIOUS (step)OWNER OF THIS APP" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/mainUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/mainBiography"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone"
tools:text="https://austinhuang.me/" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/profileActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/mainUrl">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFollow"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_pink_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnRestrict"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/restrict"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<!-- only for invisible private accounts -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnBlock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/block"
android:textColor="@color/btn_red_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_red_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/myActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/profileActions">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnTagged"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/tagged"
android:textColor="@color/btn_blue_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" />
<!-- also used as block -->
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/saved"
android:textColor="@color/btn_orange_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_orange_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnLiked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="1dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/liked"
android:textColor="@color/btn_lightpink_text_color"
android:textSize="16sp"
android:visibility="gone"
app:backgroundTint="@color/btn_lightpink_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/highlightsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/myActions"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="5dp"
android:paddingLeft="5dp"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:visibility="gone" />
<LinearLayout
android:id="@+id/profileInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainProfileImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFollowers"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?selectableItemBackgroundBorderless"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="68\nFollowers" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFollowing"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:background="?selectableItemBackgroundBorderless"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="64\nFollowing" />
</LinearLayout>
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<!-- for hashtags -->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<LinearLayout
android:id="@+id/tagInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space"
android:visibility="gone">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainTagPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFollowTag"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
app:backgroundTint="@color/btn_pink_background" />
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<!-- for locations -->
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<RelativeLayout
android:id="@+id/locInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@null"
android:orientation="vertical"
android:paddingBottom="5dp"
android:visibility="gone">
<LinearLayout
android:id="@+id/locInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainLocationImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainLocPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnMap"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:text="@string/map"
android:textColor="@color/btn_green_text_color"
android:textSize="20sp"
app:backgroundTint="@color/btn_green_background" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/locationFullName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/locInfo"
android:layout_weight="1"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textSize="16sp"
tools:text="OUR HOUSE" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationBiography"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/locationFullName"
android:background="?android:selectableItemBackground"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone"
tools:text="IN THE MIDDLE OF OUR STREET" />
<awais.instagrabber.customviews.RamboTextView
android:id="@+id/locationUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/locationBiography"
android:ellipsize="marquee"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:paddingEnd="10dp"
android:paddingRight="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="16sp"
android:visibility="gone"
tools:text="https://austinhuang.me/" />
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@layout/item_post" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/privatePage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/private_page_margins"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="40dp"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/privatePage1"
android:layout_width="@dimen/private_page_margins"
android:layout_height="@dimen/private_page_margins"
app:srcCompat="@drawable/lock" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/privatePage2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/priv_acc"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textSize="28sp" />
</LinearLayout>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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

@ -5,6 +5,25 @@
android:id="@+id/profile_nav_graph"
app:startDestination="@id/profileFragment">
<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>
<include app:graph="@navigation/hashtag_nav_graph" />
<action