Featured tab in profiles
This commit is contained in:
parent
09ffda2605
commit
955b9a4b2b
|
@ -0,0 +1,14 @@
|
|||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetAccountFeaturedHashtags extends MastodonAPIRequest<List<Hashtag>>{
|
||||
public GetAccountFeaturedHashtags(String id){
|
||||
super(HttpMethod.GET, "/accounts/"+id+"/featured_tags", new TypeToken<>(){});
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
|||
addQueryParameter("exclude_reblogs", "true");
|
||||
}
|
||||
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
|
||||
case PINNED -> addQueryParameter("pinned", "true");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +36,7 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
|
|||
INCLUDE_REPLIES,
|
||||
MEDIA,
|
||||
NO_REBLOGS,
|
||||
OWN_POSTS_AND_REPLIES
|
||||
OWN_POSTS_AND_REPLIES,
|
||||
PINNED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag>{
|
||||
private Account account;
|
||||
private String accountID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||
onDataLoaded(getArguments().getParcelableArrayList("hashtags").stream().map(p->(Hashtag)Parcels.unwrap(p)).collect(Collectors.toList()), false);
|
||||
setTitle(R.string.featured_hashtags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Hashtag s){
|
||||
return Collections.singletonList(new HashtagStatusDisplayItem(s.name, this, s));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Hashtag s){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
@Override
|
||||
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||
// no-op
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class PinnedPostsListFragment extends StatusListFragment{
|
||||
private Account account;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||
setTitle(R.string.pinned_posts);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountFeaturedHashtags;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.SectionHeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ProfileFeaturedFragment extends BaseStatusListFragment<SearchResult>{
|
||||
private Account profileAccount;
|
||||
private List<Hashtag> featuredTags;
|
||||
// private List<Account> endorsedAccounts;
|
||||
private List<Status> pinnedStatuses;
|
||||
private boolean tagsLoaded, statusesLoaded;
|
||||
|
||||
public ProfileFeaturedFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_no_refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
profileAccount=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
||||
ArrayList<StatusDisplayItem> items=switch(s.type){
|
||||
case ACCOUNT -> new ArrayList<>(Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account)));
|
||||
case HASHTAG -> new ArrayList<>(Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag)));
|
||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true);
|
||||
};
|
||||
|
||||
if(s.firstInSection){
|
||||
items.add(0, new SectionHeaderStatusDisplayItem(this, getString(switch(s.type){
|
||||
case ACCOUNT -> R.string.profile_endorsed_accounts;
|
||||
case HASHTAG -> R.string.hashtags;
|
||||
case STATUS -> R.string.posts;
|
||||
}), getString(R.string.view_all), switch(s.type){
|
||||
case ACCOUNT -> (Runnable)this::showAllEndorsedAccounts;
|
||||
case HASHTAG -> (Runnable)this::showAllFeaturedHashtags;
|
||||
case STATUS -> (Runnable)this::showAllPinnedPosts;
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(SearchResult s){
|
||||
Account acc=switch(s.type){
|
||||
case ACCOUNT -> s.account;
|
||||
case STATUS -> s.status.account;
|
||||
case HASHTAG -> null;
|
||||
};
|
||||
if(acc!=null && !knownAccounts.containsKey(acc.id))
|
||||
knownAccounts.put(acc.id, acc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
SearchResult res=getResultByID(id);
|
||||
if(res==null)
|
||||
return;
|
||||
switch(res.type){
|
||||
case ACCOUNT -> {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name);
|
||||
case STATUS -> {
|
||||
Status status=res.status.getContentStatus();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status));
|
||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(!statusesLoaded){
|
||||
new GetAccountStatuses(profileAccount.id, null, null, 1, GetAccountStatuses.Filter.PINNED)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
pinnedStatuses=result;
|
||||
statusesLoaded=true;
|
||||
onOneApiRequestCompleted();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
if(!tagsLoaded){
|
||||
new GetAccountFeaturedHashtags(profileAccount.id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Hashtag> result){
|
||||
featuredTags=result;
|
||||
tagsLoaded=true;
|
||||
onOneApiRequestCompleted();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh(){
|
||||
statusesLoaded=false;
|
||||
tagsLoaded=false;
|
||||
super.onRefresh();
|
||||
}
|
||||
|
||||
private void onOneApiRequestCompleted(){
|
||||
if(tagsLoaded && statusesLoaded){
|
||||
ArrayList<SearchResult> results=new ArrayList<>();
|
||||
if(!pinnedStatuses.isEmpty()){
|
||||
SearchResult res=new SearchResult(pinnedStatuses.get(0));
|
||||
res.firstInSection=true;
|
||||
results.add(res);
|
||||
}
|
||||
for(int i=0;i<Math.min(3, featuredTags.size());i++){
|
||||
SearchResult res=new SearchResult(featuredTags.get(i));
|
||||
res.firstInSection=(i==0);
|
||||
results.add(res);
|
||||
}
|
||||
onDataLoaded(results, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected SearchResult getResultByID(String id){
|
||||
for(SearchResult s:data){
|
||||
if(s.id.equals(id)){
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void showAllPinnedPosts(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(profileAccount));
|
||||
Nav.go(getActivity(), PinnedPostsListFragment.class, args);
|
||||
}
|
||||
|
||||
private void showAllFeaturedHashtags(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
ArrayList<Parcelable> tags=featuredTags.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new));
|
||||
args.putParcelableArrayList("hashtags", tags);
|
||||
Nav.go(getActivity(), FeaturedHashtagsListFragment.class, args);
|
||||
}
|
||||
|
||||
private void showAllEndorsedAccounts(){
|
||||
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ 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;
|
||||
|
@ -111,7 +112,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
private ProgressBarButton actionButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
|
||||
private ProfileFeaturedFragment featuredFragment;
|
||||
private AccountTimelineFragment timelineFragment;
|
||||
private ProfileAboutFragment aboutFragment;
|
||||
private TabLayout tabbar;
|
||||
private SwipeRefreshLayout refreshLayout;
|
||||
|
@ -216,14 +218,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
}
|
||||
};
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
tabViews=new FrameLayout[3];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.profile_posts;
|
||||
case 1 -> R.id.profile_posts_with_replies;
|
||||
case 2 -> R.id.profile_media;
|
||||
case 3 -> R.id.profile_about;
|
||||
case 0 -> R.id.profile_featured;
|
||||
case 1 -> R.id.profile_timeline;
|
||||
case 2 -> R.id.profile_about;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
|
@ -245,10 +246,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.posts_and_replies;
|
||||
case 2 -> R.string.media;
|
||||
case 3 -> R.string.profile_about;
|
||||
case 0 -> R.string.profile_featured;
|
||||
case 1 -> R.string.profile_timeline;
|
||||
case 2 -> R.string.profile_about;
|
||||
default -> throw new IllegalStateException();
|
||||
});
|
||||
}
|
||||
|
@ -312,12 +312,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
if(refreshing){
|
||||
refreshing=false;
|
||||
refreshLayout.setRefreshing(false);
|
||||
if(postsFragment.loaded)
|
||||
postsFragment.onRefresh();
|
||||
if(postsWithRepliesFragment.loaded)
|
||||
postsWithRepliesFragment.onRefresh();
|
||||
if(mediaFragment.loaded)
|
||||
mediaFragment.onRefresh();
|
||||
if(timelineFragment.loaded)
|
||||
timelineFragment.onRefresh();
|
||||
if(featuredFragment.loaded)
|
||||
featuredFragment.onRefresh();
|
||||
}
|
||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||
}
|
||||
|
@ -337,10 +335,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
public void dataLoaded(){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(postsFragment==null){
|
||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
if(featuredFragment==null){
|
||||
featuredFragment=new ProfileFeaturedFragment();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
args.putBoolean("__is_tab", true);
|
||||
featuredFragment.setArguments(args);
|
||||
timelineFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
aboutFragment=new ProfileAboutFragment();
|
||||
aboutFragment.setFields(fields);
|
||||
}
|
||||
|
@ -397,11 +399,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
@ -425,10 +422,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
}
|
||||
|
||||
private void applyChildWindowInsets(){
|
||||
if(postsFragment!=null && postsFragment.isAdded() && childInsets!=null){
|
||||
postsFragment.onApplyWindowInsets(childInsets);
|
||||
postsWithRepliesFragment.onApplyWindowInsets(childInsets);
|
||||
mediaFragment.onApplyWindowInsets(childInsets);
|
||||
if(timelineFragment!=null && timelineFragment.isAdded() && childInsets!=null){
|
||||
timelineFragment.onApplyWindowInsets(childInsets);
|
||||
featuredFragment.onApplyWindowInsets(childInsets);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -693,10 +689,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> postsWithRepliesFragment;
|
||||
case 2 -> mediaFragment;
|
||||
case 3 -> aboutFragment;
|
||||
case 0 -> featuredFragment;
|
||||
case 1 -> timelineFragment;
|
||||
case 2 -> aboutFragment;
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
|
@ -759,7 +754,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
invalidateOptionsMenu();
|
||||
pager.setUserInputEnabled(false);
|
||||
actionButton.setText(R.string.save_changes);
|
||||
pager.setCurrentItem(3);
|
||||
pager.setCurrentItem(2);
|
||||
for(int i=0;i<3;i++){
|
||||
tabbar.getTabAt(i).view.setEnabled(false);
|
||||
}
|
||||
|
@ -1001,7 +996,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return loaded ? 4 : 0;
|
||||
return loaded ? 3 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,12 +6,13 @@ import org.parceler.Parcel;
|
|||
import java.util.List;
|
||||
|
||||
@Parcel
|
||||
public class Hashtag extends BaseModel{
|
||||
public class Hashtag extends BaseModel implements DisplayItemsParent{
|
||||
@RequiredField
|
||||
public String name;
|
||||
@RequiredField
|
||||
public String url;
|
||||
public List<History> history;
|
||||
public int statusesCount;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
|
@ -19,6 +20,12 @@ public class Hashtag extends BaseModel{
|
|||
"name='"+name+'\''+
|
||||
", url='"+url+'\''+
|
||||
", history="+history+
|
||||
", statusesCount="+statusesCount+
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID(){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ public class SearchResult extends BaseModel implements DisplayItemsParent{
|
|||
public Type type;
|
||||
|
||||
public transient String id;
|
||||
public transient boolean firstInSection;
|
||||
|
||||
public SearchResult(){}
|
||||
|
||||
|
|
|
@ -37,12 +37,15 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{
|
|||
public void onBind(HashtagStatusDisplayItem _item){
|
||||
Hashtag item=_item.tag;
|
||||
title.setText('#'+item.name);
|
||||
int numPeople=item.history.get(0).accounts;
|
||||
if(item.history.size()>1)
|
||||
numPeople+=item.history.get(1).accounts;
|
||||
subtitle.setText(_item.parentFragment.getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
|
||||
chart.setData(item.history);
|
||||
|
||||
if(item.history!=null && !item.history.isEmpty()){
|
||||
int numPeople=item.history.get(0).accounts;
|
||||
if(item.history.size()>1)
|
||||
numPeople+=item.history.get(1).accounts;
|
||||
subtitle.setText(itemView.getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
|
||||
chart.setData(item.history);
|
||||
}else{
|
||||
subtitle.setText(itemView.getResources().getQuantityString(R.plurals.x_posts, item.statusesCount, item.statusesCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
|
||||
public class SectionHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public final String title, buttonText;
|
||||
public final Runnable onButtonClick;
|
||||
|
||||
public SectionHeaderStatusDisplayItem(BaseStatusListFragment parentFragment, String title, String buttonText, Runnable onButtonClick){
|
||||
super("", parentFragment);
|
||||
this.title=title;
|
||||
this.buttonText=buttonText;
|
||||
this.onButtonClick=onButtonClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.SECTION_HEADER;
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<SectionHeaderStatusDisplayItem>{
|
||||
private final TextView title;
|
||||
private final Button actionBtn;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_section_header, parent);
|
||||
title=findViewById(R.id.title);
|
||||
actionBtn=findViewById(R.id.action_btn);
|
||||
actionBtn.setOnClickListener(v->item.onButtonClick.run());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SectionHeaderStatusDisplayItem item){
|
||||
title.setText(item.title);
|
||||
if(item.onButtonClick!=null){
|
||||
actionBtn.setVisibility(View.VISIBLE);
|
||||
actionBtn.setText(item.buttonText);
|
||||
}else{
|
||||
actionBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -65,6 +65,7 @@ public abstract class StatusDisplayItem{
|
|||
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
|
||||
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
|
||||
case SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent);
|
||||
case SECTION_HEADER -> new SectionHeaderStatusDisplayItem.Holder(activity, parent);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -154,7 +155,8 @@ public abstract class StatusDisplayItem{
|
|||
GAP,
|
||||
EXTENDED_FOOTER,
|
||||
MEDIA_GRID,
|
||||
SPOILER
|
||||
SPOILER,
|
||||
SECTION_HEADER
|
||||
}
|
||||
|
||||
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
|
||||
|
|
|
@ -14,12 +14,12 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.utils.CustomViewHelper;
|
||||
|
||||
public class HashtagChartView extends View{
|
||||
public class HashtagChartView extends View implements CustomViewHelper{
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Path strokePath=new Path(), fillPath=new Path();
|
||||
private CornerPathEffect pathEffect=new CornerPathEffect(V.dp(3));
|
||||
private final CornerPathEffect pathEffect=new CornerPathEffect(dp(3));
|
||||
private float[] relativeOffsets=new float[7];
|
||||
|
||||
public HashtagChartView(Context context){
|
||||
|
@ -32,7 +32,7 @@ public class HashtagChartView extends View{
|
|||
|
||||
public HashtagChartView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
paint.setStrokeWidth(V.dp(1.71f));
|
||||
paint.setStrokeWidth(dp(1.71f));
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
}
|
||||
|
@ -57,20 +57,20 @@ public class HashtagChartView extends View{
|
|||
return;
|
||||
strokePath.rewind();
|
||||
fillPath.rewind();
|
||||
float step=(getWidth()-V.dp(2))/(float)(relativeOffsets.length-1);
|
||||
float maxH=getHeight()-V.dp(2);
|
||||
float x=getWidth()-V.dp(1);
|
||||
strokePath.moveTo(x, maxH-maxH*relativeOffsets[0]+V.dp(1));
|
||||
fillPath.moveTo(getWidth(), getHeight()-V.dp(1));
|
||||
fillPath.lineTo(x, maxH-maxH*relativeOffsets[0]+V.dp(1));
|
||||
float step=(getWidth()-dp(2))/(float)(relativeOffsets.length-1);
|
||||
float maxH=getHeight()-dp(2);
|
||||
float x=getWidth()-dp(1);
|
||||
strokePath.moveTo(x, maxH-maxH*relativeOffsets[0]+dp(1));
|
||||
fillPath.moveTo(getWidth(), getHeight()-dp(1));
|
||||
fillPath.lineTo(x, maxH-maxH*relativeOffsets[0]+dp(1));
|
||||
for(int i=1;i<relativeOffsets.length;i++){
|
||||
float offset=relativeOffsets[i];
|
||||
x-=step;
|
||||
float y=maxH-maxH*offset+V.dp(1);
|
||||
float y=maxH-maxH*offset+dp(1);
|
||||
strokePath.lineTo(x, y);
|
||||
fillPath.lineTo(x, y);
|
||||
}
|
||||
fillPath.lineTo(V.dp(1), getHeight()-V.dp(1));
|
||||
fillPath.lineTo(dp(1), getHeight()-dp(1));
|
||||
fillPath.close();
|
||||
}
|
||||
|
||||
|
@ -83,11 +83,11 @@ public class HashtagChartView extends View{
|
|||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorAccentLightest));
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3PrimaryInverse));
|
||||
paint.setPathEffect(null);
|
||||
canvas.drawPath(fillPath, paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), android.R.attr.colorAccent));
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Primary));
|
||||
paint.setPathEffect(pathEffect);
|
||||
canvas.drawPath(strokePath, paint);
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ public class NestedRecyclerScrollView extends CustomScrollView{
|
|||
|
||||
@Override
|
||||
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
|
||||
final RecyclerView rv = (RecyclerView) target;
|
||||
if ((dy < 0 && isScrolledToTop(rv)) || (dy > 0 && !isScrolledToBottom())) {
|
||||
if(target instanceof RecyclerView rv && ((dy < 0 && isScrolledToTop(rv)) || (dy > 0 && !isScrolledToBottom()))){
|
||||
scrollBy(0, dy);
|
||||
consumed[1] = dy;
|
||||
return;
|
||||
|
@ -37,8 +36,7 @@ public class NestedRecyclerScrollView extends CustomScrollView{
|
|||
|
||||
@Override
|
||||
public boolean onNestedPreFling(View target, float velX, float velY) {
|
||||
final RecyclerView rv = (RecyclerView) target;
|
||||
if ((velY < 0 && isScrolledToTop(rv)) || (velY > 0 && !isScrolledToBottom())) {
|
||||
if (target instanceof RecyclerView rv && ((velY < 0 && isScrolledToTop(rv)) || (velY > 0 && !isScrolledToBottom()))){
|
||||
fling((int) velY);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Section Header"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/action_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="-8dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3Primary"
|
||||
android:background="@drawable/bg_button_borderless_rounded"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
tools:text="Action"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -2,11 +2,7 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/text"
|
||||
|
@ -14,6 +10,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/m3_body_large"/>
|
||||
|
||||
</FrameLayout>
|
|
@ -215,11 +215,11 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-black"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:textSize="14dp"
|
||||
android:textStyle="bold"
|
||||
tools:text="123" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -2,39 +2,43 @@
|
|||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:layout_height="72dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp">
|
||||
android:paddingTop="14dp">
|
||||
|
||||
<org.joinmastodon.android.ui.views.HashtagChartView
|
||||
android:id="@+id/chart"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="66dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="7dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toStartOf="@id/chart"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
tools:text="#mastodev"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/chart"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textColor="?colorM3OnSurfaceVariant"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="over 9000 people talking"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -41,6 +41,7 @@
|
|||
<attr name="colorM3OnError" format="color"/>
|
||||
<attr name="colorM3ErrorContainer" format="color"/>
|
||||
<attr name="colorM3OnErrorContainer" format="color"/>
|
||||
<attr name="colorM3PrimaryInverse" format="color"/>
|
||||
|
||||
<attr name="primaryLargeButtonStyle" format="reference"/>
|
||||
<attr name="secondaryLargeButtonStyle" format="reference"/>
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
<resources>
|
||||
<item name="header" type="id"/>
|
||||
|
||||
<item name="profile_posts" type="id"/>
|
||||
<item name="profile_posts_with_replies" type="id"/>
|
||||
<item name="profile_media" type="id"/>
|
||||
<item name="profile_featured" type="id"/>
|
||||
<item name="profile_timeline" type="id"/>
|
||||
<item name="profile_about" type="id"/>
|
||||
|
||||
<item name="discover_posts" type="id"/>
|
||||
|
|
|
@ -442,4 +442,10 @@
|
|||
<string name="spoiler_hide">Re-hide</string>
|
||||
<string name="poll_multiple_choice">Choose one or more</string>
|
||||
<string name="save_changes">Save changes</string>
|
||||
<string name="profile_featured">Featured</string>
|
||||
<string name="profile_timeline">Timeline</string>
|
||||
<string name="view_all">View all</string>
|
||||
<string name="profile_endorsed_accounts">Accounts</string>
|
||||
<string name="pinned_posts">Pinned posts</string>
|
||||
<string name="featured_hashtags">Featured hashtags</string>
|
||||
</resources>
|
|
@ -65,6 +65,7 @@
|
|||
<item name="colorM3OnError">#FFF</item>
|
||||
<item name="colorM3ErrorContainer">#F9DEDC</item>
|
||||
<item name="colorM3OnErrorContainer">#410E0B</item>
|
||||
<item name="colorM3PrimaryInverse">@color/m3_sys_dark_primary</item>
|
||||
|
||||
<item name="colorWindowBackground">?colorM3Background</item>
|
||||
<item name="android:statusBarColor">?colorM3Background</item>
|
||||
|
@ -139,6 +140,7 @@
|
|||
<item name="colorM3OnError">#601410</item>
|
||||
<item name="colorM3ErrorContainer">#8C1D18</item>
|
||||
<item name="colorM3OnErrorContainer">#F9DEDC</item>
|
||||
<item name="colorM3PrimaryInverse">@color/m3_sys_light_primary</item>
|
||||
|
||||
<item name="colorWindowBackground">?colorM3Background</item>
|
||||
<item name="android:statusBarColor">?colorM3Background</item>
|
||||
|
|
Loading…
Reference in New Issue