Link cards
This commit is contained in:
parent
d25b7e67b8
commit
658415fd89
|
@ -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+'\''+
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue