From d514c7b8d66cbb0b4b0ed077aa82817ef0d4de76 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 5 Jun 2025 17:34:51 +0200 Subject: [PATCH] Featured tags displayed in profiles --- .../mastodon/activities/ProfileActivity.java | 42 +++++++++++++++++++ .../mastodon/activities/TimelineActivity.java | 9 ++++ .../endpoints/MastodonAccountsService.java | 1 + .../android/mastodon/helper/Helper.java | 1 + .../fragment/media/FragmentMediaProfile.java | 6 +-- .../timeline/FragmentMastodonTimeline.java | 14 ++++--- .../viewmodel/mastodon/AccountsVM.java | 3 +- .../mastodon/layout/activity_profile.xml | 23 +++++++++- 8 files changed, 88 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/mastodon/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/mastodon/activities/ProfileActivity.java index 1588c20c..04480b16 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/mastodon/activities/ProfileActivity.java @@ -40,6 +40,7 @@ import android.text.method.LinkMovementMethod; import android.text.style.ForegroundColorSpan; import android.text.style.UnderlineSpan; import android.util.TypedValue; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -69,6 +70,7 @@ 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.chip.Chip; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.tabs.TabLayout; @@ -96,6 +98,7 @@ import app.fedilab.android.databinding.TabProfileCustomViewBinding; import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Attachment; import app.fedilab.android.mastodon.client.entities.api.FamiliarFollowers; +import app.fedilab.android.mastodon.client.entities.api.FeaturedTag; import app.fedilab.android.mastodon.client.entities.api.Field; import app.fedilab.android.mastodon.client.entities.api.IdentityProof; import app.fedilab.android.mastodon.client.entities.api.MastodonList; @@ -116,6 +119,7 @@ import app.fedilab.android.mastodon.helper.SpannableHelper; import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.ui.drawer.FieldAdapter; import app.fedilab.android.mastodon.ui.drawer.IdentityProofsAdapter; +import app.fedilab.android.mastodon.ui.drawer.StatusAdapter; import app.fedilab.android.mastodon.ui.pageadapter.FedilabProfileTLPageAdapter; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM; @@ -229,7 +233,45 @@ public class ProfileActivity extends BaseActivity { //Check if account is homeMuted accountsVM.isMuted(Helper.getCurrentAccount(ProfileActivity.this), account).observe(this, result -> homeMuted = result != null && result); ContextCompat.registerReceiver(ProfileActivity.this, broadcast_data, new IntentFilter(Helper.BROADCAST_DATA), ContextCompat.RECEIVER_NOT_EXPORTED); + //Search for featured tags + accountsVM.getAccountFeaturedTags(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id).observe(this, featuredTags -> { + if(featuredTags != null && !featuredTags.isEmpty()) { + binding.featuredHashtagsContainer.setVisibility(View.VISIBLE); + binding.featuredHashtags.removeAllViews(); + for(FeaturedTag featuredTag: featuredTags) { + if(featuredTag.statuses_count > 0 ) { + Chip chip = new Chip(ProfileActivity.this); + chip.setClickable(true); + chip.setEnsureMinTouchTargetSize(false); + chip.setText(String.format("#%s", featuredTag.name)); + chip.setTextColor(ThemeHelper.getAttColor(ProfileActivity.this, R.attr.colorPrimary)); + chip.setOnClickListener(v -> { + Bundle args = new Bundle(); + args.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_TIMELINE); + if (account != null) { + args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, account.id); + } + args.putString(Helper.ARG_TAGGED, featuredTag.name); + args.putBoolean(Helper.ARG_SHOW_PINNED, false); + args.putBoolean(Helper.ARG_SHOW_REPLIES, false); + args.putBoolean(Helper.ARG_SHOW_REBLOGS, false); + args.putBoolean(Helper.ARG_CHECK_REMOTELY, checkRemotely); + Intent intent = new Intent(ProfileActivity.this, TimelineActivity.class); + new CachedBundle(ProfileActivity.this).insertBundle(args, Helper.getCurrentAccount(ProfileActivity.this), bundleId -> { + Bundle _bundle = new Bundle(); + _bundle.putLong(Helper.ARG_INTENT_ID, bundleId); + intent.putExtras(_bundle); + startActivity(intent); + }); + }); + binding.featuredHashtags.addView(chip); + } + } + } else { + binding.featuredHashtagsContainer.setVisibility(View.GONE); + } + }); } diff --git a/app/src/main/java/app/fedilab/android/mastodon/activities/TimelineActivity.java b/app/src/main/java/app/fedilab/android/mastodon/activities/TimelineActivity.java index d70f17ac..f5808e5c 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/activities/TimelineActivity.java +++ b/app/src/main/java/app/fedilab/android/mastodon/activities/TimelineActivity.java @@ -57,21 +57,30 @@ public class TimelineActivity extends BaseBarActivity { Timeline.TimeLineEnum timelineType = null; String lemmy_post_id = null; PinnedTimeline pinnedTimeline = null; + String tagged = null; + String timelineAccountId = null; Status status = null; if (bundle != null) { timelineType = (Timeline.TimeLineEnum) bundle.get(Helper.ARG_TIMELINE_TYPE); lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null); pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); status = (Status) bundle.getSerializable(Helper.ARG_STATUS); + tagged = bundle.getString(Helper.ARG_TAGGED, null); + timelineAccountId = bundle.getString(Helper.ARG_CACHED_ACCOUNT_ID, null); } if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { setTitle(pinnedTimeline.remoteInstance.host); } + if(tagged != null) { + setTitle(String.format("#%s",tagged)); + } FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline(); Bundle args = new Bundle(); args.putSerializable(Helper.ARG_TIMELINE_TYPE, timelineType); args.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline); args.putSerializable(Helper.ARG_LEMMY_POST_ID, lemmy_post_id); + args.putSerializable(Helper.ARG_TAGGED, tagged); + args.putSerializable(Helper.ARG_CACHED_ACCOUNT_ID, timelineAccountId); if (status != null) { args.putSerializable(Helper.ARG_STATUS, status); } diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonAccountsService.java index 35349307..7b33faac 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonAccountsService.java +++ b/app/src/main/java/app/fedilab/android/mastodon/client/endpoints/MastodonAccountsService.java @@ -124,6 +124,7 @@ public interface MastodonAccountsService { @Query("exclude_reblogs") Boolean exclude_reblogs, @Query("only_media") Boolean only_media, @Query("pinned") Boolean pinned, + @Query("tagged") String tagged, @Query("limit") int limit ); diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java index 6beaa52c..77692891 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java @@ -288,6 +288,7 @@ public class Helper { public static final String ARG_SHOW_REBLOGS = "ARG_SHOW_REBLOGS"; public static final String ARG_INITIALIZE_VIEW = "ARG_INITIALIZE_VIEW"; public static final String ARG_SHOW_PINNED = "ARG_SHOW_PINNED"; + public static final String ARG_TAGGED = "ARG_TAGGED"; public static final String ARG_SHOW_MEDIA_ONY = "ARG_SHOW_MEDIA_ONY"; public static final String ARG_MENTION = "ARG_MENTION"; public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY"; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMediaProfile.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMediaProfile.java index e22ecb8a..be57fcca 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMediaProfile.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/media/FragmentMediaProfile.java @@ -128,7 +128,7 @@ public class FragmentMediaProfile extends Fragment { public void federatedAccount(Account account) { if (account != null && isAdded() && !requireActivity().isFinishing()) { accountId = account.id; - accountsVM.getAccountStatuses(tempInstance, null, accountId, null, null, null, null, null, true, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(tempInstance, null, accountId, null, null, null, null, null, true, false, null, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statuses -> initializeStatusesCommonView(statuses)); } else { if (isAdded() && !requireActivity().isFinishing()) { @@ -141,7 +141,7 @@ public class FragmentMediaProfile extends Fragment { tempToken = BaseMainActivity.currentToken; tempInstance = BaseMainActivity.currentInstance; accountId = accountTimeline.id; - accountsVM.getAccountStatuses(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountTimeline.id, null, null, null, null, null, true, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountTimeline.id, null, null, null, null, null, true, false, null, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } } @@ -208,7 +208,7 @@ public class FragmentMediaProfile extends Fragment { if (!flagLoading) { flagLoading = true; binding.loadingNextElements.setVisibility(View.VISIBLE); - accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, max_id, null, null, null, null, true, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, max_id, null, null, null, null, true, false, null, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), newStatuses -> dealWithPagination(newStatuses)); } } else { diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java index d2127dd4..6aa76fe9 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -91,6 +91,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private Integer offset; private StatusAdapter statusAdapter; private Timeline.TimeLineEnum timelineType; + private String tagged; private List timelineStatuses; //Handle actions that can be done in other fragments private final BroadcastReceiver receive_action = new BroadcastReceiver() { @@ -418,6 +419,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null); list_id = bundle.getString(Helper.ARG_LIST_ID, null); search = bundle.getString(Helper.ARG_SEARCH_KEYWORD, null); + tagged = bundle.getString(Helper.ARG_TAGGED, null); searchCache = bundle.getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); canBeFederated = true; @@ -1171,8 +1173,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. public void federatedAccount(Account account) { if (account != null && isAdded() && !requireActivity().isFinishing()) { accountIDInRemoteInstance = account.id; - accountsVM.getAccountStatuses(tempInstance[0], null, accountIDInRemoteInstance, null, null, null, null, null, false, true, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), pinnedStatuses -> accountsVM.getAccountStatuses(tempInstance[0], null, accountIDInRemoteInstance, null, null, null, exclude_replies, exclude_reblogs, media_only, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(tempInstance[0], null, accountIDInRemoteInstance, null, null, null, null, null, false, true, tagged, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), pinnedStatuses -> accountsVM.getAccountStatuses(tempInstance[0], null, accountIDInRemoteInstance, null, null, null, exclude_replies, exclude_reblogs, media_only, false, tagged, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), otherStatuses -> { if (otherStatuses != null && otherStatuses.statuses != null) { if (pinnedStatuses != null && pinnedStatuses.statuses != null) { @@ -1315,8 +1317,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (direction == null && !checkRemotely) { if (show_pinned) { //Fetch pinned statuses to display them at the top - accountsVM.getAccountStatuses(currentInstance, MainActivity.currentToken, accountId, null, null, null, null, null, false, true, MastodonHelper.statusesPerCall(requireActivity())) - .observe(getViewLifecycleOwner(), pinnedStatuses -> accountsVM.getAccountStatuses(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountId, null, null, null, exclude_replies, exclude_reblogs, media_only, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(currentInstance, MainActivity.currentToken, accountId, null, null, null, null, null, false, true, tagged, MastodonHelper.statusesPerCall(requireActivity())) + .observe(getViewLifecycleOwner(), pinnedStatuses -> accountsVM.getAccountStatuses(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountId, null, null, null, exclude_replies, exclude_reblogs, media_only, false, tagged, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), otherStatuses -> { if (otherStatuses != null && otherStatuses.statuses != null && pinnedStatuses != null && pinnedStatuses.statuses != null) { for (Status status : pinnedStatuses.statuses) { @@ -1327,11 +1329,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. } })); } else { - accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, null, null, null, exclude_replies, exclude_reblogs, media_only, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, null, null, null, exclude_replies, exclude_reblogs, media_only, false, tagged, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); } } else if (direction == DIRECTION.BOTTOM) { - accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, max_id, null, null, exclude_replies, exclude_reblogs, media_only, false, MastodonHelper.statusesPerCall(requireActivity())) + accountsVM.getAccountStatuses(tempInstance, tempToken, accountId, max_id, null, null, exclude_replies, exclude_reblogs, media_only, false, tagged, MastodonHelper.statusesPerCall(requireActivity())) .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false, true, fetchStatus)); } else { flagLoading = false; diff --git a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/AccountsVM.java index 5795305f..30e9861c 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/mastodon/viewmodel/mastodon/AccountsVM.java @@ -372,6 +372,7 @@ public class AccountsVM extends AndroidViewModel { Boolean excludeReblogs, Boolean only_media, Boolean pinned, + String tagged, int count) { statusesMutableLiveData = new MutableLiveData<>(); MastodonAccountsService mastodonAccountsService = init(instance); @@ -379,7 +380,7 @@ public class AccountsVM extends AndroidViewModel { List statusList = null; Pagination pagination = null; Call> accountStatusesCall = mastodonAccountsService.getAccountStatuses( - token, id, maxId, sinceId, minId, excludeReplies, excludeReblogs, only_media, pinned, count); + token, id, maxId, sinceId, minId, excludeReplies, excludeReblogs, only_media, pinned, tagged, count); if (accountStatusesCall != null) { try { Response> accountStatusesResponse = accountStatusesCall.execute(); diff --git a/app/src/main/res/layouts/mastodon/layout/activity_profile.xml b/app/src/main/res/layouts/mastodon/layout/activity_profile.xml index 56916e02..d10e52a7 100644 --- a/app/src/main/res/layouts/mastodon/layout/activity_profile.xml +++ b/app/src/main/res/layouts/mastodon/layout/activity_profile.xml @@ -375,6 +375,27 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/info" /> + + +