Link cards

This commit is contained in:
Grishka 2022-02-22 07:17:16 +03:00
parent d25b7e67b8
commit 658415fd89
10 changed files with 223 additions and 3 deletions

View File

@ -16,6 +16,8 @@ public class Card extends BaseModel{
@RequiredField @RequiredField
public String url; public String url;
@RequiredField @RequiredField
public String title;
@RequiredField
public String description; public String description;
@RequiredField @RequiredField
public Type type; public Type type;
@ -46,6 +48,7 @@ public class Card extends BaseModel{
public String toString(){ public String toString(){
return "Card{"+ return "Card{"+
"url='"+url+'\''+ "url='"+url+'\''+
", title='"+title+'\''+
", description='"+description+'\''+ ", description='"+description+'\''+
", type="+type+ ", type="+type+
", authorName='"+authorName+'\''+ ", authorName='"+authorName+'\''+

View File

@ -0,0 +1,101 @@
package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
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.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
public class LinkCardStatusDisplayItem extends StatusDisplayItem{
private final Status status;
private final UrlImageLoaderRequest imgRequest;
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment);
this.status=status;
if(status.card.image!=null)
imgRequest=new UrlImageLoaderRequest(status.card.image, 1000, 1000);
else
imgRequest=null;
}
@Override
public Type getType(){
return Type.CARD;
}
@Override
public int getImageCount(){
return imgRequest==null ? 0 : 1;
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return imgRequest;
}
public static class Holder extends StatusDisplayItem.Holder<LinkCardStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView title, description, domain;
private final ImageView photo;
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
private boolean didClear;
public Holder(Context context, ViewGroup parent){
super(context, R.layout.display_item_link_card, parent);
title=findViewById(R.id.title);
description=findViewById(R.id.description);
domain=findViewById(R.id.domain);
photo=findViewById(R.id.photo);
findViewById(R.id.inner).setOnClickListener(this::onClick);
}
@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());
photo.setImageDrawable(null);
if(item.imgRequest!=null){
crossfadeDrawable.setSize(card.width, card.height);
crossfadeDrawable.setBlurhashDrawable(card.blurhashPlaceholder);
crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f);
photo.setImageDrawable(crossfadeDrawable);
didClear=false;
}
}
@Override
public void setImage(int index, Drawable drawable){
crossfadeDrawable.setImageDrawable(drawable);
if(didClear && item.status.spoilerRevealed)
crossfadeDrawable.animateAlpha(0f);
}
@Override
public void clearImage(int index){
crossfadeDrawable.setCrossfadeAlpha(1f);
didClear=true;
}
private void onClick(View v){
UiUtils.launchWebBrowser(itemView.getContext(), item.status.card.url);
}
}
}

View File

@ -55,6 +55,7 @@ public abstract class StatusDisplayItem{
case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent); case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent);
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent); case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent); case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent); case FOOTER -> new FooterStatusDisplayItem.Holder(activity, parent);
default -> throw new UnsupportedOperationException(); default -> throw new UnsupportedOperationException();
}; };
@ -96,6 +97,9 @@ public abstract class StatusDisplayItem{
if(statusForContent.poll!=null){ if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, items); buildPollItems(parentID, fragment, statusForContent.poll, items);
} }
if(statusForContent.card!=null){
items.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
}
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
return items; return items;
} }

View File

@ -0,0 +1,43 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import org.joinmastodon.android.R;
public class MaxWidthFrameLayout extends FrameLayout{
private int maxWidth;
public MaxWidthFrameLayout(Context context){
this(context, null);
}
public MaxWidthFrameLayout(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public MaxWidthFrameLayout(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.MaxWidthFrameLayout);
maxWidth=ta.getDimensionPixelSize(R.styleable.MaxWidthFrameLayout_android_maxWidth, Integer.MAX_VALUE);
ta.recycle();
}
public int getMaxWidth(){
return maxWidth;
}
public void setMaxWidth(int maxWidth){
this.maxWidth=maxWidth;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
if(MeasureSpec.getSize(widthMeasureSpec)>maxWidth){
widthMeasureSpec=maxWidth | MeasureSpec.getMode(widthMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

View File

@ -43,7 +43,7 @@ public class TabBar extends LinearLayout{
} }
private void onChildClick(View v){ private void onChildClick(View v){
listener.accept(selectedTabID); listener.accept(v.getId());
if(v.getId()==selectedTabID) if(v.getId()==selectedTabID)
return; return;
findViewById(selectedTabID).setSelected(false); findViewById(selectedTabID).setSelected(false);

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorWindowBackground" android:alpha="0.95"/>
</selector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/window_bg_alpha95"/>
</shape>

View File

@ -0,0 +1,54 @@
<?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">
<org.joinmastodon.android.ui.views.MaxWidthFrameLayout
android:id="@+id/inner"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_gravity="center_horizontal"
android:foreground="?android:selectableItemBackground"
android:maxWidth="400dp">
<ImageView
android:id="@+id/photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="#0f0"/>
<LinearLayout
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"
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"
tools:text="Link description"/>
<TextView
android:id="@+id/domain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/m3_body_medium"
android:textColor="?android:textColorSecondary"
tools:text="example.com"/>
</LinearLayout>
</org.joinmastodon.android.ui.views.MaxWidthFrameLayout>
</FrameLayout>

View File

@ -7,5 +7,10 @@
<attr name="colorDarkIcon" format="color"/> <attr name="colorDarkIcon" format="color"/>
<attr name="colorPollMostVoted" format="color"/> <attr name="colorPollMostVoted" format="color"/>
<attr name="colorPollVoted" format="color"/> <attr name="colorPollVoted" format="color"/>
<attr name="colorWindowBackground" format="color"/>
<attr name="secondaryButtonStyle" format="reference"/> <attr name="secondaryButtonStyle" format="reference"/>
<declare-styleable name="MaxWidthFrameLayout">
<attr name="android:maxWidth" format="dimension"/>
</declare-styleable>
</resources> </resources>

View File

@ -6,6 +6,7 @@
<item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item> <item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item>
<item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item> <item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item>
<item name="android:splitMotionEvents">false</item> <item name="android:splitMotionEvents">false</item>
<item name="android:windowBackground">?colorWindowBackground</item>
<item name="android:buttonStyle">@style/Widget.Mastodon.Button.Primary_DarkOnLight</item> <item name="android:buttonStyle">@style/Widget.Mastodon.Button.Primary_DarkOnLight</item>
<item name="secondaryButtonStyle">@style/Widget.Mastodon.Button.Secondary_DarkOnLight</item> <item name="secondaryButtonStyle">@style/Widget.Mastodon.Button.Secondary_DarkOnLight</item>
@ -19,7 +20,7 @@
<item name="colorBackgroundLight">@color/gray_50</item> <item name="colorBackgroundLight">@color/gray_50</item>
<item name="colorBackgroundLightest">@color/gray_25</item> <item name="colorBackgroundLightest">@color/gray_25</item>
<item name="colorDarkIcon">@color/gray_900</item> <item name="colorDarkIcon">@color/gray_900</item>
<item name="android:windowBackground">@color/white</item> <item name="colorWindowBackground">@color/white</item>
<item name="android:statusBarColor">@color/actionbar_bg</item> <item name="android:statusBarColor">@color/actionbar_bg</item>
<item name="android:navigationBarColor">@color/navigation_bar_bg</item> <item name="android:navigationBarColor">@color/navigation_bar_bg</item>
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar</item> <item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar</item>
@ -37,6 +38,7 @@
<item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item> <item name="android:enforceStatusBarContrast" tools:ignore="NewApi">false</item>
<item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item> <item name="appkitBackDrawable">@drawable/ic_fluent_arrow_left_24_regular</item>
<item name="android:splitMotionEvents">false</item> <item name="android:splitMotionEvents">false</item>
<item name="android:windowBackground">?colorWindowBackground</item>
<item name="android:buttonStyle">@style/Widget.Mastodon.Button.Primary_LightOnDark</item> <item name="android:buttonStyle">@style/Widget.Mastodon.Button.Primary_LightOnDark</item>
<item name="secondaryButtonStyle">@style/Widget.Mastodon.Button.Secondary_LightOnDark</item> <item name="secondaryButtonStyle">@style/Widget.Mastodon.Button.Secondary_LightOnDark</item>
@ -50,7 +52,7 @@
<item name="colorBackgroundLight">@color/gray_700</item> <item name="colorBackgroundLight">@color/gray_700</item>
<item name="colorBackgroundLightest">@color/gray_700</item> <item name="colorBackgroundLightest">@color/gray_700</item>
<item name="colorDarkIcon">@color/gray_25</item> <item name="colorDarkIcon">@color/gray_25</item>
<item name="android:windowBackground">@color/gray_800</item> <item name="colorWindowBackground">@color/gray_800</item>
<item name="android:statusBarColor">@color/actionbar_bg_dark</item> <item name="android:statusBarColor">@color/actionbar_bg_dark</item>
<item name="android:navigationBarColor">@color/actionbar_bg_dark</item> <item name="android:navigationBarColor">@color/actionbar_bg_dark</item>
<item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar.Dark</item> <item name="android:actionBarTheme">@style/Theme.Mastodon.Toolbar.Dark</item>