Link previews for detail statuses (#424)
* implement link preview cards on detail statuses * cleanup code
This commit is contained in:
parent
df4dfa7766
commit
5cbc7217ff
14 changed files with 420 additions and 35 deletions
|
@ -1,20 +1,28 @@
|
|||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.util.CustomTabURLSpan;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
@ -23,12 +31,24 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
|||
private TextView reblogs;
|
||||
private TextView favourites;
|
||||
private TextView application;
|
||||
private LinearLayout cardView;
|
||||
private LinearLayout cardInfo;
|
||||
private ImageView cardImage;
|
||||
private TextView cardTitle;
|
||||
private TextView cardDescription;
|
||||
private TextView cardUrl;
|
||||
|
||||
StatusDetailedViewHolder(View view) {
|
||||
super(view);
|
||||
reblogs = view.findViewById(R.id.status_reblogs);
|
||||
favourites = view.findViewById(R.id.status_favourites);
|
||||
application = view.findViewById(R.id.status_application);
|
||||
cardView = view.findViewById(R.id.card_view);
|
||||
cardInfo = view.findViewById(R.id.card_info);
|
||||
cardImage = view.findViewById(R.id.card_image);
|
||||
cardTitle = view.findViewById(R.id.card_title);
|
||||
cardDescription = view.findViewById(R.id.card_description);
|
||||
cardUrl = view.findViewById(R.id.card_link);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,11 +85,68 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
|||
}
|
||||
|
||||
@Override
|
||||
void setupWithStatus(StatusViewData status, final StatusActionListener listener,
|
||||
void setupWithStatus(final StatusViewData status, final StatusActionListener listener,
|
||||
boolean mediaPreviewEnabled) {
|
||||
super.setupWithStatus(status, listener, mediaPreviewEnabled);
|
||||
reblogs.setText(status.getReblogsCount());
|
||||
favourites.setText(status.getFavouritesCount());
|
||||
setApplication(status.getApplication());
|
||||
|
||||
if(status.getAttachments().length == 0 && status.getCard() != null && !TextUtils.isEmpty(status.getCard().url)) {
|
||||
final Card card = status.getCard();
|
||||
cardView.setVisibility(View.VISIBLE);
|
||||
cardTitle.setText(card.title);
|
||||
cardDescription.setText(card.description);
|
||||
|
||||
cardUrl.setText(card.url);
|
||||
|
||||
if(card.width > 0 && card.height > 0 && !TextUtils.isEmpty(card.image)) {
|
||||
cardImage.setVisibility(View.VISIBLE);
|
||||
|
||||
if(card.width > card.height) {
|
||||
cardView.setOrientation(LinearLayout.VERTICAL);
|
||||
cardImage.getLayoutParams().height = cardImage.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.card_image_vertical_height);
|
||||
cardImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
} else {
|
||||
cardView.setOrientation(LinearLayout.HORIZONTAL);
|
||||
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
cardImage.getLayoutParams().width = cardImage.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.card_image_horizontal_width);
|
||||
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
cardView.setClipToOutline(true);
|
||||
}
|
||||
|
||||
Picasso.with(cardImage.getContext())
|
||||
.load(card.image)
|
||||
.fit()
|
||||
.centerCrop()
|
||||
.into(cardImage);
|
||||
|
||||
} else {
|
||||
cardImage.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
cardView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
LinkHelper.openLink(card.url, v.getContext());
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
cardView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
|
@ -37,6 +38,8 @@ public class ThreadAdapter extends RecyclerView.Adapter {
|
|||
private boolean mediaPreviewEnabled;
|
||||
private int detailedStatusPosition;
|
||||
|
||||
private Card detailedStatusCard;
|
||||
|
||||
public ThreadAdapter(StatusActionListener listener) {
|
||||
this.statusActionListener = listener;
|
||||
this.statuses = new ArrayList<>();
|
||||
|
|
96
app/src/main/java/com/keylesspalace/tusky/entity/Card.java
Normal file
96
app/src/main/java/com/keylesspalace/tusky/entity/Card.java
Normal file
|
@ -0,0 +1,96 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.entity;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.keylesspalace.tusky.util.HtmlUtils;
|
||||
|
||||
public class Card implements Parcelable {
|
||||
|
||||
public String url;
|
||||
|
||||
public String title;
|
||||
|
||||
public String description;
|
||||
|
||||
public String image;
|
||||
|
||||
public String type;
|
||||
|
||||
public int width;
|
||||
|
||||
public int height;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return url.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this.url == null) {
|
||||
return this == other;
|
||||
} else if (!(other instanceof Card)) {
|
||||
return false;
|
||||
}
|
||||
Card account = (Card) other;
|
||||
return account.url.equals(this.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(url);
|
||||
dest.writeString(title);
|
||||
dest.writeString(description);
|
||||
dest.writeString(image);
|
||||
dest.writeString(type);
|
||||
dest.writeInt(width);
|
||||
dest.writeInt(height);
|
||||
}
|
||||
|
||||
public Card() {}
|
||||
|
||||
private Card(Parcel in) {
|
||||
url = in.readString();
|
||||
title = in.readString();
|
||||
description = in.readString();
|
||||
image = in.readString();
|
||||
type = in.readString();
|
||||
width = in.readInt();
|
||||
height = in.readInt();
|
||||
}
|
||||
|
||||
public static final Creator<Card> CREATOR = new Creator<Card>() {
|
||||
@Override
|
||||
public Card createFromParcel(Parcel source) {
|
||||
return new Card(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Card[] newArray(int size) {
|
||||
return new Card[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -37,6 +37,7 @@ import android.view.ViewGroup;
|
|||
import com.keylesspalace.tusky.BuildConfig;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.adapter.ThreadAdapter;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.entity.StatusContext;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
|
@ -64,6 +65,7 @@ public class ViewThreadFragment extends SFragment implements
|
|||
private ThreadAdapter adapter;
|
||||
private String thisThreadsStatusId;
|
||||
private TimelineReceiver timelineReceiver;
|
||||
private Card card;
|
||||
|
||||
private int statusIndex = 0;
|
||||
|
||||
|
@ -135,6 +137,7 @@ public class ViewThreadFragment extends SFragment implements
|
|||
public void onRefresh() {
|
||||
sendStatusRequest(thisThreadsStatusId);
|
||||
sendThreadRequest(thisThreadsStatusId);
|
||||
sendCardRequest(thisThreadsStatusId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -327,6 +330,26 @@ public class ViewThreadFragment extends SFragment implements
|
|||
callList.add(call);
|
||||
}
|
||||
|
||||
private void sendCardRequest(final String id) {
|
||||
Call<Card> call = mastodonApi.statusCard(id);
|
||||
call.enqueue(new Callback<Card>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<Card> call, @NonNull Response<Card> response) {
|
||||
if (response.isSuccessful()) {
|
||||
showCard(response.body());
|
||||
} else {
|
||||
onThreadRequestFailure(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<Card> call, @NonNull Throwable t) {
|
||||
onThreadRequestFailure(id);
|
||||
}
|
||||
});
|
||||
callList.add(call);
|
||||
}
|
||||
|
||||
private void onThreadRequestFailure(final String id) {
|
||||
View view = getView();
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
|
@ -356,7 +379,13 @@ public class ViewThreadFragment extends SFragment implements
|
|||
int i = statusIndex;
|
||||
statuses.add(i, status);
|
||||
adapter.setDetailedStatusPosition(i);
|
||||
adapter.addItem(i, statuses.getPairedItem(i));
|
||||
StatusViewData viewData = statuses.getPairedItem(i);
|
||||
if(viewData.getCard() == null && card != null) {
|
||||
viewData = new StatusViewData.Builder(viewData)
|
||||
.setCard(card)
|
||||
.createStatusViewData();
|
||||
}
|
||||
adapter.addItem(i, viewData);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
@ -391,7 +420,13 @@ public class ViewThreadFragment extends SFragment implements
|
|||
// In case we needed to delete everything (which is way easier than deleting
|
||||
// everything except one), re-insert the remaining status here.
|
||||
statuses.add(statusIndex, mainStatus);
|
||||
adapter.addItem(statusIndex, statuses.getPairedItem(statusIndex));
|
||||
StatusViewData viewData = statuses.getPairedItem(statusIndex);
|
||||
if(viewData.getCard() == null && card != null) {
|
||||
viewData = new StatusViewData.Builder(viewData)
|
||||
.setCard(card)
|
||||
.createStatusViewData();
|
||||
}
|
||||
adapter.addItem(statusIndex, viewData);
|
||||
}
|
||||
|
||||
// Insert newly fetched descendants
|
||||
|
@ -410,6 +445,21 @@ public class ViewThreadFragment extends SFragment implements
|
|||
adapter.addAll(descendantsViewData);
|
||||
}
|
||||
|
||||
private void showCard(Card card) {
|
||||
this.card = card;
|
||||
if(statuses.size() != 0) {
|
||||
StatusViewData oldViewData = statuses.getPairedItem(statusIndex);
|
||||
if(oldViewData != null) {
|
||||
StatusViewData newViewData = new StatusViewData.Builder(statuses.getPairedItem(statusIndex))
|
||||
.setCard(card)
|
||||
.createStatusViewData();
|
||||
|
||||
statuses.setPairedItem(statusIndex, newViewData);
|
||||
adapter.setItem(statusIndex, newViewData, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
statuses.clear();
|
||||
adapter.clear();
|
||||
|
|
|
@ -18,6 +18,7 @@ package com.keylesspalace.tusky.network;
|
|||
import com.keylesspalace.tusky.entity.AccessToken;
|
||||
import com.keylesspalace.tusky.entity.Account;
|
||||
import com.keylesspalace.tusky.entity.AppCredentials;
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Media;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.entity.Profile;
|
||||
|
@ -215,4 +216,9 @@ public interface MastodonApi {
|
|||
@Field("code") String code,
|
||||
@Field("grant_type") String grantType
|
||||
);
|
||||
|
||||
@GET("/api/v1/statuses/{id}/card")
|
||||
Call<Card> statusCard(
|
||||
@Path("id") String statusId
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
|
||||
public class CustomTabURLSpan extends URLSpan {
|
||||
public CustomTabURLSpan(String url) {
|
||||
super(url);
|
||||
|
@ -37,27 +29,8 @@ public class CustomTabURLSpan extends URLSpan {
|
|||
};
|
||||
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
public void onClick(View view) {
|
||||
Uri uri = Uri.parse(getURL());
|
||||
Context context = widget.getContext();
|
||||
boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("lightTheme", false);
|
||||
int toolbarColor = ContextCompat.getColor(context, lightTheme ? R.color.custom_tab_toolbar_light : R.color.custom_tab_toolbar_dark);
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(toolbarColor);
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
try {
|
||||
String packageName = CustomTabsHelper.getPackageNameToUse(context);
|
||||
|
||||
//If we cant find a package name, it means theres no browser that supports
|
||||
//Chrome Custom Tabs installed. So, we fallback to the webview
|
||||
if (packageName == null) {
|
||||
super.onClick(widget);
|
||||
} else {
|
||||
customTabsIntent.intent.setPackage(packageName);
|
||||
customTabsIntent.launchUrl(context, uri);
|
||||
}
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString());
|
||||
}
|
||||
LinkHelper.openLinkInCustomTab(uri, view.getContext());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,25 @@
|
|||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Browser;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
|
||||
|
@ -111,4 +121,71 @@ public class LinkHelper {
|
|||
view.setLinksClickable(true);
|
||||
view.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a link, depending on the settings, either in the browser or in a custom tab
|
||||
*
|
||||
* @param url a string containing the url to open
|
||||
* @param context context
|
||||
*/
|
||||
public static void openLink(String url, Context context) {
|
||||
Uri uri = Uri.parse(url);
|
||||
|
||||
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean("customTabs", true);
|
||||
if (useCustomTabs) {
|
||||
openLinkInCustomTab(uri, context);
|
||||
} else {
|
||||
openLinkInBrowser(uri, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* opens a link in the browser via Intent.ACTION_VIEW
|
||||
*
|
||||
* @param uri the uri to open
|
||||
* @param context context
|
||||
*/
|
||||
public static void openLinkInBrowser(Uri uri, Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
||||
try {
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to open a link in a custom tab
|
||||
* falls back to browser if not possible
|
||||
*
|
||||
* @param uri the uri to open
|
||||
* @param context context
|
||||
*/
|
||||
public static void openLinkInCustomTab(Uri uri, Context context) {
|
||||
boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("lightTheme", false);
|
||||
int toolbarColor = ContextCompat.getColor(context, lightTheme ? R.color.custom_tab_toolbar_light : R.color.custom_tab_toolbar_dark);
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(toolbarColor);
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
try {
|
||||
String packageName = CustomTabsHelper.getPackageNameToUse(context);
|
||||
|
||||
//If we cant find a package name, it means theres no browser that supports
|
||||
//Chrome Custom Tabs installed. So, we fallback to the webview
|
||||
if (packageName == null) {
|
||||
openLinkInBrowser(uri, context);
|
||||
} else {
|
||||
customTabsIntent.intent.setPackage(packageName);
|
||||
customTabsIntent.launchUrl(context, uri);
|
||||
}
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.keylesspalace.tusky.viewdata;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.text.Spanned;
|
||||
|
||||
import com.keylesspalace.tusky.entity.Card;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -44,6 +45,8 @@ public final class StatusViewData {
|
|||
private final boolean rebloggingEnabled;
|
||||
private final Status.Application application;
|
||||
private final List<Status.Emoji> emojis;
|
||||
@Nullable
|
||||
private final Card card;
|
||||
|
||||
public StatusViewData(String id, Spanned content, boolean reblogged, boolean favourited,
|
||||
String spoilerText, Status.Visibility visibility, Status.MediaAttachment[] attachments,
|
||||
|
@ -51,7 +54,7 @@ public final class StatusViewData {
|
|||
boolean isShowingSensitiveWarning, String userFullName, String nickname, String avatar,
|
||||
Date createdAt, String reblogsCount, String favouritesCount, String inReplyToId,
|
||||
Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
|
||||
Status.Application application, List<Status.Emoji> emojis) {
|
||||
Status.Application application, List<Status.Emoji> emojis, Card card) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
this.reblogged = reblogged;
|
||||
|
@ -76,6 +79,7 @@ public final class StatusViewData {
|
|||
this.rebloggingEnabled = rebloggingEnabled;
|
||||
this.application = application;
|
||||
this.emojis = emojis;
|
||||
this.card = card;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -179,6 +183,10 @@ public final class StatusViewData {
|
|||
return emojis;
|
||||
}
|
||||
|
||||
public Card getCard() {
|
||||
return card;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String id;
|
||||
private Spanned content;
|
||||
|
@ -204,6 +212,7 @@ public final class StatusViewData {
|
|||
private boolean rebloggingEnabled;
|
||||
private Status.Application application;
|
||||
private List<Status.Emoji> emojis;
|
||||
private Card card;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
@ -233,6 +242,8 @@ public final class StatusViewData {
|
|||
rebloggingEnabled = viewData.rebloggingEnabled;
|
||||
application = viewData.application;
|
||||
emojis = viewData.getEmojis();
|
||||
card = viewData.getCard();
|
||||
|
||||
}
|
||||
|
||||
public Builder setId(String id) {
|
||||
|
@ -355,12 +366,17 @@ public final class StatusViewData {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setCard(Card card) {
|
||||
this.card = card;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StatusViewData createStatusViewData() {
|
||||
if (this.emojis == null) emojis = Collections.emptyList();
|
||||
return new StatusViewData(id, content, reblogged, favourited, spoilerText, visibility,
|
||||
attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
|
||||
isShowingSensitiveContent, userFullName, nickname, avatar, createdAt, reblogsCount,
|
||||
favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application, emojis);
|
||||
favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application, emojis, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
app/src/main/res/drawable/card_frame_dark.xml
Normal file
6
app/src/main/res/drawable/card_frame_dark.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="5dp" />
|
||||
<stroke android:color="@color/text_color_tertiary_dark" android:width="1px" />
|
||||
<padding android:bottom="1px" android:top="1px" android:left="1px" android:right="1px"/>
|
||||
</shape>
|
6
app/src/main/res/drawable/card_frame_light.xml
Normal file
6
app/src/main/res/drawable/card_frame_light.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="5dp" />
|
||||
<stroke android:color="@color/text_color_tertiary_light" android:width="1px" />
|
||||
<padding android:bottom="1px" android:top="1px" android:left="1px" android:right="1px"/>
|
||||
</shape>
|
|
@ -104,11 +104,72 @@
|
|||
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@+id/status_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_toEndOf="@+id/status_avatar"
|
||||
android:layout_toRightOf="@+id/status_avatar"
|
||||
android:background="?attr/card_background"
|
||||
android:clipChildren="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/card_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:background="?attr/card_image_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/card_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingLeft="6dp"
|
||||
android:paddingRight="6dp"
|
||||
android:paddingTop="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/card_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:lines="1"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/card_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:maxLines="2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/card_link"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textColor="?android:textColorTertiary" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/status_media_preview_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/status_content"
|
||||
android:layout_below="@+id/card_view"
|
||||
android:layout_toEndOf="@+id/status_avatar"
|
||||
android:layout_toRightOf="@+id/status_avatar">
|
||||
|
||||
|
|
|
@ -39,5 +39,7 @@
|
|||
<attr name="compose_image_button_tint" format="reference|color" />
|
||||
<attr name="report_status_background_color" format="reference|color" />
|
||||
<attr name="report_status_divider_drawable" format="reference" />
|
||||
<attr name="card_background" format="reference|color" />
|
||||
<attr name="card_image_background" format="reference|color" />
|
||||
|
||||
</resources>
|
|
@ -21,4 +21,8 @@
|
|||
<dimen name="status_left_line_margin">38dp</dimen>
|
||||
<dimen name="text_content_margin">16dp</dimen>
|
||||
<dimen name="status_sensitive_media_button_padding">5dp</dimen>
|
||||
|
||||
<dimen name="card_image_vertical_height">160dp</dimen>
|
||||
<dimen name="card_image_horizontal_width">100dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -80,6 +80,10 @@
|
|||
<item name="material_drawer_selected">@color/color_primary_dark</item>
|
||||
<item name="material_drawer_selected_text">@color/text_color_primary_dark</item>
|
||||
<item name="material_drawer_header_selection_text">@color/text_color_primary_dark</item>
|
||||
|
||||
<item name="card_background">@drawable/card_frame_dark</item>
|
||||
<item name="card_image_background">@color/text_color_tertiary_dark</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.ImageButton.Dark" parent="@style/Widget.AppCompat.Button.Borderless.Colored">
|
||||
|
@ -156,6 +160,10 @@
|
|||
<item name="material_drawer_selected">@color/color_primary_light</item>
|
||||
<item name="material_drawer_selected_text">@color/text_color_primary_light</item>
|
||||
<item name="material_drawer_header_selection_text">@color/text_color_primary_dark</item> <!--Intentionally dark so it can be overlayed over the account's header image.-->
|
||||
|
||||
<item name="card_background">@drawable/card_frame_light</item>
|
||||
<item name="card_image_background">@color/text_color_tertiary_light</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.ImageButton.Light" parent="Widget.AppCompat.Button.Borderless.Colored">
|
||||
|
|
Loading…
Reference in a new issue