Link card improvements (AND-115)

closes #651
This commit is contained in:
Grishka 2023-11-29 07:51:11 +03:00
parent a2ea8e76fb
commit a200701e4c
12 changed files with 217 additions and 45 deletions

View File

@ -11,6 +11,7 @@ import org.joinmastodon.android.ui.utils.BlurHashDecoder;
import org.joinmastodon.android.ui.utils.BlurHashDrawable;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
@Parcel
@ -34,11 +35,14 @@ public class Card extends BaseModel{
public String embedUrl;
public String blurhash;
public List<History> history;
public Instant publishedAt;
public transient Drawable blurhashPlaceholder;
@Override
public void postprocess() throws ObjectValidationException{
if(type==null)
type=Type.LINK;
super.postprocess();
if(blurhash!=null){
Bitmap placeholder=BlurHashDecoder.decode(blurhash, 16, 16);
@ -64,6 +68,7 @@ public class Card extends BaseModel{
", embedUrl='"+embedUrl+'\''+
", blurhash='"+blurhash+'\''+
", history="+history+
", publishedAt="+publishedAt+
'}';
}

View File

@ -1,6 +1,8 @@
package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
@ -13,6 +15,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
@ -35,7 +38,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
@Override
public Type getType(){
return Type.CARD;
return status.card.type==Card.Type.VIDEO || (status.card.image!=null && status.card.width>status.card.height) ? Type.CARD_LARGE : Type.CARD_COMPACT;
}
@Override
@ -49,36 +52,65 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<LinkCardStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView title, description, domain;
private final TextView title, description, domain, timestamp;
private final ImageView photo;
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
private boolean didClear;
private final View inner;
private final boolean isLarge;
public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_link_card, parent);
public Holder(Context context, ViewGroup parent, boolean isLarge){
super(context, isLarge ? R.layout.display_item_link_card : R.layout.display_item_link_card_compact, parent);
this.isLarge=isLarge;
title=findViewById(R.id.title);
description=findViewById(R.id.description);
domain=findViewById(R.id.domain);
timestamp=findViewById(R.id.timestamp);
photo=findViewById(R.id.photo);
findViewById(R.id.inner).setOnClickListener(this::onClick);
inner=findViewById(R.id.inner);
inner.setOnClickListener(this::onClick);
inner.setOutlineProvider(OutlineProviders.roundedRect(12));
inner.setClipToOutline(true);
}
@SuppressLint("SetTextI18n")
@Override
public void onBind(LinkCardStatusDisplayItem item){
Card card=item.status.card;
title.setText(card.title);
description.setText(card.description);
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
domain.setText(Uri.parse(card.url).getHost());
if(description!=null){
description.setText(card.description);
description.setVisibility(TextUtils.isEmpty(card.description) ? View.GONE : View.VISIBLE);
}
String cardDomain=Uri.parse(card.url).getHost();
if(isLarge && !TextUtils.isEmpty(card.authorName)){
domain.setText(itemView.getContext().getString(R.string.article_by_author, card.authorName)+" · "+cardDomain);
}else{
domain.setText(cardDomain);
}
if(card.publishedAt!=null){
timestamp.setVisibility(View.VISIBLE);
timestamp.setText(" · "+UiUtils.formatRelativeTimestamp(itemView.getContext(), card.publishedAt));
}else{
timestamp.setVisibility(View.GONE);
}
photo.setImageDrawable(null);
if(item.imgRequest!=null){
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
photo.setBackground(null);
photo.setImageTintList(null);
crossfadeDrawable.setSize(card.width, card.height);
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
crossfadeDrawable.setCrossfadeAlpha(0f);
photo.setImageDrawable(null);
photo.setImageDrawable(crossfadeDrawable);
didClear=false;
}else{
photo.setBackgroundColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3SurfaceVariant));
photo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3Outline)));
photo.setScaleType(ImageView.ScaleType.CENTER);
photo.setImageResource(R.drawable.ic_feed_48px);
}
}

View File

@ -68,7 +68,8 @@ public abstract class StatusDisplayItem{
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
case CARD_LARGE -> new LinkCardStatusDisplayItem.Holder(activity, parent, true);
case CARD_COMPACT -> new LinkCardStatusDisplayItem.Holder(activity, parent, false);
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
case ACCOUNT -> new AccountStatusDisplayItem.Holder(new AccountViewHolder(parentFragment, parent, null));
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
@ -208,7 +209,8 @@ public abstract class StatusDisplayItem{
AUDIO,
POLL_OPTION,
POLL_FOOTER,
CARD,
CARD_LARGE,
CARD_COMPACT,
FOOTER,
ACCOUNT,
HASHTAG,

View File

@ -87,7 +87,7 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
StatusDisplayItem.Type type=sdi.getItem().getType();
if(type==StatusDisplayItem.Type.CARD || type==StatusDisplayItem.Type.MEDIA_GRID)
if(type==StatusDisplayItem.Type.CARD_LARGE || type==StatusDisplayItem.Type.MEDIA_GRID)
outRect.left=outRect.right=V.dp(16);
else
outRect.left=outRect.right=V.dp(8);

View File

@ -1,11 +1,14 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ImageView;
import org.joinmastodon.android.R;
public class FixedAspectRatioImageView extends ImageView{
private float aspectRatio=1;
private float aspectRatio;
private boolean useHeight;
public FixedAspectRatioImageView(Context context){
@ -18,6 +21,10 @@ public class FixedAspectRatioImageView extends ImageView{
public FixedAspectRatioImageView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FixedAspectRatioImageView);
aspectRatio=ta.getFloat(R.styleable.FixedAspectRatioImageView_aspectRatio, 1);
useHeight=ta.getBoolean(R.styleable.FixedAspectRatioImageView_useHeight, false);
ta.recycle();
}
@Override

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/m3_on_surface_overlay">
<item android:id="@android:id/mask">
<shape>
<corners android:radius="11dp"/>
<solid android:color="#000"/>
</shape>
</item>
<item>
<shape>
<corners android:radius="11dp"/>
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
</shape>
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="@android:color/white"
android:pathData="M9,42Q7.8,42 6.9,41.1Q6,40.2 6,39V9Q6,7.8 6.9,6.9Q7.8,6 9,6H32.1L42,15.9V39Q42,40.2 41.1,41.1Q40.2,42 39,42ZM9,39H39Q39,39 39,39Q39,39 39,39V17.55H30.45V9H9Q9,9 9,9Q9,9 9,9V39Q9,39 9,39Q9,39 9,39ZM13.95,33.45H34.05V30.45H13.95ZM13.95,17.55H24V14.55H13.95ZM13.95,25.5H34.05V22.5H13.95ZM9,9V17.55V9V17.55V39Q9,39 9,39Q9,39 9,39Q9,39 9,39Q9,39 9,39V9Q9,9 9,9Q9,9 9,9Z"/>
</vector>

View File

@ -3,54 +3,75 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="8dp">
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout
<LinearLayout
android:id="@+id/inner"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:foreground="?android:selectableItemBackground"
android:orientation="vertical"
android:foreground="@drawable/fg_link_card"
android:padding="1dp"
android:maxWidth="400dp">
<ImageView
<org.joinmastodon.android.ui.views.FixedAspectRatioImageView
android:id="@+id/photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:importantForAccessibility="no"
app:aspectRatio="1.7777777778"
tools:src="#0f0"/>
<LinearLayout
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:padding="8dp"
android:orientation="vertical"
android:background="@drawable/window_bg_alpha95">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_body_large"
android:singleLine="true"
android:ellipsize="end"
android:textColor="?colorM3OnSurface"
tools:text="Link title"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurface"
tools:text="Link description"/>
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/m3_title_medium"
android:maxLines="3"
android:ellipsize="end"
android:textColor="?colorM3OnSurface"
tools:text="Link title"/>
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurface"
tools:text="Link description"/>
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp">
<TextView
android:id="@+id/domain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="example.com"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="example.com"/>
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="8dp">
<RelativeLayout
android:id="@+id/inner"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:orientation="vertical"
android:foreground="@drawable/fg_link_card"
android:padding="1dp"
android:maxWidth="400dp">
<ImageView
android:id="@+id/photo"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_alignParentEnd="true"
android:scaleType="centerCrop"
android:importantForAccessibility="no"
tools:src="#0f0"/>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_toStartOf="@id/photo"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:textAppearance="@style/m3_title_medium"
android:maxLines="3"
android:ellipsize="end"
android:textColor="?colorM3OnSurface"
tools:text="Link title"/>
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@id/photo"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp">
<TextView
android:id="@+id/domain"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="example.com"/>
<TextView
android:id="@+id/timestamp"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:textAppearance="@style/m3_body_medium"
android:textColor="?colorM3OnSurfaceVariant"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="example.com"/>
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>
</RelativeLayout>
</FrameLayout>

View File

@ -54,4 +54,9 @@
<attr name="android:verticalGap" format="dimension"/>
<attr name="android:horizontalGap" format="dimension"/>
</declare-styleable>
<declare-styleable name="FixedAspectRatioImageView">
<attr name="aspectRatio" format="float"/>
<attr name="useHeight" format="boolean"/>
</declare-styleable>
</resources>

View File

@ -665,4 +665,5 @@
<string name="app_version_copied">Version number copied to clipboard</string>
<string name="onboarding_recommendations_intro">You curate your own home feed.The more people you follow, the more active and interesting it will be.</string>
<string name="onboarding_recommendations_title">Personalize your home feed</string>
<string name="article_by_author">By %s</string>
</resources>

View File

@ -375,6 +375,7 @@
<style name="m3_body_medium">
<item name="android:textSize">14dp</item>
<item name="android:lineSpacingExtra">4dp</item>
<item name="android:lineHeight">20dp</item>
</style>
<style name="m3_body_small">
@ -386,6 +387,7 @@
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">16dp</item>
<item name="android:lineSpacingExtra">5dp</item>
<item name="android:lineHeight">24dp</item>
</style>
<style name="m3_title_small">