diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java index 2c5efb83d..79aec1697 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java @@ -3,6 +3,8 @@ package org.joinmastodon.android.fragments; import android.app.Activity; import android.os.Bundle; import android.view.View; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; @@ -11,27 +13,34 @@ import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.drawables.EmptyDrawable; +import org.joinmastodon.android.ui.views.FilterChipView; import org.parceler.Parcels; import java.util.Collections; import java.util.List; +import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.api.SimpleCallback; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; +import me.grishka.appkit.utils.V; public class AccountTimelineFragment extends StatusListFragment{ private Account user; private GetAccountStatuses.Filter filter; + private HorizontalScrollView filtersBar; + private FilterChipView defaultFilter, withRepliesFilter, mediaFilter; public AccountTimelineFragment(){ setListLayoutId(R.layout.recycler_fragment_no_refresh); } - public static AccountTimelineFragment newInstance(String accountID, Account profileAccount, GetAccountStatuses.Filter filter, boolean load){ + public static AccountTimelineFragment newInstance(String accountID, Account profileAccount, boolean load){ AccountTimelineFragment f=new AccountTimelineFragment(); Bundle args=new Bundle(); args.putString("account", accountID); args.putParcelable("profileAccount", Parcels.wrap(profileAccount)); - args.putString("filter", filter.toString()); if(!load) args.putBoolean("noAutoLoad", true); args.putBoolean("__is_tab", true); @@ -41,9 +50,9 @@ public class AccountTimelineFragment extends StatusListFragment{ @Override public void onAttach(Activity activity){ - super.onAttach(activity); user=Parcels.unwrap(getArguments().getParcelable("profileAccount")); - filter=GetAccountStatuses.Filter.valueOf(getArguments().getString("filter")); + filter=GetAccountStatuses.Filter.DEFAULT; + super.onAttach(activity); } @Override @@ -92,4 +101,77 @@ public class AccountTimelineFragment extends StatusListFragment{ protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ // no-op } + + @Override + protected RecyclerView.Adapter getAdapter(){ + filtersBar=new HorizontalScrollView(getActivity()); + LinearLayout filtersLayout=new LinearLayout(getActivity()); + filtersBar.addView(filtersLayout); + filtersLayout.setOrientation(LinearLayout.HORIZONTAL); + filtersLayout.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(8)); + filtersLayout.setDividerDrawable(new EmptyDrawable(V.dp(8), 1)); + filtersLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + + defaultFilter=new FilterChipView(getActivity()); + defaultFilter.setText(R.string.posts); + defaultFilter.setTag(GetAccountStatuses.Filter.DEFAULT); + defaultFilter.setSelected(filter==GetAccountStatuses.Filter.DEFAULT); + defaultFilter.setOnClickListener(this::onFilterClick); + filtersLayout.addView(defaultFilter); + + withRepliesFilter=new FilterChipView(getActivity()); + withRepliesFilter.setText(R.string.posts_and_replies); + withRepliesFilter.setTag(GetAccountStatuses.Filter.INCLUDE_REPLIES); + withRepliesFilter.setSelected(filter==GetAccountStatuses.Filter.INCLUDE_REPLIES); + withRepliesFilter.setOnClickListener(this::onFilterClick); + filtersLayout.addView(withRepliesFilter); + + mediaFilter=new FilterChipView(getActivity()); + mediaFilter.setText(R.string.media); + mediaFilter.setTag(GetAccountStatuses.Filter.MEDIA); + mediaFilter.setSelected(filter==GetAccountStatuses.Filter.MEDIA); + mediaFilter.setOnClickListener(this::onFilterClick); + filtersLayout.addView(mediaFilter); + + MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter(); + mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(filtersBar)); + mergeAdapter.addAdapter(super.getAdapter()); + return mergeAdapter; + } + + @Override + protected int getMainAdapterOffset(){ + return super.getMainAdapterOffset()+1; + } + + private FilterChipView getViewForFilter(GetAccountStatuses.Filter filter){ + return switch(filter){ + case DEFAULT -> defaultFilter; + case INCLUDE_REPLIES -> withRepliesFilter; + case MEDIA -> mediaFilter; + default -> throw new IllegalStateException("Unexpected value: "+filter); + }; + } + + private void onFilterClick(View v){ + GetAccountStatuses.Filter newFilter=(GetAccountStatuses.Filter) v.getTag(); + if(newFilter==filter) + return; + // TODO maybe cache the filtered timelines that were already loaded? + if(currentRequest!=null){ + currentRequest.cancel(); + currentRequest=null; + } + getViewForFilter(filter).setSelected(false); + filter=newFilter; + v.setSelected(true); + data.clear(); + preloadedData.clear(); + int size=displayItems.size(); + displayItems.clear(); + adapter.notifyItemRangeRemoved(0, size); + loaded=false; + dataLoading=true; + doLoadData(); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 8ff00c3fb..7d37c3c49 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -18,7 +18,6 @@ import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Parcelable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.ImageSpan; @@ -48,7 +47,6 @@ import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountByID; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; -import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; @@ -342,7 +340,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList args.putParcelable("profileAccount", Parcels.wrap(account)); args.putBoolean("__is_tab", true); featuredFragment.setArguments(args); - timelineFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true); + timelineFragment=AccountTimelineFragment.newInstance(accountID, account, true); aboutFragment=new ProfileAboutFragment(); aboutFragment.setFields(fields); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/EmptyDrawable.java b/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/EmptyDrawable.java new file mode 100644 index 000000000..f9fd9bfe8 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/drawables/EmptyDrawable.java @@ -0,0 +1,48 @@ +package org.joinmastodon.android.ui.drawables; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class EmptyDrawable extends Drawable{ + private final int width, height; + + public EmptyDrawable(int width, int height){ + this.width=width; + this.height=height; + } + + @Override + public void draw(@NonNull Canvas canvas){ + + } + + @Override + public void setAlpha(int alpha){ + + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter){ + + } + + @Override + public int getOpacity(){ + return PixelFormat.TRANSPARENT; + } + + @Override + public int getIntrinsicWidth(){ + return width; + } + + @Override + public int getIntrinsicHeight(){ + return height; + } +}