Featured tags displayed in profiles

This commit is contained in:
Thomas 2025-06-05 17:34:51 +02:00
parent 5b843291a7
commit d514c7b8d6
8 changed files with 88 additions and 11 deletions

View file

@ -40,6 +40,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -69,6 +70,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition; 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.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.tabs.TabLayout; 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.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment; 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.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.Field;
import app.fedilab.android.mastodon.client.entities.api.IdentityProof; import app.fedilab.android.mastodon.client.entities.api.IdentityProof;
import app.fedilab.android.mastodon.client.entities.api.MastodonList; 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.helper.ThemeHelper;
import app.fedilab.android.mastodon.ui.drawer.FieldAdapter; import app.fedilab.android.mastodon.ui.drawer.FieldAdapter;
import app.fedilab.android.mastodon.ui.drawer.IdentityProofsAdapter; 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.ui.pageadapter.FedilabProfileTLPageAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM; import app.fedilab.android.mastodon.viewmodel.mastodon.NodeInfoVM;
@ -229,7 +233,45 @@ public class ProfileActivity extends BaseActivity {
//Check if account is homeMuted //Check if account is homeMuted
accountsVM.isMuted(Helper.getCurrentAccount(ProfileActivity.this), account).observe(this, result -> homeMuted = result != null && result); 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); 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);
}
});
} }

View file

@ -57,21 +57,30 @@ public class TimelineActivity extends BaseBarActivity {
Timeline.TimeLineEnum timelineType = null; Timeline.TimeLineEnum timelineType = null;
String lemmy_post_id = null; String lemmy_post_id = null;
PinnedTimeline pinnedTimeline = null; PinnedTimeline pinnedTimeline = null;
String tagged = null;
String timelineAccountId = null;
Status status = null; Status status = null;
if (bundle != null) { if (bundle != null) {
timelineType = (Timeline.TimeLineEnum) bundle.get(Helper.ARG_TIMELINE_TYPE); timelineType = (Timeline.TimeLineEnum) bundle.get(Helper.ARG_TIMELINE_TYPE);
lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null); lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null);
pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE);
status = (Status) bundle.getSerializable(Helper.ARG_STATUS); 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) { if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) {
setTitle(pinnedTimeline.remoteInstance.host); setTitle(pinnedTimeline.remoteInstance.host);
} }
if(tagged != null) {
setTitle(String.format("#%s",tagged));
}
FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline(); FragmentMastodonTimeline fragmentMastodonTimeline = new FragmentMastodonTimeline();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putSerializable(Helper.ARG_TIMELINE_TYPE, timelineType); args.putSerializable(Helper.ARG_TIMELINE_TYPE, timelineType);
args.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline); args.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline);
args.putSerializable(Helper.ARG_LEMMY_POST_ID, lemmy_post_id); 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) { if (status != null) {
args.putSerializable(Helper.ARG_STATUS, status); args.putSerializable(Helper.ARG_STATUS, status);
} }

View file

@ -124,6 +124,7 @@ public interface MastodonAccountsService {
@Query("exclude_reblogs") Boolean exclude_reblogs, @Query("exclude_reblogs") Boolean exclude_reblogs,
@Query("only_media") Boolean only_media, @Query("only_media") Boolean only_media,
@Query("pinned") Boolean pinned, @Query("pinned") Boolean pinned,
@Query("tagged") String tagged,
@Query("limit") int limit @Query("limit") int limit
); );

View file

@ -288,6 +288,7 @@ public class Helper {
public static final String ARG_SHOW_REBLOGS = "ARG_SHOW_REBLOGS"; 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_INITIALIZE_VIEW = "ARG_INITIALIZE_VIEW";
public static final String ARG_SHOW_PINNED = "ARG_SHOW_PINNED"; 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_SHOW_MEDIA_ONY = "ARG_SHOW_MEDIA_ONY";
public static final String ARG_MENTION = "ARG_MENTION"; public static final String ARG_MENTION = "ARG_MENTION";
public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY"; public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY";

View file

@ -128,7 +128,7 @@ public class FragmentMediaProfile extends Fragment {
public void federatedAccount(Account account) { public void federatedAccount(Account account) {
if (account != null && isAdded() && !requireActivity().isFinishing()) { if (account != null && isAdded() && !requireActivity().isFinishing()) {
accountId = account.id; 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)); .observe(getViewLifecycleOwner(), statuses -> initializeStatusesCommonView(statuses));
} else { } else {
if (isAdded() && !requireActivity().isFinishing()) { if (isAdded() && !requireActivity().isFinishing()) {
@ -141,7 +141,7 @@ public class FragmentMediaProfile extends Fragment {
tempToken = BaseMainActivity.currentToken; tempToken = BaseMainActivity.currentToken;
tempInstance = BaseMainActivity.currentInstance; tempInstance = BaseMainActivity.currentInstance;
accountId = accountTimeline.id; 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); .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} }
} }
@ -208,7 +208,7 @@ public class FragmentMediaProfile extends Fragment {
if (!flagLoading) { if (!flagLoading) {
flagLoading = true; flagLoading = true;
binding.loadingNextElements.setVisibility(View.VISIBLE); 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)); .observe(getViewLifecycleOwner(), newStatuses -> dealWithPagination(newStatuses));
} }
} else { } else {

View file

@ -91,6 +91,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private Integer offset; private Integer offset;
private StatusAdapter statusAdapter; private StatusAdapter statusAdapter;
private Timeline.TimeLineEnum timelineType; private Timeline.TimeLineEnum timelineType;
private String tagged;
private List<Status> timelineStatuses; private List<Status> timelineStatuses;
//Handle actions that can be done in other fragments //Handle actions that can be done in other fragments
private final BroadcastReceiver receive_action = new BroadcastReceiver() { 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); lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null);
list_id = bundle.getString(Helper.ARG_LIST_ID, null); list_id = bundle.getString(Helper.ARG_LIST_ID, null);
search = bundle.getString(Helper.ARG_SEARCH_KEYWORD, 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); searchCache = bundle.getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null);
pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE);
canBeFederated = true; canBeFederated = true;
@ -1171,8 +1173,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
public void federatedAccount(Account account) { public void federatedAccount(Account account) {
if (account != null && isAdded() && !requireActivity().isFinishing()) { if (account != null && isAdded() && !requireActivity().isFinishing()) {
accountIDInRemoteInstance = account.id; accountIDInRemoteInstance = account.id;
accountsVM.getAccountStatuses(tempInstance[0], null, accountIDInRemoteInstance, null, null, null, null, null, false, true, 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, 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 -> { .observe(getViewLifecycleOwner(), otherStatuses -> {
if (otherStatuses != null && otherStatuses.statuses != null) { if (otherStatuses != null && otherStatuses.statuses != null) {
if (pinnedStatuses != null && pinnedStatuses.statuses != null) { if (pinnedStatuses != null && pinnedStatuses.statuses != null) {
@ -1315,8 +1317,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
if (direction == null && !checkRemotely) { if (direction == null && !checkRemotely) {
if (show_pinned) { if (show_pinned) {
//Fetch pinned statuses to display them at the top //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())) 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, 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 -> { .observe(getViewLifecycleOwner(), otherStatuses -> {
if (otherStatuses != null && otherStatuses.statuses != null && pinnedStatuses != null && pinnedStatuses.statuses != null) { if (otherStatuses != null && otherStatuses.statuses != null && pinnedStatuses != null && pinnedStatuses.statuses != null) {
for (Status status : pinnedStatuses.statuses) { for (Status status : pinnedStatuses.statuses) {
@ -1327,11 +1329,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} }
})); }));
} else { } 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); .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView);
} }
} else if (direction == DIRECTION.BOTTOM) { } 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)); .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false, true, fetchStatus));
} else { } else {
flagLoading = false; flagLoading = false;

View file

@ -372,6 +372,7 @@ public class AccountsVM extends AndroidViewModel {
Boolean excludeReblogs, Boolean excludeReblogs,
Boolean only_media, Boolean only_media,
Boolean pinned, Boolean pinned,
String tagged,
int count) { int count) {
statusesMutableLiveData = new MutableLiveData<>(); statusesMutableLiveData = new MutableLiveData<>();
MastodonAccountsService mastodonAccountsService = init(instance); MastodonAccountsService mastodonAccountsService = init(instance);
@ -379,7 +380,7 @@ public class AccountsVM extends AndroidViewModel {
List<Status> statusList = null; List<Status> statusList = null;
Pagination pagination = null; Pagination pagination = null;
Call<List<Status>> accountStatusesCall = mastodonAccountsService.getAccountStatuses( Call<List<Status>> 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) { if (accountStatusesCall != null) {
try { try {
Response<List<Status>> accountStatusesResponse = accountStatusesCall.execute(); Response<List<Status>> accountStatusesResponse = accountStatusesCall.execute();

View file

@ -375,6 +375,27 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info" /> app:layout_constraintTop_toBottomOf="@+id/info" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/featured_hashtags_container"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fields_container">
<com.google.android.material.chip.ChipGroup
android:id="@+id/featured_hashtags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacingHorizontal="6dp"
app:chipSpacingVertical="6dp"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
@ -388,7 +409,7 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fields_container" app:layout_constraintTop_toBottomOf="@+id/featured_hashtags_container"
tools:visibility="visible"> tools:visibility="visible">
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView