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.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);
}
});
}

View file

@ -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);
}

View file

@ -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
);

View file

@ -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";

View file

@ -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 {

View file

@ -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<Status> 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;

View file

@ -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<Status> statusList = null;
Pagination pagination = null;
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) {
try {
Response<List<Status>> accountStatusesResponse = accountStatusesCall.execute();

View file

@ -375,6 +375,27 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
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
@ -388,7 +409,7 @@
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fields_container"
app:layout_constraintTop_toBottomOf="@+id/featured_hashtags_container"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatTextView