Profile about tab

This commit is contained in:
Grishka
2022-02-09 17:53:27 +03:00
parent 90bd7baa94
commit b8e3426a1e
12 changed files with 235 additions and 37 deletions

View File

@@ -0,0 +1,123 @@
package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.views.LinkedTextView;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileAboutFragment extends Fragment{
public UsableRecyclerView list;
private List<AccountField> fields=Collections.emptyList();
private AboutAdapter adapter;
private Paint dividerPaint=new Paint();
public void setFields(List<AccountField> fields){
this.fields=fields;
if(adapter!=null)
adapter.notifyDataSetChanged();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
list=new UsableRecyclerView(getActivity());
list.setId(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter=new AboutAdapter());
int pad=V.dp(16);
list.setPadding(pad, pad, pad, pad);
list.setClipToPadding(false);
dividerPaint.setStyle(Paint.Style.STROKE);
dividerPaint.setStrokeWidth(V.dp(1));
dividerPaint.setColor(getResources().getColor(R.color.gray_200));
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
for(int i=0;i<parent.getChildCount();i++){
View item=parent.getChildAt(i);
int pos=parent.getChildAdapterPosition(item);
if(pos<fields.size()-1){
c.drawLine(item.getLeft(), item.getBottom(), item.getRight(), item.getBottom(), dividerPaint);
}
}
}
});
return list;
}
private class AboutAdapter extends UsableRecyclerView.Adapter<AboutViewHolder>{
public AboutAdapter(){
super(null);
}
@NonNull
@Override
public AboutViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new AboutViewHolder();
}
@Override
public void onBindViewHolder(AboutViewHolder holder, int position){
holder.bind(fields.get(position));
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return fields.size();
}
}
private class AboutViewHolder extends BindableViewHolder<AccountField>{
private TextView title;
private LinkedTextView value;
private ShapeDrawable background=new ShapeDrawable();
public AboutViewHolder(){
super(getActivity(), R.layout.item_profile_about, list);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
background.getPaint().setColor(getResources().getColor(R.color.gray_50));
itemView.setBackground(background);
}
@Override
public void onBind(AccountField item){
title.setText(item.name);
value.setText(item.parsedValue);
boolean first=getAbsoluteAdapterPosition()==0, last=getAbsoluteAdapterPosition()==fields.size()-1;
float radius=V.dp(10);
float[] rad=new float[8];
if(first)
rad[0]=rad[1]=rad[2]=rad[3]=radius;
if(last)
rad[4]=rad[5]=rad[6]=rad[7]=radius;
background.setShape(new RoundRectShape(rad, null, null));
itemView.invalidateOutline();
}
}
}

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.os.Bundle;
import android.util.Log;
@@ -28,6 +27,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
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.drawables.CoverOverlayGradientDrawable;
import org.joinmastodon.android.ui.tabs.TabLayout;
@@ -38,6 +38,12 @@ import org.joinmastodon.android.ui.views.CoverImageView;
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
import org.parceler.Parcels;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -63,16 +69,18 @@ public class ProfileFragment extends LoaderFragment{
private ViewPager2 pager;
private NestedRecyclerScrollView scrollView;
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
private ProfileAboutFragment aboutFragment;
private TabLayout tabbar;
private SwipeRefreshLayout refreshLayout;
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
private Matrix coverMatrix=new Matrix();
private float titleTransY;
private Account account;
private String accountID;
private Relationship relationship;
private int statusBarHeight;
private boolean isOwnProfile;
private ArrayList<AccountField> fields=new ArrayList<>();
public ProfileFragment(){
super(R.layout.loader_fragment_overlay_toolbar);
@@ -123,9 +131,11 @@ public class ProfileFragment extends LoaderFragment{
if(getArguments().containsKey("profileAccount")){
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView();
dataLoaded();
loadRelationship();
if(!isOwnProfile)
loadRelationship();
}
scrollView.setScrollableChildSupplier(this::getScrollableRecyclerView);
@@ -157,6 +167,12 @@ public class ProfileFragment extends LoaderFragment{
}).attach();
cover.setForeground(coverGradient);
cover.setOutlineProvider(new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
outline.setEmpty();
}
});
return sizeWrapper;
}
@@ -184,9 +200,12 @@ public class ProfileFragment extends LoaderFragment{
public void onPageSelected(int position){
if(position==0)
return;
BaseRecyclerFragment<?> page=getFragmentForPage(position);
if(!page.loaded && !page.isDataLoading())
page.loadData();
Fragment _page=getFragmentForPage(position);
if(_page instanceof BaseRecyclerFragment){
BaseRecyclerFragment page=(BaseRecyclerFragment) _page;
if(!page.loaded && !page.isDataLoading())
page.loadData();
}
}
});
return true;
@@ -235,6 +254,22 @@ public class ProfileFragment extends LoaderFragment{
}else{
actionButton.setVisibility(View.GONE);
}
fields.clear();
AccountField joined=new AccountField();
joined.name=getString(R.string.profile_joined);
joined.parsedValue=joined.value=DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDateTime.ofInstant(account.createdAt, ZoneId.systemDefault()));
fields.add(joined);
for(AccountField field:account.fields){
field.parsedValue=HtmlParser.parse(field.value, account.emojis);
fields.add(field);
}
if(aboutFragment!=null){
aboutFragment.setFields(fields);
}
}
private void updateToolbar(){
@@ -334,11 +369,12 @@ public class ProfileFragment extends LoaderFragment{
}
}
private BaseRecyclerFragment<?> getFragmentForPage(int page){
private Fragment getFragmentForPage(int page){
return switch(page){
case 0 -> postsFragment;
case 1 -> postsWithRepliesFragment;
case 2 -> mediaFragment;
case 3 -> aboutFragment;
default -> throw new IllegalStateException();
};
}
@@ -363,6 +399,11 @@ public class ProfileFragment extends LoaderFragment{
case 0 -> postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
case 1 -> postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
case 2 -> mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
case 3 -> {
aboutFragment=new ProfileAboutFragment();
aboutFragment.setFields(fields);
yield aboutFragment;
}
default -> throw new IllegalArgumentException();
};
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
@@ -370,7 +411,7 @@ public class ProfileFragment extends LoaderFragment{
@Override
public int getItemCount(){
return 3;
return 4;
}
@Override

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.model;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
import org.parceler.Transient;
import java.time.Instant;
@@ -25,6 +26,8 @@ public class AccountField extends BaseModel{
*/
public Instant verifiedAt;
public transient CharSequence parsedValue;
@Override
public String toString(){
return "AccountField{"+

View File

@@ -22,7 +22,7 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
public class TextStatusDisplayItem extends StatusDisplayItem implements LinkSpan.OnLinkClickListener{
public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private ImageLoaderRequest[] emojiRequests;
private Fragment parentFragment;
@@ -37,10 +37,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem implements LinkSpan
for(int i=0; i<emojiSpans.length; i++){
emojiRequests[i]=new UrlImageLoaderRequest(emojiSpans[i].emoji.url, emojiSize, emojiSize);
}
LinkSpan[] linkSpans=((Spanned) text).getSpans(0, text.length(), LinkSpan.class);
for(LinkSpan span:linkSpans){
span.setListener(this);
}
}else{
emojiRequests=new ImageLoaderRequest[0];
}
@@ -61,14 +57,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem implements LinkSpan
return emojiRequests[index];
}
@Override
public void onLinkClick(LinkSpan span){
switch(span.getType()){
case URL -> UiUtils.launchWebBrowser(parentFragment.getActivity(), span.getLink());
case HASHTAG, MENTION -> Toast.makeText(parentFragment.getActivity(), "Not implemented yet", Toast.LENGTH_SHORT).show();
}
}
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text;
private CustomEmojiSpan[] emojiSpans;

View File

@@ -323,6 +323,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
params.height=item.getHeight();
wrap.setBackground(listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition()));
if(itemView.isAttachedToWindow()){
reset();
prepareAndStartPlayer();
}
}

View File

@@ -3,6 +3,9 @@ package org.joinmastodon.android.ui.text;
import android.content.Context;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
import android.widget.Toast;
import org.joinmastodon.android.ui.utils.UiUtils;
public class LinkSpan extends CharacterStyle {
@@ -31,8 +34,10 @@ public class LinkSpan extends CharacterStyle {
}
public void onClick(Context context){
if(listener!=null)
listener.onLinkClick(this);
switch(getType()){
case URL -> UiUtils.launchWebBrowser(context, link);
case HASHTAG, MENTION -> Toast.makeText(context, "Not implemented yet", Toast.LENGTH_SHORT).show();
}
}
public String getLink(){

View File

@@ -52,7 +52,7 @@ public class NestedRecyclerScrollView extends CustomScrollView{
private boolean isScrolledToTop(RecyclerView rv) {
final LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager();
return lm.findFirstVisibleItemPosition() == 0
&& lm.findViewByPosition(0).getTop() == 0;
&& lm.findViewByPosition(0).getTop() == rv.getPaddingTop();
}
public void setScrollableChildSupplier(Supplier<RecyclerView> scrollableChildSupplier){