diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt
index 4fd0b5c6..157916db 100644
--- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.kt
@@ -129,7 +129,7 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
}
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars",
- "useBlurhash" -> {
+ "useBlurhash", "showCardsInTimelines" -> {
restartActivitiesOnExit = true
}
"language" -> {
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index 5882b64d..61b6eecc 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -44,6 +44,7 @@ import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.interfaces.LinkListener;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
+import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper;
@@ -230,7 +231,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
mediaPreviewEnabled,
statusDisplayOptions.useAbsoluteTime(),
statusDisplayOptions.showBotOverlay(),
- statusDisplayOptions.useBlurhash()
+ statusDisplayOptions.useBlurhash(),
+ CardViewMode.NONE
);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
index d9acafe2..30baa503 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java
@@ -8,9 +8,11 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -22,14 +24,18 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
import com.google.android.material.button.MaterialButton;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Attachment.Focus;
import com.keylesspalace.tusky.entity.Attachment.MetaData;
+import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
+import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.HtmlUtils;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
@@ -86,6 +92,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private TextView pollDescription;
private Button pollButton;
+ private LinearLayout cardView;
+ private LinearLayout cardInfo;
+ private ImageView cardImage;
+ private TextView cardTitle;
+ private TextView cardDescription;
+ private TextView cardUrl;
private PollAdapter pollAdapter;
private SimpleDateFormat shortSdf;
@@ -143,6 +155,13 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
pollDescription = itemView.findViewById(R.id.status_poll_description);
pollButton = itemView.findViewById(R.id.status_poll_button);
+ cardView = itemView.findViewById(R.id.status_card_view);
+ cardInfo = itemView.findViewById(R.id.card_info);
+ cardImage = itemView.findViewById(R.id.card_image);
+ cardTitle = itemView.findViewById(R.id.card_title);
+ cardDescription = itemView.findViewById(R.id.card_description);
+ cardUrl = itemView.findViewById(R.id.card_link);
+
pollAdapter = new PollAdapter();
pollOptions.setAdapter(pollAdapter);
pollOptions.setLayoutManager(new LinearLayoutManager(pollOptions.getContext()));
@@ -683,6 +702,10 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
hideSensitiveMediaWarning();
}
+ if (cardView != null) {
+ setupCard(status, statusDisplayOptions.cardViewMode());
+ }
+
setupButtons(listener, status.getSenderId());
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
@@ -911,6 +934,80 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
return pollDescription.getContext().getString(R.string.poll_info_format, votesText, pollDurationInfo);
}
+ protected void setupCard(StatusViewData.Concrete status, CardViewMode cardViewMode) {
+ if (cardViewMode != CardViewMode.NONE && status.getAttachments().size() == 0 && status.getCard() != null && !TextUtils.isEmpty(status.getCard().getUrl())) {
+ final Card card = status.getCard();
+ cardView.setVisibility(View.VISIBLE);
+ cardTitle.setText(card.getTitle());
+ if (TextUtils.isEmpty(card.getDescription()) && TextUtils.isEmpty(card.getAuthorName())) {
+ cardDescription.setVisibility(View.GONE);
+ } else {
+ cardDescription.setVisibility(View.VISIBLE);
+ if (TextUtils.isEmpty(card.getDescription())) {
+ cardDescription.setText(card.getAuthorName());
+ } else {
+ cardDescription.setText(card.getDescription());
+ }
+ }
+
+ cardUrl.setText(card.getUrl());
+
+ if (!TextUtils.isEmpty(card.getImage())) {
+
+ int topLeftRadius = 0;
+ int topRightRadius = 0;
+ int bottomRightRadius = 0;
+ int bottomLeftRadius = 0;
+
+ int radius = cardImage.getContext().getResources()
+ .getDimensionPixelSize(R.dimen.card_radius);
+
+ if (card.getWidth() > card.getHeight()) {
+ 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;
+ topLeftRadius = radius;
+ topRightRadius = radius;
+ } 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;
+ topLeftRadius = radius;
+ bottomLeftRadius = radius;
+ }
+
+
+ Glide.with(cardImage)
+ .load(card.getImage())
+ .transform(
+ new CenterCrop(),
+ new GranularRoundedCorners(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius)
+ )
+ .into(cardImage);
+ } 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;
+ cardImage.setImageResource(R.drawable.card_image_placeholder);
+ }
+
+ cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext()));
+ cardView.setClipToOutline(true);
+ } else {
+ cardView.setVisibility(View.GONE);
+ }
+ }
+
private static String formatDuration(double durationInSeconds) {
int seconds = (int) Math.round(durationInSeconds) % 60;
int minutes = (int) durationInSeconds % 3600 / 60;
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
index 06b8aaf6..0d844b91 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusDetailedViewHolder.java
@@ -4,25 +4,18 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.resource.bitmap.CenterCrop;
-import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
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.CardViewMode;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.StatusViewData;
@@ -33,27 +26,13 @@ import java.util.Date;
class StatusDetailedViewHolder extends StatusBaseViewHolder {
private TextView reblogs;
private TextView favourites;
- private LinearLayout cardView;
- private LinearLayout cardInfo;
- private ImageView cardImage;
- private TextView cardTitle;
- private TextView cardDescription;
- private TextView cardUrl;
private View infoDivider;
StatusDetailedViewHolder(View view) {
super(view);
reblogs = view.findViewById(R.id.status_reblogs);
favourites = view.findViewById(R.id.status_favourites);
- 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);
infoDivider = view.findViewById(R.id.status_info_divider);
-
- cardView.setClipToOutline(true);
}
@Override
@@ -127,6 +106,7 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
+ setupCard(status, CardViewMode.FULL_WIDTH); // Always show card for detailed status
if (payloads == null) {
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
@@ -145,82 +125,6 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
content.setOnLongClickListener(longClickListener);
contentWarningDescription.setOnLongClickListener(longClickListener);
-
- if (status.getAttachments().size() == 0 && status.getCard() != null && !TextUtils.isEmpty(status.getCard().getUrl())) {
- final Card card = status.getCard();
- cardView.setVisibility(View.VISIBLE);
- cardTitle.setText(card.getTitle());
- if (TextUtils.isEmpty(card.getDescription()) && TextUtils.isEmpty(card.getAuthorName())) {
- cardDescription.setVisibility(View.GONE);
- } else {
- cardDescription.setVisibility(View.VISIBLE);
- if (TextUtils.isEmpty(card.getDescription())) {
- cardDescription.setText(card.getAuthorName());
- } else {
- cardDescription.setText(card.getDescription());
- }
- }
-
- cardUrl.setText(card.getUrl());
-
- if (!TextUtils.isEmpty(card.getImage())) {
-
- int topLeftRadius = 0;
- int topRightRadius = 0;
- int bottomRightRadius = 0;
- int bottomLeftRadius = 0;
-
- int radius = cardImage.getContext().getResources()
- .getDimensionPixelSize(R.dimen.card_radius);
-
- if (card.getWidth() > card.getHeight()) {
- 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;
- topLeftRadius = radius;
- topRightRadius = radius;
- } 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;
- topLeftRadius = radius;
- bottomLeftRadius = radius;
- }
-
-
- Glide.with(cardImage)
- .load(card.getImage())
- .transform(
- new CenterCrop(),
- new GranularRoundedCorners(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius)
- )
- .into(cardImage);
-
- } 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;
-
- cardImage.setImageResource(R.drawable.card_image_placeholder);
-
- }
-
- cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext()));
-
- } else {
- cardView.setVisibility(View.GONE);
- }
-
setStatusVisibility(status.getVisibility());
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
index 464d2bc6..307ddfc5 100644
--- a/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TimelineAdapter.java
@@ -63,7 +63,8 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
mediaPreviewEnabled,
statusDisplayOptions.useAbsoluteTime(),
statusDisplayOptions.showBotOverlay(),
- statusDisplayOptions.useBlurhash()
+ statusDisplayOptions.useBlurhash(),
+ statusDisplayOptions.cardViewMode()
);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
index fbc34281..65816c18 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt
@@ -36,10 +36,7 @@ import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.fragment.SFragment
import com.keylesspalace.tusky.interfaces.ReselectableFragment
import com.keylesspalace.tusky.interfaces.StatusActionListener
-import com.keylesspalace.tusky.util.NetworkState
-import com.keylesspalace.tusky.util.StatusDisplayOptions
-import com.keylesspalace.tusky.util.ThemeUtils
-import com.keylesspalace.tusky.util.hide
+import com.keylesspalace.tusky.util.*
import kotlinx.android.synthetic.main.fragment_timeline.*
import javax.inject.Inject
@@ -68,7 +65,8 @@ class ConversationsFragment : SFragment(), StatusActionListener, Injectable, Res
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
showBotOverlay = preferences.getBoolean("showBotOverlay", true),
- useBlurhash = preferences.getBoolean("useBlurhash", true)
+ useBlurhash = preferences.getBoolean("useBlurhash", true),
+ cardViewMode = CardViewMode.NONE
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt
index 3e9bd622..f30406ce 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt
@@ -43,10 +43,7 @@ import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Status
-import com.keylesspalace.tusky.util.StatusDisplayOptions
-import com.keylesspalace.tusky.util.ThemeUtils
-import com.keylesspalace.tusky.util.hide
-import com.keylesspalace.tusky.util.show
+import com.keylesspalace.tusky.util.*
import com.keylesspalace.tusky.viewdata.AttachmentViewData
import kotlinx.android.synthetic.main.fragment_report_statuses.*
import javax.inject.Inject
@@ -119,7 +116,8 @@ class ReportStatusesFragment : Fragment(), Injectable, AdapterHandler {
mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true,
useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false),
showBotOverlay = false,
- useBlurhash = preferences.getBoolean("useBlurhash", true)
+ useBlurhash = preferences.getBoolean("useBlurhash", true),
+ cardViewMode = CardViewMode.NONE
)
adapter = StatusesAdapter(statusDisplayOptions,
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
index c384d123..3ef3d524 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt
@@ -51,6 +51,7 @@ import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
import com.keylesspalace.tusky.interfaces.StatusActionListener
+import com.keylesspalace.tusky.util.CardViewMode
import com.keylesspalace.tusky.util.NetworkState
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.viewdata.AttachmentViewData
@@ -79,7 +80,8 @@ class SearchStatusesFragment : SearchFragment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_status_detailed.xml b/app/src/main/res/layout/item_status_detailed.xml
index 0436b060..8e1886f5 100644
--- a/app/src/main/res/layout/item_status_detailed.xml
+++ b/app/src/main/res/layout/item_status_detailed.xml
@@ -131,7 +131,7 @@
tools:text="Status content. Can be pretty long. " />
+ app:layout_constraintTop_toBottomOf="@id/status_card_view">
You don\'t have any drafts.
You don\'t have any scheduled statuses.
Mastodon has a minimum scheduling interval of 5 minutes.
+ Show link previews in timelines
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 6dc1faa9..d2e2bfae 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -72,6 +72,12 @@
android:title="@string/pref_title_show_notifications_filter"
app:singleLineTitle="false" />
+
+