package app.fedilab.android.activities; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.ColorStateList; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Html; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.UnderlineSpan; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityOptionsCompat; import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.client.entities.Pinned; import app.fedilab.android.client.entities.Timeline; import app.fedilab.android.client.entities.WellKnownNodeinfo; import app.fedilab.android.client.entities.app.PinnedTimeline; import app.fedilab.android.client.entities.app.RemoteInstance; import app.fedilab.android.client.mastodon.entities.Account; import app.fedilab.android.client.mastodon.entities.Attachment; import app.fedilab.android.client.mastodon.entities.Field; import app.fedilab.android.client.mastodon.entities.IdentityProof; import app.fedilab.android.client.mastodon.entities.MastodonList; import app.fedilab.android.client.mastodon.entities.RelationShip; import app.fedilab.android.databinding.ActivityProfileBinding; import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.CrossActionHelper; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.SpannableHelper; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.ui.drawer.IdentityProofsAdapter; import app.fedilab.android.ui.pageadapter.FedilabProfileTLPageAdapter; import app.fedilab.android.viewmodel.mastodon.AccountsVM; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; import app.fedilab.android.viewmodel.mastodon.ReorderVM; import app.fedilab.android.viewmodel.mastodon.TimelinesVM; import es.dmoral.toasty.Toasty; public class ProfileActivity extends BaseActivity { private RelationShip relationship; private Account account; private ScheduledExecutorService scheduledExecutorService; private action doAction; private AccountsVM accountsVM; private RecyclerView identityProofsRecycler; private List identityProofList; private ActivityProfileBinding binding; private String account_id; private String mention_str; private WellKnownNodeinfo.NodeInfo nodeInfo; private final BroadcastReceiver broadcast_data = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle b = intent.getExtras(); if (b != null) { Account accountReceived = (Account) b.getSerializable(Helper.ARG_ACCOUNT); if (b.getBoolean(Helper.RECEIVE_REDRAW_PROFILE, false) && accountReceived != null) { if (account != null && accountReceived.id.equalsIgnoreCase(account.id)) { initializeView(accountReceived); } } } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeHelper.applyTheme(this); binding = ActivityProfileBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); ActionBar actionBar = getSupportActionBar(); Bundle b = getIntent().getExtras(); binding.accountFollow.setEnabled(false); if (b != null) { account = (Account) b.getSerializable(Helper.ARG_ACCOUNT); account_id = b.getString(Helper.ARG_USER_ID, null); mention_str = b.getString(Helper.ARG_MENTION, null); } postponeEnterTransition(); //Remove title if (actionBar != null) { actionBar.setDisplayShowTitleEnabled(false); actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); } if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } binding.toolbar.setPopupTheme(Helper.popupStyle()); accountsVM = new ViewModelProvider(ProfileActivity.this).get(AccountsVM.class); if (account != null) { new Thread(() -> { account = SpannableHelper.convertAccount(ProfileActivity.this, account); Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> initializeView(account); mainHandler.post(myRunnable); }).start(); } else if (account_id != null) { accountsVM.getAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account_id).observe(ProfileActivity.this, fetchedAccount -> { account = fetchedAccount; initializeView(account); }); } else if (mention_str != null) { accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, mention_str, 1, true, false).observe(ProfileActivity.this, accounts -> { if (accounts != null && accounts.size() > 0) { account = accounts.get(0); initializeView(account); } else { Toasty.error(ProfileActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show(); finish(); } }); } else { Toasty.error(ProfileActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show(); finish(); } LocalBroadcastManager.getInstance(ProfileActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); } private void initializeView(Account account) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ProfileActivity.this); if (account == null) { Toasty.error(ProfileActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show(); finish(); return; } binding.title.setText(String.format(Locale.getDefault(), "@%s", account.acct)); binding.headerEditProfile.setOnClickListener(v -> { Intent intent = new Intent(ProfileActivity.this, EditProfileActivity.class); startActivity(intent); }); // MastodonHelper.loadPPMastodon(binding.profilePicture, account); binding.appBar.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { if (Math.abs(verticalOffset) - binding.appBar.getTotalScrollRange() == 0) { binding.profilePicture.setVisibility(View.VISIBLE); binding.title.setVisibility(View.VISIBLE); } else { binding.profilePicture.setVisibility(View.GONE); binding.title.setVisibility(View.GONE); } }); //Retrieve relationship with the connected account List accountListToCheck = new ArrayList<>(); accountListToCheck.add(account.id); //Retrieve relation ship accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, relationShips -> { if (relationShips != null && relationShips.size() > 0) { this.relationship = relationShips.get(0); updateAccount(); } }); //Retrieve identity proofs accountsVM.getIdentityProofs(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id).observe(ProfileActivity.this, identityProofs -> { this.identityProofList = identityProofs; updateAccount(); }); //Animate emojis if (account.emojis != null && account.emojis.size() > 0) { boolean disableAnimatedEmoji = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); if (!disableAnimatedEmoji) { scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); scheduledExecutorService.scheduleAtFixedRate(() -> binding.accountDn.invalidate(), 0, 130, TimeUnit.MILLISECONDS); } } binding.accountTabLayout.clearOnTabSelectedListeners(); binding.accountTabLayout.removeAllTabs(); binding.accountViewpager.clearOnPageChangeListeners(); //Tablayout for timelines/following/followers FedilabProfileTLPageAdapter fedilabProfileTLPageAdapter = new FedilabProfileTLPageAdapter(getSupportFragmentManager(), account); binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.status_cnt, Helper.withSuffix(account.statuses_count)))); binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.following_cnt, Helper.withSuffix(account.following_count)))); binding.accountTabLayout.addTab(binding.accountTabLayout.newTab().setText(getString(R.string.followers_cnt, Helper.withSuffix(account.followers_count)))); binding.accountViewpager.setAdapter(fedilabProfileTLPageAdapter); binding.accountViewpager.setOffscreenPageLimit(3); binding.accountViewpager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(binding.accountTabLayout)); binding.accountTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { binding.accountViewpager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); binding.accountTabLayout.setTabTextColors(ThemeHelper.getAttColor(ProfileActivity.this, R.attr.mTextColor), ContextCompat.getColor(ProfileActivity.this, R.color.cyanea_accent_dark_reference)); binding.accountTabLayout.setTabIconTint(ThemeHelper.getColorStateList(ProfileActivity.this)); boolean disableGif = sharedpreferences.getBoolean(getString(R.string.SET_DISABLE_GIF), false); String targetedUrl = disableGif ? account.avatar_static : account.avatar; Glide.with(ProfileActivity.this) .asDrawable() .dontTransform() .load(targetedUrl).into( new CustomTarget() { @Override public void onResourceReady(@NonNull final Drawable resource, Transition transition) { binding.profilePicture.setImageDrawable(resource); startPostponedEnterTransition(); } @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { binding.profilePicture.setImageResource(R.drawable.ic_person); startPostponedEnterTransition(); } @Override public void onLoadCleared(@Nullable Drawable placeholder) { } } ); //Load header MastodonHelper.loadProfileMediaMastodon(binding.bannerPp, account, MastodonHelper.MediaAccountType.HEADER); //Redraws icon for locked accounts final float scale = getResources().getDisplayMetrics().density; if (account.locked) { Drawable img = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_lock_24); assert img != null; img.setBounds(0, 0, (int) (16 * scale + 0.5f), (int) (16 * scale + 0.5f)); binding.accountUn.setCompoundDrawables(null, null, img, null); } else { binding.accountUn.setCompoundDrawables(null, null, null, null); } //Peertube account watched by a Mastodon account //Bot account if (account.bot) { binding.accountBot.setVisibility(View.VISIBLE); } if (account.acct != null) { setTitle(account.acct); } final SpannableString content = new SpannableString(getString(R.string.disclaimer_full)); content.setSpan(new UnderlineSpan(), 0, content.length(), 0); content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(ProfileActivity.this, R.color.cyanea_accent_reference)), 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); binding.warningMessage.setText(content); binding.warningMessage.setOnClickListener(view -> { if (!account.url.toLowerCase().startsWith("http://") && !account.url.toLowerCase().startsWith("https://")) account.url = "http://" + account.url; Helper.openBrowser(ProfileActivity.this, account.url); }); //Timed muted account if (account.mute_expires_at != null && account.mute_expires_at.after(new Date())) { binding.tempMute.setVisibility(View.VISIBLE); SpannableString content_temp_mute = new SpannableString(getString(R.string.timed_mute_profile, account.acct, account.mute_expires_at)); content_temp_mute.setSpan(new UnderlineSpan(), 0, content_temp_mute.length(), 0); binding.tempMute.setText(content_temp_mute); } //This account was moved to another one if (account.moved != null) { binding.accountMoved.setVisibility(View.VISIBLE); Drawable imgTravel = ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_card_travel_24); assert imgTravel != null; imgTravel.setBounds(0, 0, (int) (20 * scale + 0.5f), (int) (20 * scale + 0.5f)); binding.accountMoved.setCompoundDrawables(imgTravel, null, null, null); //Retrieves content and make account names clickable SpannableString spannableString = SpannableHelper.moveToText(ProfileActivity.this, account); binding.accountMoved.setText(spannableString, TextView.BufferType.SPANNABLE); binding.accountMoved.setMovementMethod(LinkMovementMethod.getInstance()); } if (account.acct.contains("@")) binding.warningMessage.setVisibility(View.VISIBLE); else binding.warningMessage.setVisibility(View.GONE); //Fields for profile List fields = account.fields; if (fields != null && fields.size() > 0) { for (int i = 0; i < fields.size(); i++) { LinearLayout field; TextView labelView; TextView valueView; switch (i) { case 1: field = binding.field1; labelView = binding.label1; valueView = binding.value1; break; case 2: field = binding.field2; labelView = binding.label2; valueView = binding.value2; break; case 3: field = binding.field3; labelView = binding.label3; valueView = binding.value3; break; default: field = binding.field4; labelView = binding.label4; valueView = binding.value4; break; } field.setVisibility(View.VISIBLE); if (fields.get(i).verified_at != null) { valueView.setCompoundDrawablesWithIntrinsicBounds(null, null, ContextCompat.getDrawable(ProfileActivity.this, R.drawable.ic_baseline_verified_24), null); fields.get(i).value_span.setSpan(new ForegroundColorSpan(ContextCompat.getColor(ProfileActivity.this, R.color.verified_text)), 0, fields.get(i).value_span.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } valueView.setText(fields.get(i).value_span != null ? fields.get(i).value_span : fields.get(i).value, TextView.BufferType.SPANNABLE); valueView.setMovementMethod(LinkMovementMethod.getInstance()); labelView.setText(fields.get(i).name); } binding.fieldsContainer.setVisibility(View.VISIBLE); } binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE); binding.accountUn.setText(String.format("@%s", account.acct)); binding.accountUn.setOnLongClickListener(v -> { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); String account_id = account.acct; if (account_id.split("@").length == 1) account_id += "@" + BaseMainActivity.currentInstance; ClipData clip = ClipData.newPlainText("mastodon_account_id", "@" + account_id); Toasty.info(ProfileActivity.this, getString(R.string.account_id_clipbloard), Toast.LENGTH_SHORT).show(); assert clipboard != null; clipboard.setPrimaryClip(clip); return false; }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) binding.accountNote.setText(account.span_note != null ? account.span_note : new SpannableString(Html.fromHtml(account.note, Html.FROM_HTML_MODE_COMPACT)), TextView.BufferType.SPANNABLE); else binding.accountNote.setText(account.span_note != null ? account.span_note : new SpannableString(Html.fromHtml(account.note)), TextView.BufferType.SPANNABLE); binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance()); MastodonHelper.loadPPMastodon(binding.accountPp, account); binding.accountPp.setOnClickListener(v -> { Intent intent = new Intent(ProfileActivity.this, MediaActivity.class); Bundle b = new Bundle(); Attachment attachment = new Attachment(); attachment.description = account.acct; attachment.preview_url = account.avatar; attachment.url = account.avatar; attachment.remote_url = account.avatar; attachment.type = "image"; ArrayList attachments = new ArrayList<>(); attachments.add(attachment); b.putSerializable(Helper.ARG_MEDIA_ARRAY, attachments); b.putInt(Helper.ARG_MEDIA_POSITION, 1); intent.putExtras(b); ActivityOptionsCompat options = ActivityOptionsCompat .makeSceneTransitionAnimation(ProfileActivity.this, binding.accountPp, attachment.url); // start the new activity startActivity(intent, options.toBundle()); }); binding.accountNotification.setOnClickListener(v -> { if (relationship != null && relationship.followed_by) { relationship.notifying = !relationship.notifying; accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, relationship.showing_reblogs, relationship.notifying) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); } }); binding.accountFollow.setOnClickListener(v -> { if (doAction == action.NOTHING) { Toasty.info(ProfileActivity.this, getString(R.string.nothing_to_do), Toast.LENGTH_LONG).show(); } else if (doAction == action.FOLLOW) { binding.accountFollow.setEnabled(false); accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); } else if (doAction == action.UNFOLLOW) { boolean confirm_unfollow = sharedpreferences.getBoolean(getString(R.string.SET_UNFOLLOW_VALIDATION), true); if (confirm_unfollow) { AlertDialog.Builder unfollowConfirm = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); unfollowConfirm.setTitle(getString(R.string.unfollow_confirm)); unfollowConfirm.setMessage(account.acct); unfollowConfirm.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); unfollowConfirm.setPositiveButton(R.string.yes, (dialog, which) -> { binding.accountFollow.setEnabled(false); accountsVM.unfollow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); dialog.dismiss(); }); unfollowConfirm.show(); } else { binding.accountFollow.setEnabled(false); binding.accountFollow.setEnabled(false); accountsVM.unfollow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); } } else if (doAction == action.UNBLOCK) { binding.accountFollow.setEnabled(false); accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); } }); binding.accountNotification.setOnLongClickListener(v -> { CrossActionHelper.doCrossAction(ProfileActivity.this, CrossActionHelper.TypeOfCrossAction.FOLLOW_ACTION, account, null); return false; }); binding.accountDate.setText(Helper.shortDateToString(account.created_at)); binding.accountDate.setVisibility(View.VISIBLE); String[] accountInstanceArray = account.acct.split("@"); String accountInstance = BaseMainActivity.currentInstance; if (accountInstanceArray.length > 1) { accountInstance = accountInstanceArray[1]; } NodeInfoVM nodeInfoVM = new ViewModelProvider(ProfileActivity.this).get(NodeInfoVM.class); String finalAccountInstance = accountInstance; nodeInfoVM.getNodeInfo(accountInstance).observe(ProfileActivity.this, nodeInfo -> { this.nodeInfo = nodeInfo; if (nodeInfo != null && nodeInfo.software != null) { binding.instanceInfo.setText(nodeInfo.software.name); binding.instanceInfo.setVisibility(View.VISIBLE); binding.instanceInfo.setOnClickListener(v -> { Intent intent = new Intent(ProfileActivity.this, InstanceProfileActivity.class); Bundle b = new Bundle(); b.putString(Helper.ARG_INSTANCE, finalAccountInstance); intent.putExtras(b); startActivity(intent); }); } }); } /*** * This methode is called to update the view once an action has been performed */ private void updateAccount() { //The value for account is from same server so id can be used if (account.id.equals(BaseMainActivity.accountWeakReference.get().user_id)) { binding.accountFollow.setVisibility(View.GONE); binding.headerEditProfile.setVisibility(View.VISIBLE); binding.headerEditProfile.bringToFront(); } //Manage indentity proofs if not yet displayed if (identityProofList != null && identityProofList.size() > 0) { ImageView identity_proofs_indicator = findViewById(R.id.identity_proofs_indicator); identity_proofs_indicator.setVisibility(View.VISIBLE); //Recyclerview for identity proof has not been inflated yet if (identityProofsRecycler == null) { identity_proofs_indicator.setOnClickListener(v -> { AlertDialog.Builder builder = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); identityProofsRecycler = new RecyclerView(ProfileActivity.this); LinearLayoutManager mLayoutManager = new LinearLayoutManager(ProfileActivity.this); identityProofsRecycler.setLayoutManager(mLayoutManager); IdentityProofsAdapter identityProofsAdapter = new IdentityProofsAdapter(identityProofList); identityProofsRecycler.setAdapter(identityProofsAdapter); builder.setView(identityProofsRecycler); builder .setTitle(R.string.identity_proofs) .setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()) .show(); }); } } int[][] states = new int[][]{ new int[]{android.R.attr.state_enabled}, // enabled new int[]{-android.R.attr.state_enabled}, // disabled new int[]{-android.R.attr.state_checked}, // unchecked new int[]{android.R.attr.state_pressed} // pressed }; int[] colors = new int[]{ ContextCompat.getColor(ProfileActivity.this, R.color.mastodonC4), ContextCompat.getColor(ProfileActivity.this, R.color.mastodonC4___), ContextCompat.getColor(ProfileActivity.this, R.color.mastodonC4), ContextCompat.getColor(ProfileActivity.this, R.color.mastodonC4) }; binding.accountFollow.setBackgroundTintList(new ColorStateList(states, colors)); binding.accountFollow.setEnabled(true); //Visibility depending of the relationship if (relationship != null) { if (relationship.blocked_by) { binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_add_24); binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setEnabled(false); binding.accountFollow.setContentDescription(getString(R.string.action_disabled)); } if (relationship.requested) { binding.accountFollowRequest.setVisibility(View.VISIBLE); binding.accountFollow.setImageResource(R.drawable.ic_baseline_hourglass_full_24); binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setContentDescription(getString(R.string.follow_request)); doAction = action.UNFOLLOW; } if (relationship.following) { binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_remove_24); binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(ProfileActivity.this, R.color.red_1))); doAction = action.UNFOLLOW; binding.accountFollow.setContentDescription(getString(R.string.action_unfollow)); binding.accountFollow.setVisibility(View.VISIBLE); } else if (relationship.blocking) { binding.accountFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(ProfileActivity.this, R.color.red_1))); binding.accountFollow.setImageResource(R.drawable.ic_baseline_lock_open_24); doAction = action.UNBLOCK; binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setContentDescription(getString(R.string.action_unblock)); } else { binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_add_24); doAction = action.FOLLOW; binding.accountFollow.setVisibility(View.VISIBLE); binding.accountFollow.setContentDescription(getString(R.string.action_follow)); } if (!relationship.following) { binding.accountNotification.setVisibility(View.GONE); } else { binding.accountNotification.setVisibility(View.VISIBLE); } if (relationship.notifying) { binding.accountNotification.setImageResource(R.drawable.ic_baseline_notifications_active_24); } else { binding.accountNotification.setImageResource(R.drawable.ic_baseline_notifications_off_24); } //Account note if (relationship.note == null || relationship.note.trim().isEmpty()) { binding.personalNote.setText(R.string.note_for_account); } else { binding.personalNote.setText(relationship.note); } binding.personalNote.setOnClickListener(view -> { AlertDialog.Builder builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderInner.setTitle(R.string.note_for_account); EditText input = new EditText(ProfileActivity.this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); input.setLayoutParams(lp); input.setSingleLine(false); input.setText(relationship.note); input.setImeOptions(EditorInfo.IME_FLAG_NO_ENTER_ACTION); builderInner.setView(input); builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderInner.setPositiveButton(R.string.validate, (dialog, which) -> { String notes = input.getText().toString().trim(); binding.accountNote.setText(notes); accountsVM.updateNote(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, notes); dialog.dismiss(); }); builderInner.show(); }); } invalidateOptionsMenu(); } @Override public boolean onCreateOptionsMenu(@NonNull Menu menu) { getMenuInflater().inflate(R.menu.activity_profile, menu); if (account != null) { final boolean isOwner = account.id != null && account.id.compareToIgnoreCase(BaseMainActivity.currentUserID) == 0; String[] splitAcct = account.acct.split("@"); //check if user is from the same instance if (splitAcct.length <= 1) { //If yes, these entries must be hidden menu.findItem(R.id.action_follow_instance).setVisible(false); menu.findItem(R.id.action_block_instance).setVisible(false); } if (isOwner) { menu.findItem(R.id.action_block).setVisible(false); menu.findItem(R.id.action_report).setVisible(false); menu.findItem(R.id.action_mute).setVisible(false); menu.findItem(R.id.action_mention).setVisible(false); menu.findItem(R.id.action_follow_instance).setVisible(false); menu.findItem(R.id.action_block_instance).setVisible(false); menu.findItem(R.id.action_hide_boost).setVisible(false); menu.findItem(R.id.action_endorse).setVisible(false); menu.findItem(R.id.action_direct_message).setVisible(false); menu.findItem(R.id.action_add_to_list).setVisible(false); } else { menu.findItem(R.id.action_block).setVisible(true); menu.findItem(R.id.action_mute).setVisible(true); menu.findItem(R.id.action_mention).setVisible(true); } //Update menu title depending of relationship if (relationship != null) { if (!relationship.following) { menu.findItem(R.id.action_hide_boost).setVisible(false); menu.findItem(R.id.action_endorse).setVisible(false); } if (relationship.blocking) { menu.findItem(R.id.action_block).setTitle(R.string.action_unblock); } if (relationship.muting) { menu.findItem(R.id.action_mute).setTitle(R.string.action_unmute); } if (relationship.endorsed) { menu.findItem(R.id.action_endorse).setTitle(R.string.unendorse); } else { menu.findItem(R.id.action_endorse).setTitle(R.string.endorse); } if (relationship.showing_reblogs) { menu.findItem(R.id.action_hide_boost).setTitle(getString(R.string.hide_boost, account.username)); } else { menu.findItem(R.id.action_hide_boost).setTitle(getString(R.string.show_boost, account.username)); } } } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); String[] splitAcct = account.acct.split("@"); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ProfileActivity.this); AlertDialog.Builder builderInner; final boolean isOwner = account.id.compareToIgnoreCase(BaseMainActivity.currentUserID) == 0; final String[] stringArrayConf; if (isOwner) { stringArrayConf = getResources().getStringArray(R.array.more_action_owner_confirm); } else { stringArrayConf = getResources().getStringArray(R.array.more_action_confirm); } action doActionAccount; if (itemId == android.R.id.home) { finish(); return true; } else if (itemId == R.id.action_follow_instance) { String finalInstanceName = splitAcct[1]; ReorderVM reorderVM = new ViewModelProvider(ProfileActivity.this).get(ReorderVM.class); //Get pinned instances reorderVM.getPinned().observe(ProfileActivity.this, pinned -> { boolean alreadyPinned = false; for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) { if (pinnedTimeline.remoteInstance != null && pinnedTimeline.remoteInstance.host.compareToIgnoreCase(finalInstanceName) == 0) { alreadyPinned = true; break; } } if (alreadyPinned) { Toasty.info(ProfileActivity.this, getString(R.string.toast_instance_already_added), Toast.LENGTH_LONG).show(); return; } if (nodeInfo != null) { RemoteInstance.InstanceType instanceType; if (nodeInfo.software.name.compareToIgnoreCase("peertube") == 0) { instanceType = RemoteInstance.InstanceType.PEERTUBE; } else if (nodeInfo.software.name.compareToIgnoreCase("pixelfed") == 0) { instanceType = RemoteInstance.InstanceType.PIXELFED; } else if (nodeInfo.software.name.compareToIgnoreCase("misskey") == 0) { instanceType = RemoteInstance.InstanceType.MISSKEY; } else if (nodeInfo.software.name.compareToIgnoreCase("gnu") == 0) { instanceType = RemoteInstance.InstanceType.GNU; } else { instanceType = RemoteInstance.InstanceType.MASTODON; } RemoteInstance remoteInstance = new RemoteInstance(); remoteInstance.type = instanceType; remoteInstance.host = finalInstanceName; PinnedTimeline pinnedTimeline = new PinnedTimeline(); pinnedTimeline.remoteInstance = remoteInstance; pinnedTimeline.displayed = true; pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.position = pinned.pinnedTimelines.size(); pinned.pinnedTimelines.add(pinnedTimeline); new Thread(() -> { try { new Pinned(ProfileActivity.this).updatePinned(pinned); runOnUiThread(() -> { Bundle b = new Bundle(); b.putBoolean(Helper.RECEIVE_REDRAW_TOPBAR, true); Intent intentBD = new Intent(Helper.BROADCAST_DATA); intentBD.putExtras(b); LocalBroadcastManager.getInstance(ProfileActivity.this).sendBroadcast(intentBD); }); } catch (DBException e) { e.printStackTrace(); } }).start(); } }); return true; } else if (itemId == R.id.action_filter) { AlertDialog.Builder filterTagDialog = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); Set featuredTagsSet = sharedpreferences.getStringSet(getString(R.string.SET_FEATURED_TAGS), null); List tags = new ArrayList<>(); if (featuredTagsSet != null) { tags = new ArrayList<>(featuredTagsSet); } tags.add(0, getString(R.string.no_tags)); String[] tagsString = tags.toArray(new String[0]); List finalTags = tags; String tag = sharedpreferences.getString(getString(R.string.SET_FEATURED_TAG_ACTION), null); int checkedposition = 0; int i = 0; for (String _t : tags) { if (_t.equals(tag)) checkedposition = i; i++; } filterTagDialog.setSingleChoiceItems(tagsString, checkedposition, (dialog, item1) -> { String tag1; if (item1 == 0) { tag1 = null; } else { tag1 = finalTags.get(item1); } SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(getString(R.string.SET_FEATURED_TAG_ACTION), tag1); editor.apply(); dialog.dismiss(); }); filterTagDialog.show(); return true; } else if (itemId == R.id.action_endorse) { if (relationship != null) if (relationship.endorsed) { accountsVM.endorse(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, relationShip -> this.relationship = relationShip); } else { accountsVM.unendorse(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, relationShip -> this.relationship = relationShip); } return true; } else if (itemId == R.id.action_hide_boost) { if (relationship != null) if (relationship.showing_reblogs) { accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, false, relationship.notifying) .observe(ProfileActivity.this, relationShip -> this.relationship = relationShip); } else { accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, relationship.notifying) .observe(ProfileActivity.this, relationShip -> this.relationship = relationShip); } return true; } else if (itemId == R.id.action_direct_message) { Intent intent = new Intent(ProfileActivity.this, ComposeActivity.class); Bundle b = new Bundle(); b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account); b.putString(Helper.ARG_VISIBILITY, "direct"); intent.putExtras(b); startActivity(intent); return true; } else if (itemId == R.id.action_add_to_list) { TimelinesVM timelinesVM = new ViewModelProvider(ProfileActivity.this).get(TimelinesVM.class); timelinesVM.getLists(BaseMainActivity.currentInstance, BaseMainActivity.currentToken) .observe(ProfileActivity.this, mastodonLists -> { if (mastodonLists == null || mastodonLists.size() == 0) { Toasty.info(ProfileActivity.this, getString(R.string.action_lists_empty), Toast.LENGTH_SHORT).show(); return; } accountsVM.getListContainingAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id) .observe(ProfileActivity.this, mastodonListUserIs -> { AlertDialog.Builder builderSingle = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderSingle.setTitle(getString(R.string.action_lists_add_to)); builderSingle.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()); String[] listsId = new String[mastodonLists.size()]; String[] listsArray = new String[mastodonLists.size()]; boolean[] presentArray = new boolean[mastodonLists.size()]; int i = 0; List userIds = new ArrayList<>(); userIds.add(account.id); for (MastodonList mastodonList : mastodonLists) { listsArray[i] = mastodonList.title; presentArray[i] = false; listsId[i] = mastodonList.id; for (MastodonList mastodonListPresent : mastodonListUserIs) { if (mastodonList.id.equalsIgnoreCase(mastodonListPresent.id)) { presentArray[i] = true; break; } } i++; } builderSingle.setMultiChoiceItems(listsArray, presentArray, (dialog, which, isChecked) -> { if (!relationship.following) { accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false) .observe(ProfileActivity.this, newRelationShip -> { relationship = newRelationShip; updateAccount(); if (isChecked) { timelinesVM.addAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); } else { timelinesVM.deleteAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); } }); } else { if (isChecked) { timelinesVM.addAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); } else { timelinesVM.deleteAccountsList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, listsId[which], userIds); } } }); builderSingle.show(); }); }); return true; } else if (itemId == R.id.action_open_browser) { if (account.url != null) { if (!account.url.toLowerCase().startsWith("http://") && !account.url.toLowerCase().startsWith("https://")) account.url = "http://" + account.url; Helper.openBrowser(ProfileActivity.this, account.url); } return true; } else if (itemId == R.id.action_mention) { Intent intent; Bundle b; intent = new Intent(ProfileActivity.this, ComposeActivity.class); b = new Bundle(); b.putSerializable(Helper.ARG_ACCOUNT_MENTION, account); intent.putExtras(b); startActivity(intent); return true; } else if (itemId == R.id.action_mute) { if (relationship.muting) { builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderInner.setTitle(stringArrayConf[4]); doActionAccount = action.UNMUTE; } else { builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderInner.setTitle(stringArrayConf[0]); doActionAccount = action.MUTE; } } else if (itemId == R.id.action_report) { builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderInner.setTitle(R.string.report_account); //Text for report EditText input = new EditText(ProfileActivity.this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); input.setLayoutParams(lp); builderInner.setView(input); builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { String comment = null; if (input.getText() != null) comment = input.getText().toString(); accountsVM.report(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, null, null, null, comment, false); dialog.dismiss(); }); builderInner.show(); return true; } else if (itemId == R.id.action_block) { builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); if (relationship.blocking) { builderInner.setTitle(stringArrayConf[5]); doActionAccount = action.UNBLOCK; } else { builderInner.setTitle(stringArrayConf[1]); doActionAccount = action.BLOCK; } } else if (itemId == R.id.action_block_instance) { builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); doActionAccount = action.BLOCK_DOMAIN; String domain = account.acct.split("@")[1]; builderInner.setMessage(getString(R.string.block_domain_confirm_message, domain)); } else { return true; } builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { String target; if (item.getItemId() == R.id.action_block_instance) { target = account.acct.split("@")[1]; } else { target = account.id; } switch (doActionAccount) { case MUTE: accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target, true, 0) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); break; case UNMUTE: accountsVM.unmute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); break; case BLOCK: accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); break; case UNBLOCK: accountsVM.unblock(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) .observe(ProfileActivity.this, relationShip -> { this.relationship = relationShip; updateAccount(); }); break; case BLOCK_DOMAIN: accountsVM.addDomainBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target); break; } dialog.dismiss(); }); builderInner.show(); return true; } @Override public void onDestroy() { if (scheduledExecutorService != null) { scheduledExecutorService.shutdownNow(); scheduledExecutorService = null; } LocalBroadcastManager.getInstance(ProfileActivity.this).unregisterReceiver(broadcast_data); super.onDestroy(); } public enum action { FOLLOW, UNFOLLOW, BLOCK, UNBLOCK, NOTHING, MUTE, UNMUTE, REPORT, BLOCK_DOMAIN } }