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

View File

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M10.733 19.79c0.3 0.286 0.774 0.275 1.06-0.025 0.286-0.3 0.274-0.775-0.026-1.06L5.516 12.75H20.25c0.414 0 0.75-0.336 0.75-0.75 0-0.415-0.336-0.75-0.75-0.75H5.516l6.251-5.955c0.3-0.286 0.312-0.76 0.026-1.06-0.286-0.3-0.76-0.312-1.06-0.026l-7.42 7.067c-0.168 0.16-0.268 0.366-0.3 0.58C3.006 11.901 3 11.95 3 12c0 0.05 0.005 0.098 0.014 0.145 0.031 0.213 0.131 0.418 0.3 0.579l7.419 7.067z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View File

@ -0,0 +1,28 @@
<?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="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/gray_50"
android:elevation="3dp"
android:outlineProvider="background"
android:padding="16dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_label_medium"
android:minHeight="16dp"
android:textAllCaps="true"
tools:text="Field title"/>
<org.joinmastodon.android.ui.views.LinkedTextView
android:id="@+id/value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_body_large"
tools:text="Field value"/>
</LinearLayout>

View File

@ -14,6 +14,7 @@
<color name="gray_50t">#CCF9FAFB</color>
<color name="gray_50">#F9FAFB</color>
<color name="gray_100">#F2F4F7</color>
<color name="gray_200">#E4E7EC</color>
<color name="gray_800">#282C37</color>
<color name="gray_500">#667085</color>

View File

@ -66,4 +66,5 @@
<item quantity="one">%,d post</item>
<item quantity="other">%,d posts</item>
</plurals>
<string name="profile_joined">Joined</string>
</resources>

View File

@ -15,6 +15,7 @@
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar</item>
<item name="android:buttonStyle">@style/Widget.Mastodon.Button</item>
<item name="android:alertDialogTheme">@style/Theme.Mastodon.Dialog.Alert</item>
<item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item>
</style>
<style name="Theme.Mastodon.Toolbar" parent="android:ThemeOverlay.Material.ActionBar">
@ -28,6 +29,10 @@
<item name="android:textColorSecondary">@color/gray_50</item>
<item name="android:drawableTint">@color/gray_50</item>
<item name="android:popupTheme">@style/Theme.Mastodon</item>
<item name="android:titleTextAppearance">@style/m3_title_medium</item>
<item name="android:titleTextColor">@color/gray_50</item>
<item name="android:subtitleTextAppearance">@style/m3_body_medium</item>
<item name="android:subtitleTextColor">@color/gray_50</item>
</style>
<style name="Widget.Mastodon.Button" parent="android:Widget.Material.Button">
@ -44,22 +49,9 @@
<item name="android:dialogPreferredPadding">24dp</item>
<item name="android:windowBackground">@drawable/bg_alert</item>
<item name="android:colorBackground">@color/gray_100</item>
<item name="android:buttonBarStyle">@style/Widget.Mastodon.ButtonBar</item>
<item name="android:buttonBarButtonStyle">@style/Widget.Mastodon.ButtonBarButton</item>
</style>
<style name="Widget.Mastodon.ButtonBar" parent="android:Widget.Material.Light.ButtonBar.AlertDialog">
<!-- <item name="android:layout_marginEnd">4dp</item>-->
<!-- <item name="android:layout_marginStart">12dp</item>-->
<!-- <item name="android:layout_marginTop">20dp</item>-->
<!-- <item name="android:layout_marginBottom">20dp</item>-->
<!-- <item name="android:paddingEnd">4dp</item>-->
<!-- <item name="android:paddingStart">12dp</item>-->
<!-- <item name="android:paddingTop">20dp</item>-->
<!-- <item name="android:paddingBottom">20dp</item>-->
</style>
<style name="Widget.Mastodon.ButtonBarButton" parent="android:Widget.Material.Button.Borderless">
<item name="android:textAllCaps">false</item>
<item name="android:layout_marginEnd">8dp</item>
@ -82,6 +74,11 @@
<item name="android:textColor">@color/text_primary</item>
</style>
<style name="m3_body_medium">
<item name="android:textSize">14dp</item>
<item name="android:textColor">@color/text_primary</item>
</style>
<style name="m3_title_medium">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">16dp</item>
@ -94,6 +91,13 @@
<item name="android:textColor">@color/text_secondary</item>
</style>
<style name="m3_label_medium">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">12dp</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:lineSpacingMultiplier">1.14</item>
</style>
<style name="m3_label_large">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textColor">@color/text_secondary</item>