Discover stuff
This commit is contained in:
parent
bb9cf5f5df
commit
9f0b55918d
|
@ -0,0 +1,20 @@
|
|||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetFollowSuggestions extends MastodonAPIRequest<List<FollowSuggestion>>{
|
||||
public GetFollowSuggestions(int limit){
|
||||
super(HttpMethod.GET, "/suggestions", new TypeToken<>(){});
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix(){
|
||||
return "/api/v2";
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.joinmastodon.android.api.requests;
|
||||
package org.joinmastodon.android.api.requests.instance;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.joinmastodon.android.api.requests;
|
||||
package org.joinmastodon.android.api.requests.instance;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Instance;
|
|
@ -0,0 +1,15 @@
|
|||
package org.joinmastodon.android.api.requests.trends;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetTrendingHashtags extends MastodonAPIRequest<List<Hashtag>>{
|
||||
public GetTrendingHashtags(int limit){
|
||||
super(HttpMethod.GET, "/trends", new TypeToken<>(){});
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package org.joinmastodon.android.api.requests.trends;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetTrendingLinks extends MastodonAPIRequest<List<Card>>{
|
||||
public GetTrendingLinks(){
|
||||
super(HttpMethod.GET, "/trends/links", new TypeToken<>(){});
|
||||
}
|
||||
}
|
|
@ -8,12 +8,8 @@ import org.joinmastodon.android.model.Status;
|
|||
import java.util.List;
|
||||
|
||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public GetTrendingStatuses(String maxID, String minID, int limit){
|
||||
public GetTrendingStatuses(int limit){
|
||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.google.gson.JsonParseException;
|
|||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
@ -34,7 +34,6 @@ import java.util.Comparator;
|
|||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper>{
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
|
||||
public DiscoverAccountsFragment(){
|
||||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if(relationshipsRequest!=null){
|
||||
relationshipsRequest.cancel();
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
currentRequest=new GetFollowSuggestions(count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new AccountsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
outRect.bottom=outRect.left=outRect.right=V.dp(16);
|
||||
if(parent.getChildAdapterPosition(view)==0)
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
});
|
||||
((UsableRecyclerView)list).setDrawSelectorOnTop(true);
|
||||
}
|
||||
|
||||
private void loadRelationships(){
|
||||
relationships=Collections.emptyMap();
|
||||
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||
relationshipsRequest.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequest=null;
|
||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof AccountViewHolder)
|
||||
((AccountViewHolder) holder).rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AccountViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new AccountViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return 2+data.get(position).emojiHelper.getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
AccountWrapper item=data.get(position);
|
||||
if(image==0)
|
||||
return item.avaRequest;
|
||||
else if(image==1)
|
||||
return item.coverRequest;
|
||||
else
|
||||
return item.emojiHelper.getImageRequest(image-2);
|
||||
}
|
||||
}
|
||||
|
||||
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final ImageView cover, avatar;
|
||||
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||
private final ProgressBarButton actionButton;
|
||||
private final ProgressBar actionProgress;
|
||||
private final View actionWrap;
|
||||
|
||||
private Relationship relationship;
|
||||
|
||||
public AccountViewHolder(){
|
||||
super(getActivity(), R.layout.item_discover_account, list);
|
||||
cover=findViewById(R.id.cover);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
followersCount=findViewById(R.id.followers_count);
|
||||
followersLabel=findViewById(R.id.followers_label);
|
||||
followingCount=findViewById(R.id.following_count);
|
||||
followingLabel=findViewById(R.id.following_label);
|
||||
postsCount=findViewById(R.id.posts_count);
|
||||
postsLabel=findViewById(R.id.posts_label);
|
||||
actionButton=findViewById(R.id.action_btn);
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
||||
itemView.setClipToOutline(true);
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||
cover.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(AccountWrapper item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.account.acct);
|
||||
bio.setText(item.parsedBio);
|
||||
followersCount.setText(UiUtils.abbreviateNumber(item.account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(item.account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, item.account.statusesCount)));
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else if(index==1){
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
}
|
||||
if(image instanceof Animatable && !((Animatable) image).isRunning())
|
||||
((Animatable) image).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
private void onActionButtonClick(View v){
|
||||
itemView.setHasTransientState(true);
|
||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||
itemView.setHasTransientState(false);
|
||||
relationships.put(item.account.id, rel);
|
||||
rebind();
|
||||
});
|
||||
}
|
||||
|
||||
private void setActionProgressVisible(boolean visible){
|
||||
actionButton.setTextVisible(!visible);
|
||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
actionButton.setClickable(!visible);
|
||||
}
|
||||
}
|
||||
|
||||
protected class AccountWrapper{
|
||||
public Account account;
|
||||
public ImageLoaderRequest avaRequest, coverRequest;
|
||||
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
public CharSequence parsedName, parsedBio;
|
||||
|
||||
public AccountWrapper(Account account){
|
||||
this.account=account;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), accountID);
|
||||
if(account.emojis.isEmpty()){
|
||||
parsedName=account.displayName;
|
||||
}else{
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop{
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
private FrameLayout[] tabViews;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
|
||||
private DiscoverPostsFragment postsFragment;
|
||||
private TrendingHashtagsFragment hashtagsFragment;
|
||||
private DiscoverNewsFragment newsFragment;
|
||||
private DiscoverAccountsFragment accountsFragment;
|
||||
|
||||
private String accountID;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
LinearLayout view=(LinearLayout) inflater.inflate(R.layout.fragment_discover, container, false);
|
||||
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.discover_posts;
|
||||
case 1 -> R.id.discover_hashtags;
|
||||
case 2 -> R.id.discover_news;
|
||||
case 3 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
||||
tabViews[i]=tabView;
|
||||
}
|
||||
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageSelected(int position){
|
||||
if(position==0)
|
||||
return;
|
||||
Fragment _page=getFragmentForPage(position);
|
||||
if(_page instanceof BaseRecyclerFragment){
|
||||
BaseRecyclerFragment page=(BaseRecyclerFragment) _page;
|
||||
if(!page.loaded && !page.isDataLoading())
|
||||
page.loadData();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(postsFragment==null){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
|
||||
postsFragment=new DiscoverPostsFragment();
|
||||
postsFragment.setArguments(args);
|
||||
|
||||
hashtagsFragment=new TrendingHashtagsFragment();
|
||||
hashtagsFragment.setArguments(args);
|
||||
|
||||
newsFragment=new DiscoverNewsFragment();
|
||||
newsFragment.setArguments(args);
|
||||
|
||||
accountsFragment=new DiscoverAccountsFragment();
|
||||
accountsFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||
.add(R.id.discover_news, newsFragment)
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.hashtags;
|
||||
case 2 -> R.string.news;
|
||||
case 3 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
}
|
||||
});
|
||||
tabLayoutMediator.attach();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
||||
postsFragment.loadData();
|
||||
}
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> hashtagsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 3 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
}
|
||||
|
||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card>{
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
|
||||
public DiscoverNewsFragment(){
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingLinks()
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Card> result){
|
||||
imageRequests=result.stream()
|
||||
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
||||
.collect(Collectors.toList());
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new LinksAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 0, 0));
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public LinksAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public LinkViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new LinkViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(LinkViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return imageRequests.get(position)==null ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return imageRequests.get(position);
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkViewHolder extends BindableViewHolder<Card> implements UsableRecyclerView.Clickable, ImageLoaderViewHolder{
|
||||
private final TextView name, title, subtitle;
|
||||
private final ImageView photo;
|
||||
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
|
||||
private boolean didClear;
|
||||
|
||||
public LinkViewHolder(){
|
||||
super(getActivity(), R.layout.item_trending_link, list);
|
||||
name=findViewById(R.id.name);
|
||||
title=findViewById(R.id.title);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
photo=findViewById(R.id.photo);
|
||||
photo.setOutlineProvider(OutlineProviders.roundedRect(2));
|
||||
photo.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Card item){
|
||||
name.setText(item.providerName);
|
||||
title.setText(item.title);
|
||||
int num=item.history.get(0).uses;
|
||||
if(item.history.size()>1)
|
||||
num+=item.history.get(1).uses;
|
||||
subtitle.setText(getResources().getQuantityString(R.plurals.discussed_x_times, num, num));
|
||||
crossfadeDrawable.setSize(item.width, item.height);
|
||||
crossfadeDrawable.setBlurhashDrawable(item.blurhashPlaceholder);
|
||||
crossfadeDrawable.setCrossfadeAlpha(0f);
|
||||
photo.setImageDrawable(null);
|
||||
photo.setImageDrawable(crossfadeDrawable);
|
||||
didClear=false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
crossfadeDrawable.setImageDrawable(drawable);
|
||||
if(didClear)
|
||||
crossfadeDrawable.animateAlpha(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
crossfadeDrawable.setCrossfadeAlpha(1f);
|
||||
didClear=true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
UiUtils.launchWebBrowser(getActivity(), item.url);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,14 +11,14 @@ import java.util.List;
|
|||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class SearchFragment extends StatusListFragment{
|
||||
public class DiscoverPostsFragment extends StatusListFragment{
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(offset>0 ? getMaxID() : null, null, count)
|
||||
currentRequest=new GetTrendingStatuses(count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
|
@ -36,7 +36,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||
private FragmentRootLinearLayout content;
|
||||
private HomeTimelineFragment homeTimelineFragment;
|
||||
private NotificationsFragment notificationsFragment;
|
||||
private SearchFragment searchFragment;
|
||||
private DiscoverFragment searchFragment;
|
||||
private ProfileFragment profileFragment;
|
||||
private TabBar tabBar;
|
||||
private View tabBarWrap;
|
||||
|
@ -60,7 +60,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||
homeTimelineFragment.setArguments(args);
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
searchFragment=new SearchFragment();
|
||||
searchFragment=new DiscoverFragment();
|
||||
searchFragment.setArguments(args);
|
||||
notificationsFragment=new NotificationsFragment();
|
||||
notificationsFragment.setArguments(args);
|
||||
|
@ -168,6 +168,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||
LoaderFragment lf=(LoaderFragment) newFragment;
|
||||
if(!lf.loaded && !lf.dataLoading)
|
||||
lf.loadData();
|
||||
}else if(newFragment instanceof DiscoverFragment){
|
||||
((DiscoverFragment) newFragment).loadData();
|
||||
}
|
||||
currentTab=tab;
|
||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.animation.AnimatorListenerAdapter;
|
|||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
|
@ -39,15 +38,13 @@ 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.SetAccountBlocked;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
|
@ -382,9 +379,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, account.followersCount));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, account.followingCount));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, account.statusesCount));
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, Math.min(999, account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, Math.min(999, account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, Math.min(999, account.statusesCount)));
|
||||
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
UiUtils.loadCustomEmojiInTextView(bio);
|
||||
|
@ -508,13 +505,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
private void updateRelationship(){
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
if(relationship.blocking){
|
||||
actionButton.setText(R.string.button_blocked);
|
||||
}else if(relationship.muting){
|
||||
actionButton.setText(R.string.button_muted);
|
||||
}else{
|
||||
actionButton.setText(relationship.following ? R.string.button_following : R.string.button_follow);
|
||||
}
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
}
|
||||
|
||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||
|
@ -569,13 +560,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
else
|
||||
saveAndExitEditMode();
|
||||
}else{
|
||||
if(relationship.blocking){
|
||||
confirmToggleBlocked();
|
||||
}else if(relationship.muting){
|
||||
confirmToggleMuted();
|
||||
}else{
|
||||
toggleFollowing();
|
||||
}
|
||||
UiUtils.performAccountAction(getActivity(), account, accountID, relationship, actionButton, this::setActionProgressVisible, this::updateRelationship);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,26 +697,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void toggleFollowing(){
|
||||
setActionProgressVisible(true);
|
||||
new SetAccountFollowed(account.id, !relationship.following)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
relationship=result;
|
||||
updateRelationship();
|
||||
setActionProgressVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
setActionProgressVisible(false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void confirmToggleMuted(){
|
||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||
}
|
||||
|
@ -829,10 +794,4 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleViewHolder extends RecyclerView.ViewHolder{
|
||||
public SimpleViewHolder(@NonNull View itemView){
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.HashtagChartView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag>{
|
||||
private String accountID;
|
||||
|
||||
public TrendingHashtagsFragment(){
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingHashtags(10)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Hashtag> result){
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new HashtagsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, .5f, 16, 16));
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public HashtagViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new HashtagViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HashtagViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class HashtagViewHolder extends BindableViewHolder<Hashtag> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title, subtitle;
|
||||
private final HashtagChartView chart;
|
||||
|
||||
public HashtagViewHolder(){
|
||||
super(getActivity(), R.layout.item_trending_hashtag, list);
|
||||
title=findViewById(R.id.title);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
chart=findViewById(R.id.chart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Hashtag item){
|
||||
title.setText('#'+item.name);
|
||||
int numPeople=item.history.get(0).accounts;
|
||||
if(item.history.size()>1)
|
||||
numPeople+=item.history.get(1).accounts;
|
||||
subtitle.setText(getResources().getQuantityString(R.plurals.x_people_talking, numPeople, numPeople));
|
||||
chart.setData(item.history);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
|||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
|
@ -22,7 +21,7 @@ import android.widget.Toast;
|
|||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
|
|
|
@ -11,6 +11,8 @@ import org.joinmastodon.android.ui.utils.BlurHashDecoder;
|
|||
import org.joinmastodon.android.ui.utils.BlurHashDrawable;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Parcel
|
||||
public class Card extends BaseModel{
|
||||
@RequiredField
|
||||
|
@ -31,6 +33,7 @@ public class Card extends BaseModel{
|
|||
public String image;
|
||||
public String embedUrl;
|
||||
public String blurhash;
|
||||
public List<History> history;
|
||||
|
||||
public transient Drawable blurhashPlaceholder;
|
||||
|
||||
|
@ -60,6 +63,7 @@ public class Card extends BaseModel{
|
|||
", image='"+image+'\''+
|
||||
", embedUrl='"+embedUrl+'\''+
|
||||
", blurhash='"+blurhash+'\''+
|
||||
", history="+history+
|
||||
'}';
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
|
||||
public class FollowSuggestion extends BaseModel{
|
||||
@RequiredField
|
||||
public Account account;
|
||||
// public String source;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
account.postprocess();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DividerItemDecoration extends RecyclerView.ItemDecoration{
|
||||
private Paint paint=new Paint();
|
||||
private int paddingStart, paddingEnd;
|
||||
|
||||
public DividerItemDecoration(Context context, @AttrRes int color, float thicknessDp, int paddingStartDp, int paddingEndDp){
|
||||
paint.setColor(UiUtils.getThemeColor(context, color));
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(V.dp(thicknessDp));
|
||||
paddingStart=V.dp(paddingStartDp);
|
||||
paddingEnd=V.dp(paddingEndDp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
|
||||
int padLeft=isRTL ? paddingEnd : paddingStart;
|
||||
int padRight=isRTL ? paddingStart : paddingEnd;
|
||||
int totalItems=parent.getAdapter().getItemCount();
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
int pos=parent.getChildAdapterPosition(child);
|
||||
if(pos<totalItems-1){
|
||||
float y=Math.round(child.getY()+child.getHeight()-paint.getStrokeWidth()/2f);
|
||||
paint.setAlpha(Math.round(255f*child.getAlpha()));
|
||||
c.drawLine(padLeft+child.getX(), y, child.getX()+child.getWidth()-padRight, y, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.graphics.Outline;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class OutlineProviders{
|
||||
private static SparseArray<ViewOutlineProvider> roundedRects=new SparseArray<>();
|
||||
|
||||
private OutlineProviders(){
|
||||
//no instance
|
||||
}
|
||||
|
@ -16,4 +21,26 @@ public class OutlineProviders{
|
|||
outline.setAlpha(view.getAlpha());
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewOutlineProvider roundedRect(int dp){
|
||||
ViewOutlineProvider provider=roundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
return provider;
|
||||
provider=new RoundRectOutlineProvider(V.dp(dp));
|
||||
roundedRects.put(dp, provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
private static class RoundRectOutlineProvider extends ViewOutlineProvider{
|
||||
private final int radius;
|
||||
|
||||
private RoundRectOutlineProvider(int radius){
|
||||
this.radius=radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package org.joinmastodon.android.ui;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SimpleViewHolder extends RecyclerView.ViewHolder{
|
||||
public SimpleViewHolder(@NonNull View itemView){
|
||||
super(itemView);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
@ -24,6 +23,7 @@ import org.joinmastodon.android.model.Account;
|
|||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
super(parentID, parentFragment);
|
||||
this.user=user;
|
||||
this.createdAt=createdAt;
|
||||
avaRequest=new UrlImageLoaderRequest(user.avatar);
|
||||
avaRequest=new UrlImageLoaderRequest(user.avatar, V.dp(50), V.dp(50));
|
||||
this.accountID=accountID;
|
||||
parsedName=new SpannableStringBuilder(user.displayName);
|
||||
this.status=status;
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.joinmastodon.android.ui.displayitems;
|
|||
import android.app.Activity;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.StateSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
@ -12,6 +11,7 @@ import org.joinmastodon.android.R;
|
|||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.joinmastodon.android.R;
|
|||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.widget.TextView;
|
|||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
|
|
|
@ -2325,8 +2325,8 @@ public class TabLayout extends HorizontalScrollView {
|
|||
/** A {@link LinearLayout} containing {@link Tab} instances for use with {@link TabLayout}. */
|
||||
public final class TabView extends LinearLayout {
|
||||
private Tab tab;
|
||||
private TextView textView;
|
||||
private ImageView iconView;
|
||||
public TextView textView;
|
||||
public ImageView iconView;
|
||||
@Nullable private View badgeAnchorView;
|
||||
// @Nullable private BadgeDrawable badgeDrawable;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.joinmastodon.android.ui.displayitems;
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spanned;
|
||||
|
@ -12,7 +12,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
|
||||
class CustomEmojiHelper{
|
||||
public class CustomEmojiHelper{
|
||||
public List<List<CustomEmojiSpan>> spans=new ArrayList<>();
|
||||
public List<ImageLoaderRequest> requests=new ArrayList<>();
|
||||
|
|
@ -13,12 +13,14 @@ import android.os.Looper;
|
|||
import android.provider.OpenableColumns;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
|
@ -200,7 +202,7 @@ public class UiUtils{
|
|||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
||||
activity.getString(currentlyBlocked ? R.string.do_block : R.string.do_unblock), ()->{
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
|
||||
new SetAccountBlocked(account.id, !currentlyBlocked)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
|
@ -258,4 +260,39 @@ public class UiUtils{
|
|||
.exec(accountID);
|
||||
});
|
||||
}
|
||||
|
||||
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
||||
if(relationship.blocking){
|
||||
button.setText(R.string.button_blocked);
|
||||
}else if(relationship.muting){
|
||||
button.setText(R.string.button_muted);
|
||||
}else{
|
||||
button.setText(relationship.following ? R.string.button_following : R.string.button_follow);
|
||||
}
|
||||
}
|
||||
|
||||
public static void performAccountAction(Activity activity, Account account, String accountID, Relationship relationship, Button button, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback){
|
||||
if(relationship.blocking){
|
||||
confirmToggleBlockUser(activity, accountID, account, true, resultCallback);
|
||||
}else if(relationship.muting){
|
||||
confirmToggleMuteUser(activity, accountID, account, true, resultCallback);
|
||||
}else{
|
||||
progressCallback.accept(true);
|
||||
new SetAccountFollowed(account.id, !relationship.following)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.accept(result);
|
||||
progressCallback.accept(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
progressCallback.accept(false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.CornerPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.History;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HashtagChartView extends View{
|
||||
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 float[] relativeOffsets=new float[7];
|
||||
|
||||
public HashtagChartView(Context context){
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HashtagChartView(Context context, AttributeSet attrs){
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HashtagChartView(Context context, AttributeSet attrs, int defStyle){
|
||||
super(context, attrs, defStyle);
|
||||
paint.setStrokeWidth(V.dp(1.71f));
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStrokeJoin(Paint.Join.ROUND);
|
||||
}
|
||||
|
||||
public void setData(List<History> data){
|
||||
int max=0;
|
||||
for(History h:data){
|
||||
max=Math.max(h.accounts, max);
|
||||
}
|
||||
if(relativeOffsets.length!=data.size())
|
||||
relativeOffsets=new float[data.size()];
|
||||
int i=0;
|
||||
for(History h:data){
|
||||
relativeOffsets[i]=(float)h.accounts/max;
|
||||
i++;
|
||||
}
|
||||
updatePath();
|
||||
}
|
||||
|
||||
private void updatePath(){
|
||||
if(getWidth()<1)
|
||||
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));
|
||||
for(int i=1;i<relativeOffsets.length;i++){
|
||||
float offset=relativeOffsets[i];
|
||||
x-=step;
|
||||
float y=maxH-maxH*offset+V.dp(1);
|
||||
strokePath.lineTo(x, y);
|
||||
fillPath.lineTo(x, y);
|
||||
}
|
||||
fillPath.lineTo(V.dp(1), getHeight()-V.dp(1));
|
||||
fillPath.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh){
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
updatePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas){
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorAccentLightest));
|
||||
paint.setPathEffect(null);
|
||||
canvas.drawPath(fillPath, paint);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setColor(UiUtils.getThemeColor(getContext(), android.R.attr.colorAccent));
|
||||
paint.setPathEffect(pathEffect);
|
||||
canvas.drawPath(strokePath, paint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?android:textColorPrimary" android:state_selected="true"/>
|
||||
<item android:color="?colorTabInactive"/>
|
||||
</selector>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape>
|
||||
<solid android:color="?android:statusBarColor"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:height="2dp" android:gravity="bottom">
|
||||
<shape>
|
||||
<solid android:color="?colorPollVoted"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?colorSearchField"/>
|
||||
<corners android:radius="6dp"/>
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="14dp"/>
|
||||
<stroke android:width="2dp" android:color="?colorBackgroundLight"/>
|
||||
</shape>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M10 2.75c4.004 0 7.25 3.246 7.25 7.25 0 1.732-0.607 3.322-1.62 4.569l4.9 4.9c0.293 0.294 0.293 0.768 0 1.061-0.266 0.267-0.683 0.29-0.976 0.073L19.47 20.53l-4.901-4.9c-1.247 1.013-2.837 1.62-4.569 1.62-4.004 0-7.25-3.246-7.25-7.25S5.996 2.75 10 2.75zm0 1.5c-3.176 0-5.75 2.574-5.75 5.75s2.574 5.75 5.75 5.75 5.75-2.574 5.75-5.75S13.176 4.25 10 4.25z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<me.grishka.appkit.views.FragmentRootLinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="?android:statusBarColor">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:hint="@string/search_hint"
|
||||
android:textColorHint="?colorSearchHint"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="16dp"
|
||||
android:singleLine="true"
|
||||
android:inputType="textFilter"
|
||||
android:paddingLeft="48dp"
|
||||
android:paddingRight="48dp"
|
||||
android:elevation="0dp"
|
||||
android:background="@drawable/bg_search_field"/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:tint="?colorSearchHint"
|
||||
android:src="@drawable/ic_fluent_search_24_regular"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<org.joinmastodon.android.ui.tabs.TabLayout
|
||||
android:id="@+id/tabbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:tabGravity="start"
|
||||
app:tabMinWidth="120dp"
|
||||
app:tabIndicator="@drawable/mtrl_tabs_default_indicator"
|
||||
app:tabIndicatorAnimationMode="elastic"
|
||||
app:tabIndicatorColor="?android:textColorPrimary"
|
||||
app:tabMode="scrollable"
|
||||
android:background="@drawable/bg_discover_tabs"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</me.grishka.appkit.views.FragmentRootLinearLayout>
|
|
@ -0,0 +1,179 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="wrap_content"
|
||||
android:background="?colorBackgroundLight"
|
||||
android:elevation="2dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="128dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/avatar_border"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="-6dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:background="@drawable/discover_ava_bg"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="-4dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="#f00" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="24dp"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_below="@id/cover"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
tools:text="Eugen"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_alignLeft="@id/name"
|
||||
android:layout_alignRight="@id/name"
|
||||
android:layout_below="@id/name"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="\@Gargron@mastodon.social"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/avatar"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
tools:text="Founder, CEO and lead developer @Mastodon, Germany." />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/posts_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_below="@id/bio"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/posts_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/followers_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_toEndOf="@id/posts_btn"
|
||||
android:layout_alignTop="@id/posts_btn"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/followers_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/followers_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/following_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignTop="@id/posts_btn"
|
||||
android:layout_toEndOf="@id/followers_btn"
|
||||
android:layout_marginStart="12dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
<TextView
|
||||
android:id="@+id/following_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_large"
|
||||
tools:text="123"/>
|
||||
<TextView
|
||||
android:id="@+id/following_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
tools:text="following"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/action_btn_wrap"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignTop="@id/posts_btn"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:padding="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:clipToPadding="false">
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
android:id="@+id/action_btn"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Edit Profile"/>
|
||||
<ProgressBar
|
||||
android:id="@+id/action_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
style="?android:progressBarStyleSmall"
|
||||
android:elevation="10dp"
|
||||
android:outlineProvider="none"
|
||||
android:indeterminateTint="?colorButtonText"
|
||||
android:visibility="gone"/>
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<org.joinmastodon.android.ui.views.HashtagChartView
|
||||
android:id="@+id/chart"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="7dp"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/chart"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="#mastodev"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/chart"
|
||||
android:textAppearance="@style/m3_body_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="over 9000 people talking"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="132dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignBottom="@id/subtitle"
|
||||
android:layout_marginStart="8dp"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="#0f0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:textAppearance="@style/m3_title_small"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Site Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:maxLines="5"
|
||||
android:ellipsize="end"
|
||||
tools:text="Title title title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/title"
|
||||
android:layout_toStartOf="@id/photo"
|
||||
android:textAppearance="@style/m3_label_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
tools:text="Discussed 123 times"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -11,6 +11,10 @@
|
|||
<attr name="secondaryButtonStyle" format="reference"/>
|
||||
<attr name="buttonBackground" format="reference"/>
|
||||
<attr name="colorAccentLight" format="color"/>
|
||||
<attr name="colorSearchField" format="color"/>
|
||||
<attr name="colorSearchHint" format="color"/>
|
||||
<attr name="colorTabInactive" format="color"/>
|
||||
<attr name="colorAccentLightest" format="color"/>
|
||||
|
||||
<declare-styleable name="MaxWidthFrameLayout">
|
||||
<attr name="android:maxWidth" format="dimension"/>
|
||||
|
|
|
@ -6,4 +6,9 @@
|
|||
<item name="profile_posts_with_replies" type="id"/>
|
||||
<item name="profile_media" type="id"/>
|
||||
<item name="profile_about" type="id"/>
|
||||
|
||||
<item name="discover_posts" type="id"/>
|
||||
<item name="discover_hashtags" type="id"/>
|
||||
<item name="discover_news" type="id"/>
|
||||
<item name="discover_users" type="id"/>
|
||||
</resources>
|
|
@ -133,4 +133,16 @@
|
|||
<string name="pause">Pause</string>
|
||||
<string name="log_out">Log out</string>
|
||||
<string name="add_account">Add account</string>
|
||||
<string name="search_hint">Search</string>
|
||||
<string name="hashtags">Hashtags</string>
|
||||
<string name="news">News</string>
|
||||
<string name="for_you">For you</string>
|
||||
<plurals name="x_people_talking">
|
||||
<item quantity="one">%d person is talking</item>
|
||||
<item quantity="other">%d people are talking</item>
|
||||
</plurals>
|
||||
<plurals name="discussed_x_times">
|
||||
<item quantity="one">Discussed %d time</item>
|
||||
<item quantity="other">Discussed %d times</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -28,6 +28,10 @@
|
|||
<item name="colorPollMostVoted">@color/primary_500</item>
|
||||
<item name="colorPollVoted">@color/gray_300</item>
|
||||
<item name="colorAccentLight">@color/primary_600</item>
|
||||
<item name="colorSearchField">@color/gray_200</item>
|
||||
<item name="colorSearchHint">@color/gray_600</item>
|
||||
<item name="colorTabInactive">@color/gray_400</item>
|
||||
<item name="colorAccentLightest">@color/primary_100</item>
|
||||
|
||||
<item name="buttonBackground">@drawable/bg_button_primary_dark_on_light</item>
|
||||
|
||||
|
@ -64,6 +68,12 @@
|
|||
<item name="colorPollVoted">@color/gray_600</item>
|
||||
<item name="colorAccentLight">@color/primary_600</item>
|
||||
|
||||
<!-- TODO dark colors -->
|
||||
<item name="colorSearchField">@color/gray_200</item>
|
||||
<item name="colorSearchHint">@color/gray_600</item>
|
||||
<item name="colorTabInactive">@color/gray_400</item>
|
||||
<item name="colorAccentLightest">@color/primary_100</item>
|
||||
|
||||
<item name="buttonBackground">@drawable/bg_button_primary_light_on_dark</item>
|
||||
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
|
@ -172,6 +182,7 @@
|
|||
<style name="m3_body_large">
|
||||
<item name="android:textSize">16dp</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:lineSpacingExtra">5dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_body_medium">
|
||||
|
@ -183,6 +194,7 @@
|
|||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
<item name="android:textSize">16dp</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:lineSpacingExtra">5dp</item>
|
||||
</style>
|
||||
|
||||
<style name="m3_title_small">
|
||||
|
|
Loading…
Reference in New Issue