package awais.instagrabber.fragments.main; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.SpannableStringBuilder; import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; 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.AlertDialog; import androidx.appcompat.content.res.AppCompatResources; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.Snackbar; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Set; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.HighlightsAdapter; import awais.instagrabber.asyncs.CreateThreadAction; import awais.instagrabber.asyncs.ProfilePostFetchService; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; import awais.instagrabber.databinding.FragmentProfileBinding; import awais.instagrabber.databinding.LayoutProfileDetailsBinding; import awais.instagrabber.db.datasources.AccountDataSource; import awais.instagrabber.db.datasources.FavoriteDataSource; import awais.instagrabber.db.entities.Account; import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.db.repositories.AccountRepository; import awais.instagrabber.db.repositories.FavoriteRepository; import awais.instagrabber.db.repositories.RepositoryCallback; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.managers.DirectMessagesManager; import awais.instagrabber.managers.InboxManager; import awais.instagrabber.models.HighlightModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.responses.FriendshipChangeResponse; import awais.instagrabber.repositories.responses.FriendshipRestrictResponse; import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.UserProfileContextLink; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.AppStateViewModel; import awais.instagrabber.viewmodels.HighlightsViewModel; import awais.instagrabber.webservices.FriendshipService; import awais.instagrabber.webservices.GraphQLService; import awais.instagrabber.webservices.MediaService; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.UserService; import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "ProfileFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private MainActivity fragmentActivity; private CoordinatorLayout root; private FragmentProfileBinding binding; private boolean isLoggedIn; private String cookie; private String username; private User profileModel; private ActionMode actionMode; private Handler usernameSettingHandler; private FriendshipService friendshipService; private StoriesService storiesService; private MediaService mediaService; private UserService userService; private GraphQLService graphQLService; private boolean shouldRefresh = true; private boolean hasStories = false; private HighlightsAdapter highlightsAdapter; private HighlightsViewModel highlightsViewModel; private MenuItem blockMenuItem, restrictMenuItem, chainingMenuItem; private MenuItem muteStoriesMenuItem, mutePostsMenuItem; private boolean accountIsUpdated = false; private boolean postsSetupDone = false; private Set selectedFeedModels; private Media downloadFeedModel; private int downloadChildPosition = -1; private long myId; private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_PROFILE_POSTS_LAYOUT); private final Runnable usernameSettingRunnable = () -> { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); if (actionBar != null && !TextUtils.isEmpty(username)) { final String finalUsername = username.startsWith("@") ? username.substring(1) : username; actionBar.setTitle(finalUsername); actionBar.setSubtitle(null); } }; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override public void handleOnBackPressed() { binding.postsRecyclerView.endSelection(); } }; private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( R.menu.multi_select_download_menu, new CallbacksHelper() { @Override public void onDestroy(final ActionMode mode) { binding.postsRecyclerView.endSelection(); } @Override public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { if (item.getItemId() == R.id.action_download) { if (ProfileFragment.this.selectedFeedModels == null) return false; final Context context = getContext(); if (context == null) return false; if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels)); binding.postsRecyclerView.endSelection(); return true; } requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION); return true; } return false; } }); private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { @Override public void onPostClick(final Media feedModel, final View profilePicView, final View mainPostImage) { openPostDialog(feedModel, profilePicView, mainPostImage, -1); } @Override public void onSliderClick(final Media feedModel, final int position) { openPostDialog(feedModel, null, null, position); } @Override public void onCommentsClick(final Media feedModel) { final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment( feedModel.getCode(), feedModel.getPk(), feedModel.getUser().getPk() ); NavHostFragment.findNavController(ProfileFragment.this).navigate(commentsAction); } @Override public void onDownloadClick(final Media feedModel, final int childPosition) { final Context context = getContext(); if (context == null) return; if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) { DownloadUtils.showDownloadDialog(context, feedModel, childPosition); return; } downloadFeedModel = feedModel; downloadChildPosition = childPosition; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @Override public void onHashtagClick(final String hashtag) { final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag); NavHostFragment.findNavController(ProfileFragment.this).navigate(action); } @Override public void onLocationClick(final Media feedModel) { final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); NavHostFragment.findNavController(ProfileFragment.this).navigate(action); } @Override public void onMentionClick(final String mention) { navigateToProfile(mention.trim()); } @Override public void onNameClick(final Media feedModel, final View profilePicView) { navigateToProfile("@" + feedModel.getUser().getUsername()); } @Override public void onProfilePicClick(final Media feedModel, final View profilePicView) { navigateToProfile("@" + feedModel.getUser().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 Media feedModel, final View profilePicView, final View mainPostImage, final int position) { final PostViewV2Fragment.Builder builder = PostViewV2Fragment .builder(feedModel); if (position >= 0) { builder.setPosition(position); } if (!layoutPreferences.isAnimationDisabled()) { builder.setSharedProfilePicElement(profilePicView) .setSharedMainPostElement(mainPostImage); } final PostViewV2Fragment postViewV2Fragment = builder.build(); postViewV2Fragment.setOnDeleteListener(() -> { postViewV2Fragment.dismiss(); binding.postsRecyclerView.refresh(); }); final FragmentManager fragmentManager = getChildFragmentManager(); if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) return; postViewV2Fragment.show(fragmentManager, "post_view"); } }; private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { @Override public void onSelectionStart() { if (!onBackPressedCallback.isEnabled()) { final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); onBackPressedCallback.setEnabled(true); onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); } if (actionMode == null) { actionMode = fragmentActivity.startActionMode(multiSelectAction); } } @Override public void onSelectionChange(final Set selectedFeedModels) { final String title = getString(R.string.number_selected, selectedFeedModels.size()); if (actionMode != null) { actionMode.setTitle(title); } ProfileFragment.this.selectedFeedModels = selectedFeedModels; } @Override public void onSelectionEnd() { if (onBackPressedCallback.isEnabled()) { onBackPressedCallback.setEnabled(false); onBackPressedCallback.remove(); } if (actionMode != null) { actionMode.finish(); actionMode = null; } } }; private LayoutProfileDetailsBinding profileDetailsBinding; private AccountRepository accountRepository; private FavoriteRepository favoriteRepository; private AppStateViewModel appStateViewModel; private boolean disableDm = false; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); cookie = Utils.settingsHelper.getString(Constants.COOKIE); isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; myId = CookieUtils.getUserIdFromCookie(cookie); final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); fragmentActivity = (MainActivity) requireActivity(); friendshipService = isLoggedIn ? FriendshipService.getInstance(deviceUuid, csrfToken, myId) : null; storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null; mediaService = isLoggedIn ? MediaService.getInstance(null, null, 0) : null; userService = isLoggedIn ? UserService.getInstance() : null; graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); final Context context = getContext(); if (context == null) return; accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class); setHasOptionsMenu(true); } @Override public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { if (root != null) { if (getArguments() != null) { final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments()); final String username = fragmentArgs.getUsername(); if (TextUtils.isEmpty(username) && profileModel != null) { final String profileModelUsername = profileModel.getUsername(); final boolean isSame = ("@" + profileModelUsername).equals(this.username); if (isSame) { setUsernameDelayed(); fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot()); shouldRefresh = false; return root; } } if (username == null || !username.equals(this.username)) { fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot()); shouldRefresh = true; return root; } } setUsernameDelayed(); fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot()); shouldRefresh = false; return root; } binding = FragmentProfileBinding.inflate(inflater, container, false); root = binding.getRoot(); profileDetailsBinding = LayoutProfileDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false); fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot()); return root; } @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.profile_menu, menu); blockMenuItem = menu.findItem(R.id.block); final boolean isNotMe = profileModel != null && !Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie)); if (blockMenuItem != null) { if (isNotMe) { blockMenuItem.setVisible(true); blockMenuItem.setTitle(profileModel.getFriendshipStatus().isBlocking() ? R.string.unblock : R.string.block); } else { blockMenuItem.setVisible(false); } } restrictMenuItem = menu.findItem(R.id.restrict); if (restrictMenuItem != null) { if (isNotMe) { restrictMenuItem.setVisible(true); restrictMenuItem.setTitle(profileModel.getFriendshipStatus().isRestricted() ? R.string.unrestrict : R.string.restrict); } else { restrictMenuItem.setVisible(false); } } muteStoriesMenuItem = menu.findItem(R.id.mute_stories); if (muteStoriesMenuItem != null) { if (isNotMe) { muteStoriesMenuItem.setVisible(true); muteStoriesMenuItem.setTitle(profileModel.getFriendshipStatus().isMutingReel() ? R.string.mute_stories : R.string.unmute_stories); } else { muteStoriesMenuItem.setVisible(false); } } mutePostsMenuItem = menu.findItem(R.id.mute_posts); if (mutePostsMenuItem != null) { if (isNotMe) { mutePostsMenuItem.setVisible(true); mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.mute_posts : R.string.unmute_posts); } else { mutePostsMenuItem.setVisible(false); } } chainingMenuItem = menu.findItem(R.id.chaining); if (chainingMenuItem != null) { chainingMenuItem.setVisible(isNotMe); } } @Override public boolean onOptionsItemSelected(@NonNull final MenuItem item) { if (item.getItemId() == R.id.layout) { showPostsLayoutPreferences(); return true; } if (item.getItemId() == R.id.restrict) { if (!isLoggedIn) return false; final String action = profileModel.getFriendshipStatus().isRestricted() ? "Unrestrict" : "Restrict"; friendshipService.toggleRestrict( profileModel.getPk(), !profileModel.getFriendshipStatus().isRestricted(), new ServiceCallback() { @Override public void onSuccess(final FriendshipRestrictResponse result) { Log.d(TAG, action + " success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error while performing " + action, t); } }); return true; } if (item.getItemId() == R.id.block) { if (!isLoggedIn) return false; if (profileModel.getFriendshipStatus().isBlocking()) { friendshipService.unblock( profileModel.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { Log.d(TAG, "Unblock success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error unblocking", t); } }); return true; } friendshipService.block( profileModel.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { Log.d(TAG, "Block success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error blocking", t); } }); return true; } if (item.getItemId() == R.id.chaining) { if (!isLoggedIn) return false; final Bundle bundle = new Bundle(); bundle.putString("type", "chaining"); bundle.putLong("targetId", profileModel.getPk()); NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment, bundle); return true; } if (item.getItemId() == R.id.mute_stories) { if (!isLoggedIn) return false; final String action = profileModel.getFriendshipStatus().isMutingReel() ? "Unmute stories" : "Mute stories"; friendshipService.changeMute( profileModel.getFriendshipStatus().isMutingReel(), profileModel.getPk(), true, new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { Log.d(TAG, action + " success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error while performing " + action, t); } }); return true; } if (item.getItemId() == R.id.mute_posts) { if (!isLoggedIn) return false; final String action = profileModel.getFriendshipStatus().isMuting() ? "Unmute stories" : "Mute stories"; friendshipService.changeMute( profileModel.getFriendshipStatus().isMuting(), profileModel.getPk(), false, new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { Log.d(TAG, action + " success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error while performing " + action, t); } }); return true; } return super.onOptionsItemSelected(item); } @Override public void onRefresh() { profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE); profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); fetchProfileDetails(); } @Override public void onDestroy() { super.onDestroy(); if (usernameSettingHandler != null) { usernameSettingHandler.removeCallbacks(usernameSettingRunnable); } if (highlightsViewModel != null) { highlightsViewModel.getList().postValue(Collections.emptyList()); } } @Override public void onDestroyView() { super.onDestroyView(); if (profileDetailsBinding != null) { fragmentActivity.removeCollapsingView(profileDetailsBinding.getRoot()); } } @Override public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; final Context context = getContext(); if (context == null) return; if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { if (downloadFeedModel == null) return; DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition); downloadFeedModel = null; downloadChildPosition = -1; return; } if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) { DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels)); binding.postsRecyclerView.endSelection(); } } private void init() { disableDm = !Utils.isNavRootInCurrentTabs("direct_messages_nav_graph"); if (getArguments() != null) { final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments()); username = fragmentArgs.getUsername(); setUsernameDelayed(); } if (TextUtils.isEmpty(username) && !isLoggedIn) { profileDetailsBinding.infoContainer.setVisibility(View.GONE); binding.swipeRefreshLayout.setEnabled(false); binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24); binding.privatePage2.setText(R.string.no_acc); binding.privatePage.setVisibility(View.VISIBLE); return; } binding.swipeRefreshLayout.setEnabled(true); setupHighlights(); setupCommonListeners(); fetchProfileDetails(); } private void fetchProfileDetails() { accountIsUpdated = false; String usernameTemp = username.trim(); if (usernameTemp.startsWith("@")) { usernameTemp = usernameTemp.substring(1); } if (TextUtils.isEmpty(usernameTemp)) { appStateViewModel.getCurrentUserLiveData().observe(getViewLifecycleOwner(), user -> { if (user == null) return; profileModel = user; username = profileModel.getUsername(); setUsernameDelayed(); setProfileDetails(); }); return; } if (isLoggedIn) { userService.getUsernameInfo(usernameTemp, new ServiceCallback() { @Override public void onSuccess(final User user) { userService.getUserFriendship(user.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipStatus status) { user.setFriendshipStatus(status); profileModel = user; setProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error fetching profile relationship", t); final Context context = getContext(); try { if (t == null) Toast.makeText(context, R.string.error_loading_profile_loggedin, Toast.LENGTH_LONG).show(); else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); } catch (final Throwable ignored) {} } }); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error fetching profile", t); final Context context = getContext(); try { if (t == null) Toast.makeText(context, R.string.error_loading_profile_loggedin, Toast.LENGTH_LONG).show(); else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); } catch (final Throwable ignored) {} } }); return; } graphQLService.fetchUser(usernameTemp, new ServiceCallback() { @Override public void onSuccess(final User user) { profileModel = user; setProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error fetching profile", t); final Context context = getContext(); try { if (t == null) Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_LONG).show(); else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); } catch (final Throwable ignored) {} } }); } private void setProfileDetails() { final Context context = getContext(); if (context == null) return; if (profileModel == null) { binding.swipeRefreshLayout.setRefreshing(false); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); return; } if (!postsSetupDone) { setupPosts(); } else { binding.postsRecyclerView.refresh(); } profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE); final long profileId = profileModel.getPk(); if (isLoggedIn) { fetchStoryAndHighlights(profileId); } setupButtons(profileId); 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.favorite_short); favoriteRepository.insertOrUpdateFavorite(new Favorite( result.getId(), profileModel.getUsername(), FavoriteType.USER, profileModel.getFullName(), profileModel.getProfilePicUrl(), result.getDateAdded() ), new RepositoryCallback() { @Override public void onSuccess(final Void result) {} @Override public void onDataNotAvailable() {} }); } @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() { favoriteRepository.insertOrUpdateFavorite(new Favorite( 0, profileModel.getUsername(), FavoriteType.USER, profileModel.getFullName(), profileModel.getProfilePicUrl(), new Date() ), new RepositoryCallback() { @Override public void onSuccess(final Void result) { profileDetailsBinding.favChip.setText(R.string.favorite_short); profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); showSnackbar(getString(R.string.added_to_favs)); } @Override public void onDataNotAvailable() {} }); } })); profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl()); profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE); profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE); final long followersCount = profileModel.getFollowerCount(); final long followingCount = profileModel.getFollowingCount(); final String postCount = String.valueOf(profileModel.getMediaCount()); SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count, profileModel.getMediaCount() > 2000000000L ? 2000000000 : (int) profileModel.getMediaCount(), 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(getResources().getQuantityString(R.plurals.main_posts_followers, followersCount > 2000000000L ? 2000000000 : (int) followersCount, 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(); span = new SpannableStringBuilder(getString(R.string.main_posts_following, followingCountStr)); 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.getFullName()) ? profileModel.getUsername() : profileModel.getFullName()); final String biography = profileModel.getBiography(); if (TextUtils.isEmpty(biography)) { profileDetailsBinding.mainBiography.setVisibility(View.GONE); } else { profileDetailsBinding.mainBiography.setVisibility(View.VISIBLE); profileDetailsBinding.mainBiography.setText(biography); profileDetailsBinding.mainBiography.addOnHashtagListener(autoLinkItem -> { final NavController navController = NavHostFragment.findNavController(this); final Bundle bundle = new Bundle(); final String originalText = autoLinkItem.getOriginalText().trim(); bundle.putString(ARG_HASHTAG, originalText); navController.navigate(R.id.action_global_hashTagFragment, bundle); }); profileDetailsBinding.mainBiography.addOnMentionClickListener(autoLinkItem -> { final String originalText = autoLinkItem.getOriginalText().trim(); navigateToProfile(originalText); }); profileDetailsBinding.mainBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(), autoLinkItem.getOriginalText() .trim())); profileDetailsBinding.mainBiography .addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); profileDetailsBinding.mainBiography.setOnLongClickListener(v -> { String[] commentDialogList; if (!TextUtils.isEmpty(cookie)) { commentDialogList = new String[]{ getResources().getString(R.string.bio_copy), getResources().getString(R.string.bio_translate) }; } else { commentDialogList = new String[]{ getResources().getString(R.string.bio_copy) }; } new AlertDialog.Builder(context) .setItems(commentDialogList, (d, w) -> { switch (w) { case 0: Utils.copyText(context, biography); break; case 1: mediaService.translate(String.valueOf(profileModel.getPk()), "3", new ServiceCallback() { @Override public void onSuccess(final String result) { if (TextUtils.isEmpty(result)) { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } new AlertDialog.Builder(context) .setTitle(profileModel.getUsername()) .setMessage(result) .setPositiveButton(R.string.ok, null) .show(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error translating bio", t); Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); } }); break; } }) .setNegativeButton(R.string.cancel, null) .show(); return true; }); } String profileContext = profileModel.getProfileContext(); if (TextUtils.isEmpty(profileContext)) { profileDetailsBinding.profileContext.setVisibility(View.GONE); } else { profileDetailsBinding.profileContext.setVisibility(View.VISIBLE); final List userProfileContextLinks = profileModel.getProfileContextLinks(); for (int i = 0; i < userProfileContextLinks.size(); i++) { final UserProfileContextLink link = userProfileContextLinks.get(i); if (link.getUsername() != null) profileContext = profileContext.substring(0, link.getStart() + i) + "@" + profileContext.substring(link.getStart() + i); } profileDetailsBinding.profileContext.setText(profileContext); profileDetailsBinding.profileContext.addOnMentionClickListener(autoLinkItem -> { final String originalText = autoLinkItem.getOriginalText().trim(); navigateToProfile(originalText); }); } final String url = profileModel.getExternalUrl(); if (TextUtils.isEmpty(url)) { profileDetailsBinding.mainUrl.setVisibility(View.GONE); } else { profileDetailsBinding.mainUrl.setVisibility(View.VISIBLE); profileDetailsBinding.mainUrl.setText(url); profileDetailsBinding.mainUrl.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); profileDetailsBinding.mainUrl.setOnLongClickListener(v -> { Utils.copyText(context, url); return true; }); } if (!isReallyPrivate()) { if (isLoggedIn) { profileDetailsBinding.mainFollowing.setClickable(true); profileDetailsBinding.mainFollowers.setClickable(true); final View.OnClickListener followClickListener = v -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToFollowViewerFragment( profileId, v == profileDetailsBinding.mainFollowers, profileModel.getUsername()); NavHostFragment.findNavController(this).navigate(action); }; profileDetailsBinding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null); profileDetailsBinding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null); } binding.postsRecyclerView.setVisibility(View.VISIBLE); } else { profileDetailsBinding.mainFollowers.setClickable(false); profileDetailsBinding.mainFollowing.setClickable(false); binding.privatePage1.setImageResource(R.drawable.lock); binding.privatePage2.setText(R.string.priv_acc); binding.privatePage.setVisibility(View.VISIBLE); binding.postsRecyclerView.setVisibility(View.GONE); binding.swipeRefreshLayout.setRefreshing(false); } } private void setupButtons(final long profileId) { profileDetailsBinding.btnTagged.setVisibility(isReallyPrivate() ? View.GONE : View.VISIBLE); if (isLoggedIn) { if (Objects.equals(profileId, myId)) { profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE); profileDetailsBinding.btnSaved.setVisibility(View.VISIBLE); profileDetailsBinding.btnLiked.setVisibility(View.VISIBLE); profileDetailsBinding.btnDM.setVisibility(View.GONE); profileDetailsBinding.btnSaved.setText(R.string.saved); if (!accountIsUpdated) updateAccountInfo(); return; } profileDetailsBinding.btnSaved.setVisibility(View.GONE); profileDetailsBinding.btnLiked.setVisibility(View.GONE); profileDetailsBinding.btnDM.setVisibility(disableDm ? View.GONE : View.VISIBLE); profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE); final Context context = getContext(); if (context == null) return; if (profileModel.getFriendshipStatus().isFollowing() || profileModel.getFriendshipStatus().isFollowedBy()) { profileDetailsBinding.mainStatus.setVisibility(View.VISIBLE); if (!profileModel.getFriendshipStatus().isFollowing()) { profileDetailsBinding.mainStatus.setChipBackgroundColor(AppCompatResources.getColorStateList(context, R.color.blue_800)); profileDetailsBinding.mainStatus.setText(R.string.status_follower); } else if (!profileModel.getFriendshipStatus().isFollowedBy()) { profileDetailsBinding.mainStatus.setChipBackgroundColor(AppCompatResources.getColorStateList(context, R.color.deep_orange_800)); profileDetailsBinding.mainStatus.setText(R.string.status_following); } else { profileDetailsBinding.mainStatus.setChipBackgroundColor(AppCompatResources.getColorStateList(context, R.color.green_800)); profileDetailsBinding.mainStatus.setText(R.string.status_mutual); } } if (profileModel.getFriendshipStatus().isFollowing()) { profileDetailsBinding.btnFollow.setText(R.string.unfollow); profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); } else if (profileModel.getFriendshipStatus().isOutgoingRequest()) { profileDetailsBinding.btnFollow.setText(R.string.cancel); profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); } else { profileDetailsBinding.btnFollow.setText(R.string.follow); profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_24); } if (restrictMenuItem != null) { restrictMenuItem.setVisible(true); restrictMenuItem.setTitle(profileModel.getFriendshipStatus().isRestricted() ? R.string.unrestrict : R.string.restrict); } if (blockMenuItem != null) { blockMenuItem.setVisible(true); blockMenuItem.setTitle(profileModel.getFriendshipStatus().isBlocking() ? R.string.unblock : R.string.block); } if (muteStoriesMenuItem != null) { muteStoriesMenuItem.setVisible(true); muteStoriesMenuItem.setTitle(profileModel.getFriendshipStatus().isMutingReel() ? R.string.unmute_stories : R.string.mute_stories); } if (mutePostsMenuItem != null) { mutePostsMenuItem.setVisible(true); mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts); } if (chainingMenuItem != null) { chainingMenuItem.setVisible(true); } return; } } private void updateAccountInfo() { if (profileModel == null) return; accountRepository.insertOrUpdateAccount( profileModel.getPk(), profileModel.getUsername(), cookie, profileModel.getFullName(), profileModel.getProfilePicUrl(), new RepositoryCallback() { @Override public void onSuccess(final Account result) { accountIsUpdated = true; } @Override public void onDataNotAvailable() { Log.e(TAG, "onDataNotAvailable: insert failed"); } }); } private void fetchStoryAndHighlights(final long profileId) { storiesService.getUserStory( StoryViewerOptions.forUser(profileId, profileModel.getFullName()), new ServiceCallback>() { @Override public void onSuccess(final List storyModels) { if (storyModels != null && !storyModels.isEmpty()) { profileDetailsBinding.mainProfileImage.setStoriesBorder(1); hasStories = true; } } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error", t); } }); storiesService.fetchHighlights(profileId, new ServiceCallback>() { @Override public void onSuccess(final List result) { if (result != null) { profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); highlightsViewModel.getList().postValue(result); } else profileDetailsBinding.highlightsList.setVisibility(View.GONE); } @Override public void onFailure(final Throwable t) { profileDetailsBinding.highlightsList.setVisibility(View.GONE); Log.e(TAG, "Error", t); } }); } private void setupCommonListeners() { final Context context = getContext(); profileDetailsBinding.btnFollow.setOnClickListener(v -> { if (profileModel.getFriendshipStatus().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( profileModel.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { // Log.d(TAG, "Unfollow success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error unfollowing", t); } })) .setNegativeButton(R.string.cancel, null) .show(); } else if (profileModel.getFriendshipStatus().isFollowing() || profileModel.getFriendshipStatus().isOutgoingRequest()) { friendshipService.unfollow( profileModel.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { // Log.d(TAG, "Unfollow success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error unfollowing", t); } }); } else { friendshipService.follow( profileModel.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { // Log.d(TAG, "Follow success: " + result); fetchProfileDetails(); } @Override public void onFailure(final Throwable t) { Log.e(TAG, "Error following", t); } }); } }); profileDetailsBinding.btnSaved.setOnClickListener(v -> { final NavDirections action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false); NavHostFragment.findNavController(this).navigate(action); }); profileDetailsBinding.btnLiked.setOnClickListener(v -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), profileModel.getPk(), PostItemType.LIKED); NavHostFragment.findNavController(this).navigate(action); }); profileDetailsBinding.btnTagged.setOnClickListener(v -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), profileModel.getPk(), PostItemType.TAGGED); NavHostFragment.findNavController(this).navigate(action); }); if (!disableDm) { profileDetailsBinding.btnDM.setOnClickListener(v -> { profileDetailsBinding.btnDM.setEnabled(false); new CreateThreadAction(cookie, profileModel.getPk(), thread -> { if (thread == null) { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); profileDetailsBinding.btnDM.setEnabled(true); return; } final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager(); if (!inboxManager.containsThread(thread.getThreadId())) { thread.setTemp(true); inboxManager.addThread(thread, 0); } fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername()); profileDetailsBinding.btnDM.setEnabled(true); }).execute(); }); } profileDetailsBinding.mainProfileImage.setOnClickListener(v -> { if (!hasStories) { // show profile pic showProfilePicDialog(); return; } // show dialog final String[] options = {getString(R.string.view_pfp), getString(R.string.show_stories)}; final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { if (which == AlertDialog.BUTTON_NEUTRAL) { dialog.dismiss(); return; } if (which == 1) { // show stories final NavDirections action = ProfileFragmentDirections .actionProfileFragmentToStoryViewerFragment(StoryViewerOptions.forUser(profileModel.getPk(), profileModel.getFullName())); NavHostFragment.findNavController(this).navigate(action); return; } showProfilePicDialog(); }; if (context == null) return; new AlertDialog.Builder(context) .setItems(options, profileDialogListener) .setNegativeButton(R.string.cancel, null) .show(); }); } private void showSnackbar(final String message) { final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); snackbar.setAction(R.string.ok, v -> snackbar.dismiss()) .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) .setAnchorView(fragmentActivity.getBottomNavView()) .show(); } private void showProfilePicDialog() { if (profileModel != null) { final FragmentManager fragmentManager = getParentFragmentManager(); final ProfilePicDialogFragment fragment = ProfilePicDialogFragment.getInstance(profileModel.getPk(), username, profileModel.getProfilePicUrl()); final FragmentTransaction ft = fragmentManager.beginTransaction(); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .add(fragment, "profilePicDialog") .commit(); } } private void setUsernameDelayed() { if (usernameSettingHandler == null) { usernameSettingHandler = new Handler(Looper.getMainLooper()); } usernameSettingHandler.postDelayed(usernameSettingRunnable, 200); } private void setupPosts() { binding.postsRecyclerView.setViewModelStoreOwner(this) .setLifeCycleOwner(this) .setPostFetchService(new ProfilePostFetchService(profileModel, isLoggedIn)) .setLayoutPreferences(layoutPreferences) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) .setSelectionModeCallback(selectionModeCallback) .init(); binding.swipeRefreshLayout.setRefreshing(true); postsSetupDone = true; } private void updateSwipeRefreshState() { binding.swipeRefreshLayout.setRefreshing(binding.postsRecyclerView.isFetching()); } private void setupHighlights() { highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); highlightsAdapter = new HighlightsAdapter((model, position) -> { final StoryViewerOptions options = StoryViewerOptions.forHighlight(model.getTitle()); options.setCurrentFeedStoryIndex(position); final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(options); NavHostFragment.findNavController(this).navigate(action); }); final Context context = getContext(); if (context == null) return; final RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false); profileDetailsBinding.highlightsList.setLayoutManager(layoutManager); profileDetailsBinding.highlightsList.setAdapter(highlightsAdapter); highlightsViewModel.getList().observe(getViewLifecycleOwner(), highlightModels -> highlightsAdapter.submitList(highlightModels)); } 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( Constants.PREF_PROFILE_POSTS_LAYOUT, preferences -> { layoutPreferences = preferences; new Handler().postDelayed(() -> binding.postsRecyclerView.setLayoutPreferences(preferences), 200); }); fragment.show(getChildFragmentManager(), "posts_layout_preferences"); } private boolean isReallyPrivate() { if (profileModel.getPk() == myId) return false; final FriendshipStatus friendshipStatus = profileModel.getFriendshipStatus(); return !friendshipStatus.isFollowing() && profileModel.isPrivate(); } }