From 4d6ac5d2939b1f88f71d3c762507c3d9d71a43ff Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 19 Dec 2020 21:38:21 -0500 Subject: [PATCH] profile viewer improvement 1. restore tagged posts access for anons 2. chip-ify profile viewer, bring it to consistency with tag/loc viewers 3. add a following/er status chip 4. pluralize "post(s)" and "follower(s)" 5. correct favourited string --- .../instagrabber/asyncs/CommentsFetcher.java | 3 + .../instagrabber/asyncs/PostFetcher.java | 1 + .../instagrabber/asyncs/ProfileFetcher.java | 1 + .../asyncs/SavedPostFetchService.java | 2 +- .../fragments/HashTagFragment.java | 4 +- .../fragments/LocationFragment.java | 5 +- .../fragments/main/ProfileFragment.java | 200 ++++++++++-------- .../instagrabber/models/HashtagModel.java | 2 +- .../instagrabber/models/LocationModel.java | 2 +- .../instagrabber/models/ProfileModel.java | 31 +-- .../repositories/ProfileRepository.java | 3 - .../instagrabber/utils/ResponseBodyUtils.java | 10 +- .../webservices/DiscoverService.java | 1 + .../webservices/ProfileService.java | 181 +++++----------- .../webservices/StoriesService.java | 2 +- .../res/layout/layout_profile_details.xml | 132 +++++++----- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 16 +- 18 files changed, 301 insertions(+), 296 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java index 77ab5371..4876e3e3 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java @@ -122,6 +122,7 @@ public final class CommentsFetcher extends AsyncTask { owner.optInt("edge_followed_by"), -1, owner.optBoolean("followed_by_viewer"), + false, owner.optBoolean("restricted_by_viewer"), owner.optBoolean("blocked_by_viewer"), owner.optBoolean("requested_by_viewer") diff --git a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java index e2e4fe2c..7e78b0b2 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java @@ -72,6 +72,7 @@ public final class ProfileFetcher extends AsyncTask { user.getJSONObject("edge_followed_by").getLong("count"), user.getJSONObject("edge_follow").getLong("count"), user.optBoolean("followed_by_viewer"), + user.optBoolean("follows_viewer"), user.optBoolean("restricted_by_viewer"), user.optBoolean("blocked_by_viewer"), user.optBoolean("requested_by_viewer")); diff --git a/app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java b/app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java index 36c668d1..fcae9b65 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java +++ b/app/src/main/java/awais/instagrabber/asyncs/SavedPostFetchService.java @@ -50,7 +50,7 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService { profileService.fetchLiked(nextMaxId, callback); break; case TAGGED: - profileService.fetchTagged(profileId, nextMaxId, callback); + profileService.fetchTagged(profileId, 30, nextMaxId, callback); break; case SAVED: default: diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 26733e12..c89f7970 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -513,7 +513,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe })); hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); final String postCount = String.valueOf(hashtagModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); + final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, + hashtagModel.getPostCount() > 2000000000L ? 2000000000 : hashtagModel.getPostCount().intValue(), + postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); hashtagDetailsBinding.mainTagPostCount.setText(span); diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index acccc0b9..d857c083 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -400,8 +400,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR // binding.swipeRefreshLayout.setRefreshing(true); locationDetailsBinding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); final String postCount = String.valueOf(locationModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, - postCount)); + final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, + locationModel.getPostCount() > 2000000000L ? 2000000000 : locationModel.getPostCount().intValue(), + postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); locationDetailsBinding.mainLocPostCount.setText(span); diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 4c12b4e3..180f6e25 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -370,10 +370,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } if (item.getItemId() == R.id.restrict) { if (!isLoggedIn) return false; - final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; + final String action = profileModel.isRestricted() ? "Unrestrict" : "Restrict"; friendshipService.toggleRestrict( profileModel.getId(), - !profileModel.getRestricted(), + !profileModel.isRestricted(), CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback() { @Override @@ -392,7 +392,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe if (item.getItemId() == R.id.block) { final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); if (!isLoggedIn) return false; - if (profileModel.getBlocked()) { + if (profileModel.isBlocked()) { friendshipService.unblock( userIdFromCookie, profileModel.getId(), @@ -571,44 +571,85 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe fetchStoryAndHighlights(profileId); } setupButtons(profileId, myId); - if (!profileId.equals(myId)) { - profileDetailsBinding.favCb.setVisibility(View.VISIBLE); - favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback() { - @Override - public void onSuccess(final Favorite result) { - profileDetailsBinding.favCb.setChecked(true); - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); - } + profileDetailsBinding.favChip.setVisibility(View.VISIBLE); + final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); + favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + profileDetailsBinding.favChip.setText(R.string.added_to_favs); + } - @Override - public void onDataNotAvailable() { - profileDetailsBinding.favCb.setChecked(false); - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); - } - }); - } else { - profileDetailsBinding.favCb.setVisibility(View.GONE); - } + @Override + public void onDataNotAvailable() { + profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + profileDetailsBinding.favChip.setText(R.string.add_to_favorites); + } + }); + profileDetailsBinding.favChip.setOnClickListener( + v -> favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + favoriteRepository.deleteFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + profileDetailsBinding.favChip.setText(R.string.add_to_favorites); + profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + showSnackbar(getString(R.string.removed_from_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + + @Override + public void onDataNotAvailable() { + final String finalUsername = username.startsWith("@") ? username.substring(1) : username; + favoriteRepository.insertOrUpdateFavorite(new Favorite( + -1, + finalUsername, + FavoriteType.USER, + profileModel.getName(), + profileModel.getSdProfilePic(), + new Date() + ), new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + profileDetailsBinding.favChip.setText(R.string.added_to_favs); + profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + showSnackbar(getString(R.string.added_to_favs)); + } + + @Override + public void onDataNotAvailable() {} + }); + } + })); profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getHdProfilePic()); - final long followersCount = profileModel.getFollowersCount(); - final long followingCount = profileModel.getFollowingCount(); + final Long followersCount = profileModel.getFollowersCount(); + final Long followingCount = profileModel.getFollowingCount(); final String postCount = String.valueOf(profileModel.getPostCount()); - SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, - postCount)); + SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, + profileModel.getPostCount() > 2000000000L ? 2000000000 : profileModel.getPostCount().intValue(), + postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); profileDetailsBinding.mainPostCount.setText(span); + profileDetailsBinding.mainPostCount.setVisibility(View.VISIBLE); final String followersCountStr = String.valueOf(followersCount); final int followersCountStrLen = followersCountStr.length(); - span = new SpannableStringBuilder(getString(R.string.main_posts_followers, - followersCountStr)); + span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_followers, + followersCount > 2000000000L ? 2000000000 : followersCount.intValue(), + followersCountStr)); span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0); profileDetailsBinding.mainFollowers.setText(span); + profileDetailsBinding.mainFollowers.setVisibility(View.VISIBLE); final String followingCountStr = String.valueOf(followingCount); final int followingCountStrLen = followingCountStr.length(); @@ -617,6 +658,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0); profileDetailsBinding.mainFollowing.setText(span); + profileDetailsBinding.mainFollowing.setVisibility(View.VISIBLE); profileDetailsBinding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername() : profileModel.getName()); @@ -701,10 +743,25 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe profileDetailsBinding.btnLiked.setVisibility(View.GONE); profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE); - if (profileModel.getFollowing()) { + if (profileModel.isFollowing() || profileModel.isFollower()) { + profileDetailsBinding.mainStatus.setVisibility(View.VISIBLE); + if (!profileModel.isFollowing()) { + profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.blue_800)); + profileDetailsBinding.mainStatus.setText(R.string.status_follower); + } + else if (!profileModel.isFollower()) { + profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.deep_orange_800)); + profileDetailsBinding.mainStatus.setText(R.string.status_following); + } + else { + profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.green_800)); + profileDetailsBinding.mainStatus.setText(R.string.status_mutual); + } + } + if (profileModel.isFollowing()) { profileDetailsBinding.btnFollow.setText(R.string.unfollow); profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); - } else if (profileModel.getRequested()) { + } else if (profileModel.isRequested()) { profileDetailsBinding.btnFollow.setText(R.string.cancel); profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); } else { @@ -713,7 +770,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } if (restrictMenuItem != null) { restrictMenuItem.setVisible(true); - if (profileModel.getRestricted()) { + if (profileModel.isRestricted()) { restrictMenuItem.setTitle(R.string.unrestrict); } else { restrictMenuItem.setTitle(R.string.restrict); @@ -722,7 +779,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); if (blockMenuItem != null) { blockMenuItem.setVisible(true); - if (profileModel.getBlocked()) { + if (profileModel.isBlocked()) { blockMenuItem.setTitle(R.string.unblock); } else { blockMenuItem.setTitle(R.string.block); @@ -732,7 +789,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { restrictMenuItem.setVisible(true); - if (profileModel.getRestricted()) { + if (profileModel.isRestricted()) { restrictMenuItem.setTitle(R.string.unrestrict); } else { restrictMenuItem.setTitle(R.string.restrict); @@ -771,9 +828,34 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void setupCommonListeners() { + final Context context = getContext(); final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); profileDetailsBinding.btnFollow.setOnClickListener(v -> { - if (profileModel.getFollowing() || profileModel.getRequested()) { + if (profileModel.isFollowing() && profileModel.isPrivate()) { + new AlertDialog.Builder(context) + .setTitle(R.string.priv_acc) + .setMessage(R.string.priv_acc_confirm) + .setPositiveButton(R.string.confirm, (d, w) -> + friendshipService.unfollow( + userIdFromCookie, + profileModel.getId(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoChangeRootResponse result) { + // Log.d(TAG, "Unfollow success: " + result); + onRefresh(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error unfollowing", t); + } + })) + .setNegativeButton(R.string.cancel, null) + .show(); + } + else if (profileModel.isFollowing() || profileModel.isRequested()) { friendshipService.unfollow( userIdFromCookie, profileModel.getId(), @@ -860,66 +942,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } showProfilePicDialog(); }; - final Context context = getContext(); if (context == null) return; new AlertDialog.Builder(context) .setItems(options, profileDialogListener) .setNegativeButton(R.string.cancel, null) .show(); }); - profileDetailsBinding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> { - // do not do anything if state matches the db, as listener is set before profile details are set - final Context context = getContext(); - if (context == null) return; - final String finalUsername = username.startsWith("@") ? username.substring(1) : username; - favoriteRepository.getFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback() { - @Override - public void onSuccess(final Favorite result) { - if (isChecked) return; // already a fav - buttonView.setVisibility(View.GONE); - profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); - favoriteRepository.deleteFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback() { - @Override - public void onSuccess(final Void result) { - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); - profileDetailsBinding.favProgress.setVisibility(View.GONE); - profileDetailsBinding.favCb.setVisibility(View.VISIBLE); - showSnackbar(getString(R.string.removed_from_favs)); - } - - @Override - public void onDataNotAvailable() {} - }); - } - - @Override - public void onDataNotAvailable() { - if (!isChecked) return; // not in fav already - buttonView.setVisibility(View.GONE); - profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); - final Favorite model = new Favorite( - -1, - finalUsername, - FavoriteType.USER, - profileModel.getName(), - profileModel.getSdProfilePic(), - new Date() - ); - favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback() { - @Override - public void onSuccess(final Void result) { - profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); - profileDetailsBinding.favProgress.setVisibility(View.GONE); - profileDetailsBinding.favCb.setVisibility(View.VISIBLE); - showSnackbar(getString(R.string.added_to_favs)); - } - - @Override - public void onDataNotAvailable() {} - }); - } - }); - }); } private void showSnackbar(final String message) { diff --git a/app/src/main/java/awais/instagrabber/models/HashtagModel.java b/app/src/main/java/awais/instagrabber/models/HashtagModel.java index fbcac9db..58ff7932 100755 --- a/app/src/main/java/awais/instagrabber/models/HashtagModel.java +++ b/app/src/main/java/awais/instagrabber/models/HashtagModel.java @@ -29,7 +29,7 @@ public final class HashtagModel implements Serializable { return sdProfilePic; } - public long getPostCount() { return postCount; } + public Long getPostCount() { return postCount; } public boolean getFollowing() { return following; } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/LocationModel.java b/app/src/main/java/awais/instagrabber/models/LocationModel.java index fedaa773..b237d4e4 100755 --- a/app/src/main/java/awais/instagrabber/models/LocationModel.java +++ b/app/src/main/java/awais/instagrabber/models/LocationModel.java @@ -59,5 +59,5 @@ public final class LocationModel implements Serializable { return sdProfilePic; } - public long getPostCount() { return postCount; } + public Long getPostCount() { return postCount; } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/ProfileModel.java b/app/src/main/java/awais/instagrabber/models/ProfileModel.java index e6367ef6..ffed64f7 100755 --- a/app/src/main/java/awais/instagrabber/models/ProfileModel.java +++ b/app/src/main/java/awais/instagrabber/models/ProfileModel.java @@ -3,15 +3,15 @@ package awais.instagrabber.models; import java.io.Serializable; public final class ProfileModel implements Serializable { - private final boolean isPrivate, reallyPrivate, isVerified, following, restricted, blocked, requested; + private final boolean isPrivate, reallyPrivate, isVerified, following, follower, restricted, blocked, requested; private final long postCount, followersCount, followingCount; private final String id, username, name, biography, url, sdProfilePic, hdProfilePic; public ProfileModel(final boolean isPrivate, final boolean reallyPrivate, final boolean isVerified, final String id, final String username, final String name, final String biography, final String url, final String sdProfilePic, final String hdProfilePic, final long postCount, - final long followersCount, final long followingCount, final boolean following, final boolean restricted, - final boolean blocked, final boolean requested) { + final long followersCount, final long followingCount, final boolean following, final boolean follower, + final boolean restricted, final boolean blocked, final boolean requested) { this.isPrivate = isPrivate; this.reallyPrivate = reallyPrivate; this.isVerified = isVerified; @@ -26,21 +26,22 @@ public final class ProfileModel implements Serializable { this.followersCount = followersCount; this.followingCount = followingCount; this.following = following; + this.follower = follower; this.restricted = restricted; this.blocked = blocked; this.requested = requested; } public static ProfileModel getDefaultProfileModel() { - return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false); + return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); } public static ProfileModel getDefaultProfileModel(final String userId) { - return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false); + return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); } public static ProfileModel getDefaultProfileModel(final String userId, final String username) { - return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false); + return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); } public boolean isPrivate() { @@ -83,31 +84,35 @@ public final class ProfileModel implements Serializable { return hdProfilePic; } - public long getPostCount() { + public Long getPostCount() { return postCount; } - public long getFollowersCount() { + public Long getFollowersCount() { return followersCount; } - public long getFollowingCount() { + public Long getFollowingCount() { return followingCount; } - public boolean getFollowing() { + public boolean isFollowing() { return following; } - public boolean getRestricted() { + public boolean isFollower() { + return follower; + } + + public boolean isRestricted() { return restricted; } - public boolean getBlocked() { + public boolean isBlocked() { return blocked; } - public boolean getRequested() { + public boolean isRequested() { return requested; } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java index 8643b682..ec6a0d6e 100644 --- a/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/ProfileRepository.java @@ -20,7 +20,4 @@ public interface ProfileRepository { @GET("/api/v1/feed/liked/") Call fetchLiked(@QueryMap Map queryParams); - - @GET("/api/v1/usertags/{profileId}/feed/") - Call fetchTagged(@Path("profileId") final String profileId, @QueryMap Map queryParams); } diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index e0f028ce..c9029a00 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -203,7 +203,7 @@ public final class ResponseBodyUtils { userObj.getString("full_name"), null, null, userObj.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); + null, 0, 0, 0, false, false, false, false, false); } final MediaItemType mediaType = getMediaItemType(mediaObj.optInt("media_type", -1)); @@ -291,7 +291,7 @@ public final class ResponseBodyUtils { userObject.getString("full_name"), null, null, userObject.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); + null, 0, 0, 0, false, false, false, false, false); } final ProfileModel[] leftuserModels = new ProfileModel[leftusersLen]; @@ -305,7 +305,7 @@ public final class ResponseBodyUtils { userObject.getString("full_name"), null, null, userObject.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); + null, 0, 0, 0, false, false, false, false, false); } final Long[] adminIDs = new Long[adminsLen]; @@ -470,7 +470,7 @@ public final class ResponseBodyUtils { profile.getString("full_name"), null, null, profile.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); + null, 0, 0, 0, false, false, false, false, false); } break; @@ -622,6 +622,7 @@ public final class ResponseBodyUtils { 0, 0, following, + false, restricted, false, requested); @@ -705,6 +706,7 @@ public final class ResponseBodyUtils { false, false, false, + false, false); } JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); diff --git a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java index 1f542694..84d7f305 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java @@ -165,6 +165,7 @@ public class DiscoverService extends BaseService { false, false, false, + false, false); } final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson); diff --git a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java index 47766743..9ba5a620 100644 --- a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java +++ b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java @@ -89,7 +89,6 @@ public class ProfileService extends BaseService { }); } - public void fetchPosts(final ProfileModel profileModel, final int postsPerPage, final String cursor, @@ -156,135 +155,18 @@ public class ProfileService extends BaseService { } final JSONArray edges = mediaPosts.getJSONArray("edges"); for (int i = 0; i < edges.length(); ++i) { - final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); - final String mediaType = mediaNode.optString("__typename"); - if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) + final JSONObject itemJson = edges.optJSONObject(i); + if (itemJson == null) { continue; - final boolean isVideo = mediaNode.getBoolean("is_video"); - final long videoViews = mediaNode.optLong("video_view_count", 0); - - final String displayUrl = mediaNode.optString("display_url"); - if (TextUtils.isEmpty(displayUrl)) continue; - final String resourceUrl; - - if (isVideo) { - resourceUrl = mediaNode.getString("video_url"); - } else { - resourceUrl = mediaNode.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(mediaNode) : displayUrl; } - JSONObject tempJsonObject = mediaNode.optJSONObject("edge_media_to_comment"); - final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; - tempJsonObject = mediaNode.optJSONObject("edge_media_preview_like"); - final long likesCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; - tempJsonObject = mediaNode.optJSONObject("edge_media_to_caption"); - final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; - String captionText = null; - if (captions != null && captions.length() > 0) { - if ((tempJsonObject = captions.optJSONObject(0)) != null && - (tempJsonObject = tempJsonObject.optJSONObject("node")) != null) { - captionText = tempJsonObject.getString("text"); - } + final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); + if (feedModel != null) { + feedModels.add(feedModel); } - final JSONObject location = mediaNode.optJSONObject("location"); - // Log.d(TAG, "location: " + (location == null ? null : location.toString())); - String locationId = null; - String locationName = null; - if (location != null) { - locationName = location.optString("name"); - if (location.has("id")) { - locationId = location.getString("id"); - } else if (location.has("pk")) { - locationId = location.getString("pk"); - } - // Log.d(TAG, "locationId: " + locationId); - } - int height = 0; - int width = 0; - final JSONObject dimensions = mediaNode.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = mediaNode.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final FeedModel.Builder builder = new FeedModel.Builder() - .setProfileModel(profileModel) - .setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setViewCount(videoViews) - .setPostId(mediaNode.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(resourceUrl) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl) - .setShortCode(mediaNode.getString(Constants.EXTRAS_SHORTCODE)) - .setPostCaption(captionText) - .setCommentsCount(commentsCount) - .setTimestamp(mediaNode.optLong("taken_at_timestamp", -1)) - .setLiked(mediaNode.getBoolean("viewer_has_liked")) - .setBookmarked(mediaNode.getBoolean("viewer_has_saved")) - .setLikesCount(likesCount) - .setLocationName(locationName) - .setLocationId(locationId) - .setImageHeight(height) - .setImageWidth(width); - final boolean isSlider = "GraphSidecar".equals(mediaType) && mediaNode.has("edge_sidecar_to_children"); - if (isSlider) { - builder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER); - final JSONObject sidecar = mediaNode.optJSONObject("edge_sidecar_to_children"); - if (sidecar != null) { - final JSONArray children = sidecar.optJSONArray("edges"); - if (children != null) { - final List sliderItems = getSliderItems(children); - builder.setSliderItems(sliderItems); - } - } - } - final FeedModel feedModel = builder.build(); - feedModels.add(feedModel); } return new PostsFetchResponse(feedModels, hasNextPage, endCursor); } - @NonNull - private List getSliderItems(final JSONArray children) throws JSONException { - final List sliderItems = new ArrayList<>(); - for (int j = 0; j < children.length(); ++j) { - final JSONObject childNode = children.optJSONObject(j).getJSONObject("node"); - final boolean isChildVideo = childNode.optBoolean("is_video"); - int height = 0; - int width = 0; - final JSONObject dimensions = childNode.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = childNode.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final PostChild sliderItem = new PostChild.Builder() - .setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setPostId(childNode.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(isChildVideo ? childNode.getString("video_url") - : childNode.getString("display_url")) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl - : childNode.getString("display_url")) - .setVideoViews(childNode.optLong("video_view_count", 0)) - .setHeight(height) - .setWidth(width) - .build(); - // Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem); - sliderItems.add(sliderItem); - } - return sliderItems; - } - public void fetchSaved(final String maxId, final ServiceCallback callback) { final ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -358,13 +240,17 @@ public class ProfileService extends BaseService { } public void fetchTagged(final String profileId, - final String maxId, + final int postsPerPage, + final String cursor, final ServiceCallback callback) { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - if (!TextUtils.isEmpty(maxId)) { - builder.put("max_id", maxId); - } - final Call request = repository.fetchTagged(profileId, builder.build()); + final Map queryMap = new HashMap<>(); + queryMap.put("query_hash", "31fe64d9463cbbe58319dced405c6206"); + queryMap.put("variables", "{" + + "\"id\":\"" + profileId + "\"," + + "\"first\":" + postsPerPage + "," + + "\"after\":\"" + (cursor == null ? "" : cursor) + "\"" + + "}"); + final Call request = wwwRepository.fetch(queryMap); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { @@ -377,7 +263,7 @@ public class ProfileService extends BaseService { callback.onSuccess(null); return; } - final SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, false); + final SavedPostsFetchResponse savedPostsFetchResponse = parseTaggedPostsResponse(body); callback.onSuccess(savedPostsFetchResponse); } catch (JSONException e) { Log.e(TAG, "onResponse", e); @@ -411,6 +297,41 @@ public class ProfileService extends BaseService { ); } + @NonNull + private SavedPostsFetchResponse parseTaggedPostsResponse(@NonNull final String body) + throws JSONException { + final List feedModels = new ArrayList<>(); + final JSONObject timelineFeed = new JSONObject(body) + .getJSONObject("data") + .getJSONObject(Constants.EXTRAS_USER) + .getJSONObject("edge_user_to_photos_of_you"); + final String endCursor; + final boolean hasNextPage; + + final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); + if (pageInfo.has("has_next_page")) { + hasNextPage = pageInfo.getBoolean("has_next_page"); + endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; + } else { + hasNextPage = false; + endCursor = null; + } + + final JSONArray feedItems = timelineFeed.getJSONArray("edges"); + + for (int i = 0; i < feedItems.length(); ++i) { + final JSONObject itemJson = feedItems.optJSONObject(i); + if (itemJson == null) { + continue; + } + final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); + if (feedModel != null) { + feedModels.add(feedModel); + } + } + return new SavedPostsFetchResponse(hasNextPage, endCursor, timelineFeed.getInt("count"), "ok", feedModels); + } + private List parseItems(final JSONArray items, final boolean isInMedia) throws JSONException { if (items == null) { return Collections.emptyList(); diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index c895a3fa..d5a0c723 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -123,7 +123,7 @@ public class StoriesService extends BaseService { user.getString("username"), null, null, null, user.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); + null, 0, 0, 0, false, false, false, false, false); final String id = node.getString("id"); final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media"); feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead)); diff --git a/app/src/main/res/layout/layout_profile_details.xml b/app/src/main/res/layout/layout_profile_details.xml index 41abd3b3..eb14c1f9 100644 --- a/app/src/main/res/layout/layout_profile_details.xml +++ b/app/src/main/res/layout/layout_profile_details.xml @@ -18,45 +18,96 @@ app:layout_constraintEnd_toStartOf="@id/mainPostCount" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="@id/fav_chip" tools:background="@mipmap/ic_launcher" /> - + app:layout_constraintTop_toTopOf="@id/mainProfileImage" + tools:text="35 Posts" /> - + app:layout_constraintTop_toTopOf="@id/mainPostCount" + tools:text="omg what do u expect" /> - + + + app:layout_constraintTop_toBottomOf="@id/mainPostCount" + app:rippleColor="@color/grey_400" + tools:text="10 Following" /> + + + + @@ -151,29 +202,12 @@ app:iconGravity="top" app:iconTint="@color/deep_purple_200" app:layout_constraintBottom_toTopOf="@id/highlights_barrier" - app:layout_constraintEnd_toStartOf="@id/btnTagged" + app:layout_constraintEnd_toStartOf="@id/btnSaved" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/mainUrl" app:rippleColor="@color/purple_200" tools:visibility="visible" /> - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ab79c8d3..3545af5f 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,6 +4,7 @@ @dimen/profile_picture_size 90dp + 40dp 40dp 24dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf373b48..b82e1caf 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,10 +45,15 @@ Use Instadp for high definition profile pictures Import/Export Language - %s\nPosts - %s Posts - %s\nFollowers - %s\nFollowing + + %s Post + %s Posts + + + %s Follower + %s Followers + + %s Following Autoplay videos Always mute videos Select what to download @@ -107,6 +112,9 @@ Unblock Restrict Unrestrict + Following each other + Followed by you + Following you Map Export Import