refactor: Extract PreviewCard display code to PreviewCardView (#184)

This commit is contained in:
Nik Clayton 2023-10-19 12:54:58 +02:00 committed by GitHub
parent 3157f8d946
commit d39eb3b642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 322 additions and 544 deletions

View File

@ -773,7 +773,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="778"
line="780"
column="5"/>
</issue>
@ -938,7 +938,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/announcements/AnnouncementAdapter.kt"
line="133"
line="162"
column="9"/>
</issue>
@ -2093,7 +2093,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="766"
line="768"
column="13"/>
</issue>
@ -2104,7 +2104,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="800"
line="802"
column="13"/>
</issue>
@ -3787,7 +3787,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
line="232"
line="217"
column="25"/>
</issue>
@ -3798,7 +3798,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
line="320"
line="305"
column="34"/>
</issue>
@ -3809,7 +3809,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
line="333"
line="318"
column="13"/>
</issue>
@ -3820,7 +3820,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewImageFragment.kt"
line="340"
line="325"
column="17"/>
</issue>
@ -3886,7 +3886,7 @@
errorLine2=" ~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewVideoFragment.kt"
line="145"
line="140"
column="32"/>
</issue>
@ -3897,7 +3897,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/fragment/ViewVideoFragment.kt"
line="226"
line="221"
column="21"/>
</issue>
@ -4260,17 +4260,6 @@
column="6"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_announcement.xml"
line="7"
column="6"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
@ -4766,39 +4755,6 @@
column="6"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="192"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="202"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="212"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
@ -4806,7 +4762,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="287"
line="233"
column="6"/>
</issue>
@ -4817,7 +4773,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="315"
line="261"
column="6"/>
</issue>
@ -4828,7 +4784,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="343"
line="289"
column="6"/>
</issue>
@ -4854,39 +4810,6 @@
column="6"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="173"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="184"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
errorLine1=" &lt;TextView"
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="195"
column="14"/>
</issue>
<issue
id="SelectableText"
message="Consider making the text value selectable by specifying `android:textIsSelectable=&quot;true&quot;`"
@ -4894,7 +4817,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="234"
line="180"
column="6"/>
</issue>
@ -4905,7 +4828,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="263"
line="209"
column="6"/>
</issue>
@ -4916,7 +4839,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/item_status_detailed.xml"
line="277"
line="223"
column="6"/>
</issue>
@ -5202,7 +5125,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/layout/item_status.xml"
line="277"
line="223"
column="9"/>
</issue>
@ -5316,17 +5239,6 @@
column="5"/>
</issue>
<issue
id="SetTextI18n"
message="Do not concatenate text displayed with `setText`. Use resource string with placeholders."
errorLine1=" this.text = &quot;${reaction.name} ${reaction.count}&quot;"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/pachli/components/announcements/AnnouncementAdapter.kt"
line="91"
column="37"/>
</issue>
<issue
id="HardcodedText"
message="Hardcoded string &quot;Filter: MyFilter&quot;, should use `@string` resource"

View File

@ -27,12 +27,8 @@ import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.ShapeAppearanceModel;
import java.text.NumberFormat;
import java.util.Collections;
@ -50,6 +46,7 @@ import app.pachli.entity.Filter;
import app.pachli.entity.FilterResult;
import app.pachli.entity.HashTag;
import app.pachli.entity.Poll;
import app.pachli.entity.PreviewCardKind;
import app.pachli.entity.Status;
import app.pachli.interfaces.StatusActionListener;
import app.pachli.util.AbsoluteTimeFormatter;
@ -66,6 +63,7 @@ import app.pachli.util.TouchDelegateHelper;
import app.pachli.view.MediaPreviewImageView;
import app.pachli.view.MediaPreviewLayout;
import app.pachli.view.PollView;
import app.pachli.view.PreviewCardView;
import app.pachli.viewdata.PollViewData;
import app.pachli.viewdata.StatusViewData;
import at.connyduck.sparkbutton.SparkButton;
@ -105,12 +103,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
@NonNull
private final PollView pollView;
private final LinearLayout cardView;
private final LinearLayout cardInfo;
private final ShapeableImageView cardImage;
private final TextView cardTitle;
private final TextView cardDescription;
private final TextView cardUrl;
private final PreviewCardView cardView;
protected final LinearLayout filteredPlaceholder;
protected final TextView filteredPlaceholderLabel;
protected final Button filteredPlaceholderShowButton;
@ -160,11 +153,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
pollView = itemView.findViewById(R.id.status_poll);
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);
filteredPlaceholder = itemView.findViewById(R.id.status_filtered_placeholder);
filteredPlaceholderLabel = itemView.findViewById(R.id.status_filter_label);
@ -990,9 +978,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
@NonNull final StatusDisplayOptions statusDisplayOptions,
@NonNull final StatusActionListener listener
) {
if (cardView == null) {
return;
}
if (cardView == null) return;
final Status actionable = status.getActionable();
final Card card = actionable.getCard();
@ -1006,110 +992,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
(!status.isCollapsible() || !status.isCollapsed())) {
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());
PreviewCardView.OnClickListener cardListener = (PreviewCardView.Target target) -> {
if (card.getKind().equals(PreviewCardKind.PHOTO) && !TextUtils.isEmpty(card.getEmbedUrl())) {
cardView.getContext().startActivity(ViewMediaActivity.newSingleImageIntent(cardView.getContext(), card.getEmbedUrl()));
} else {
cardDescription.setText(card.getDescription());
listener.onViewUrl(card.getUrl());
}
}
};
cardUrl.setText(card.getUrl());
// Statuses from other activitypub sources can be marked sensitive even if there's no media,
// so let's blur the preview in that case
// If media previews are disabled, show placeholder for cards as well
if (statusDisplayOptions.mediaPreviewEnabled() && !actionable.getSensitive() && !TextUtils.isEmpty(card.getImage())) {
int radius = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_radius);
ShapeAppearanceModel.Builder cardImageShape = ShapeAppearanceModel.builder();
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;
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius);
cardImageShape.setTopRightCorner(CornerFamily.ROUNDED, 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;
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius);
cardImageShape.setBottomLeftCorner(CornerFamily.ROUNDED, radius);
}
cardImage.setShapeAppearanceModel(cardImageShape.build());
cardImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
RequestBuilder<Drawable> builder = Glide.with(cardImage.getContext())
.load(card.getImage())
.dontTransform();
if (statusDisplayOptions.useBlurhash() && !TextUtils.isEmpty(card.getBlurhash())) {
builder = builder.placeholder(decodeBlurHash(card.getBlurhash()));
}
builder.into(cardImage);
} else if (statusDisplayOptions.useBlurhash() && !TextUtils.isEmpty(card.getBlurhash())) {
int radius = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_radius);
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;
ShapeAppearanceModel cardImageShape = ShapeAppearanceModel.builder()
.setTopLeftCorner(CornerFamily.ROUNDED, radius)
.setBottomLeftCorner(CornerFamily.ROUNDED, radius)
.build();
cardImage.setShapeAppearanceModel(cardImageShape);
cardImage.setScaleType(ImageView.ScaleType.CENTER_CROP);
Glide.with(cardImage.getContext())
.load(decodeBlurHash(card.getBlurhash()))
.dontTransform()
.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.setShapeAppearanceModel(new ShapeAppearanceModel());
cardImage.setScaleType(ImageView.ScaleType.CENTER);
Glide.with(cardImage.getContext())
.load(R.drawable.card_image_placeholder)
.into(cardImage);
}
View.OnClickListener visitLink = v -> listener.onViewUrl(card.getUrl());
cardView.setOnClickListener(visitLink);
// View embedded photos in our image viewer instead of opening the browser
cardImage.setOnClickListener(card.getType().equals(Card.TYPE_PHOTO) && !TextUtils.isEmpty(card.getEmbedUrl()) ?
v -> cardView.getContext().startActivity(ViewMediaActivity.newSingleImageIntent(cardView.getContext(), card.getEmbedUrl())) :
visitLink);
cardView.setClipToOutline(true);
cardView.bind(card, actionable.getSensitive(), statusDisplayOptions, cardListener);
} else {
cardView.setVisibility(View.GONE);
}

View File

@ -17,119 +17,18 @@
package app.pachli.components.trending
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.ImageView.ScaleType
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import app.pachli.R
import app.pachli.databinding.ItemTrendingLinkBinding
import app.pachli.entity.TrendsLink
import app.pachli.util.StatusDisplayOptions
import app.pachli.util.decodeBlurHash
import app.pachli.util.hide
import com.bumptech.glide.Glide
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
class TrendingLinkViewHolder(
private val binding: ItemTrendingLinkBinding,
private val onClick: (String) -> Unit,
) : RecyclerView.ViewHolder(binding.root) {
private var link: TrendsLink? = null
private val radius =
binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_radius).toFloat()
init {
itemView.setOnClickListener { link?.let { onClick(it.url) } }
}
fun bind(link: TrendsLink, statusDisplayOptions: StatusDisplayOptions) {
this.link = link
// TODO: This is very similar to the code in StatusBaseViewHolder.setupCard().
// Can a "PreviewCardView" type be created to encapsulate all this?
binding.cardTitle.text = link.title
when {
link.description.isNotBlank() -> link.description
link.authorName.isNotBlank() -> link.authorName
else -> null
}?.let { binding.cardDescription.text = it } ?: binding.cardDescription.hide()
binding.cardLink.text = link.url
val cardImageShape = ShapeAppearanceModel.Builder()
if (statusDisplayOptions.mediaPreviewEnabled && !link.image.isNullOrBlank()) {
if (link.width > link.height) {
binding.statusCardView.orientation = LinearLayout.VERTICAL
binding.cardImage.layoutParams.height =
binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_vertical_height)
binding.cardImage.layoutParams.width = MATCH_PARENT
binding.cardInfo.layoutParams.height = MATCH_PARENT
binding.cardInfo.layoutParams.width = WRAP_CONTENT
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius)
cardImageShape.setTopRightCorner(CornerFamily.ROUNDED, radius)
} else {
binding.statusCardView.orientation = LinearLayout.HORIZONTAL
binding.cardImage.layoutParams.height = MATCH_PARENT
binding.cardImage.layoutParams.width =
binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width)
binding.cardInfo.layoutParams.height = WRAP_CONTENT
binding.cardInfo.layoutParams.width = MATCH_PARENT
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius)
cardImageShape.setBottomLeftCorner(CornerFamily.ROUNDED, radius)
}
binding.cardImage.shapeAppearanceModel = cardImageShape.build()
binding.cardImage.scaleType = ScaleType.CENTER_CROP
val builder = Glide.with(binding.cardImage.context)
.load(link.image)
.dontTransform()
if (statusDisplayOptions.useBlurhash && !link.blurhash.isNullOrBlank()) {
builder
.placeholder(decodeBlurHash(binding.cardImage.context, link.blurhash))
.into(binding.cardImage)
} else {
builder.into(binding.cardImage)
}
} else if (statusDisplayOptions.useBlurhash && !link.blurhash.isNullOrBlank()) {
binding.statusCardView.orientation = LinearLayout.HORIZONTAL
binding.cardImage.layoutParams.height = MATCH_PARENT
binding.cardImage.layoutParams.width =
binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width)
binding.cardInfo.layoutParams.height = WRAP_CONTENT
binding.cardInfo.layoutParams.width = MATCH_PARENT
cardImageShape
.setTopLeftCorner(CornerFamily.ROUNDED, radius)
.setBottomLeftCorner(CornerFamily.ROUNDED, radius)
binding.cardImage.shapeAppearanceModel = cardImageShape.build()
binding.cardImage.scaleType = ScaleType.CENTER_CROP
Glide.with(binding.cardImage.context)
.load(decodeBlurHash(binding.cardImage.context, link.blurhash))
.dontTransform()
.into(binding.cardImage)
} else {
binding.statusCardView.orientation = LinearLayout.HORIZONTAL
binding.cardImage.layoutParams.height = MATCH_PARENT
binding.cardImage.layoutParams.width =
binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width)
binding.cardInfo.layoutParams.height = WRAP_CONTENT
binding.cardInfo.layoutParams.width = MATCH_PARENT
binding.cardImage.shapeAppearanceModel = ShapeAppearanceModel()
binding.cardImage.scaleType = ScaleType.CENTER
Glide.with(binding.cardImage.context)
.load(R.drawable.card_image_placeholder)
.into(binding.cardImage)
binding.statusCardView.bind(link, sensitive = false, statusDisplayOptions) {
onClick(link.url)
}
binding.statusCardView.clipToOutline = true
}
}

View File

@ -18,17 +18,21 @@ package app.pachli.entity
import com.google.gson.annotations.SerializedName
data class Card(
val url: String,
val title: String,
val description: String,
@SerializedName("author_name") val authorName: String,
val image: String,
val type: String,
val width: Int,
val height: Int,
val blurhash: String?,
@SerializedName("embed_url") val embedUrl: String?,
) {
override val url: String,
override val title: String,
override val description: String,
@SerializedName("type") override val kind: PreviewCardKind,
@SerializedName("author_name") override val authorName: String,
@SerializedName("author_url") override val authorUrl: String,
@SerializedName("provider_name") override val providerName: String,
@SerializedName("provider_url") override val providerUrl: String,
override val html: String,
override val width: Int,
override val height: Int,
override val image: String? = null,
@SerializedName("embed_url") override val embedUrl: String,
override val blurhash: String? = null,
) : PreviewCard {
override fun hashCode() = url.hashCode()
@ -39,8 +43,4 @@ data class Card(
val account = other as Card?
return account?.url == this.url
}
companion object {
const val TYPE_PHOTO = "photo"
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright 2023 Pachli Association
*
* This file is a part of Pachli.
*
* 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.
*
* Pachli 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 Pachli; if not,
* see <http://www.gnu.org/licenses>.
*/
package app.pachli.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import app.pachli.R
import app.pachli.databinding.PreviewCardBinding
import app.pachli.entity.PreviewCard
import app.pachli.util.StatusDisplayOptions
import app.pachli.util.decodeBlurHash
import app.pachli.util.hide
import com.bumptech.glide.Glide
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
/**
* Compound view that displays [PreviewCard].
*
* Classes hosting this should provide a [PreviewCardView.OnClickListener] to be notified when the
* the user clicks on the card.
*/
class PreviewCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
/** Where on the card the user clicked */
enum class Target {
/** Any part of the card that's not the image */
CARD,
/** The image **/
IMAGE,
}
fun interface OnClickListener {
/** @param target Where on the card the user clicked */
fun onClick(target: Target)
}
private val binding: PreviewCardBinding
private val radius = context.resources.getDimensionPixelSize(R.dimen.card_radius).toFloat()
init {
val inflater = context.getSystemService(LayoutInflater::class.java)
binding = PreviewCardBinding.inflate(inflater, this)
orientation = VERTICAL
}
/**
* Binds the [PreviewCard] data to the view.
*
* @param card The card to bind
* @param sensitive True if the status that contained this card was marked sensitive
* @param statusDisplayOptions
* @param listener
*/
fun bind(
card: PreviewCard,
sensitive: Boolean,
statusDisplayOptions: StatusDisplayOptions,
listener: OnClickListener,
): Unit = with(binding) {
cardTitle.text = card.title
when {
card.description.isNotBlank() -> card.description
card.authorName.isNotBlank() -> card.authorName
else -> null
}?.let { cardDescription.text = it } ?: cardDescription.hide()
previewCardWrapper.setOnClickListener { listener.onClick(Target.CARD) }
cardImage.setOnClickListener { listener.onClick(Target.IMAGE) }
cardLink.text = card.url
previewCardWrapper.clipToOutline = true
// Either:
// 1. Card has a (possibly sensitive) image that user wants to see, or
// 2. Card has a blurhash, use that as the image, or
// 3. Use R.drawable.card_image_placeholder
if (statusDisplayOptions.mediaPreviewEnabled && (!sensitive || statusDisplayOptions.showSensitiveMedia) && !card.image.isNullOrBlank()) {
cardImage.shapeAppearanceModel = if (card.width > card.height) {
setTopBottomLayout()
} else {
setLeftRightLayout()
}.build()
cardImage.scaleType = ImageView.ScaleType.CENTER_CROP
val builder = Glide.with(cardImage.context)
.load(card.image)
.dontTransform()
if (statusDisplayOptions.useBlurhash && !card.blurhash.isNullOrBlank()) {
builder
.placeholder(decodeBlurHash(cardImage.context, card.blurhash!!))
.into(cardImage)
} else {
builder.into(cardImage)
}
} else if (statusDisplayOptions.useBlurhash && !card.blurhash.isNullOrBlank()) {
cardImage.shapeAppearanceModel = setLeftRightLayout().build()
cardImage.scaleType = ImageView.ScaleType.CENTER_CROP
Glide.with(cardImage.context)
.load(decodeBlurHash(cardImage.context, card.blurhash!!))
.dontTransform()
.into(cardImage)
} else {
cardImage.shapeAppearanceModel = setLeftRightLayout().build()
cardImage.scaleType = ImageView.ScaleType.CENTER
Glide.with(cardImage.context)
.load(R.drawable.card_image_placeholder)
.into(cardImage)
}
}
/** Adjusts the layout parameters to place the image above the information views */
private fun setTopBottomLayout() = with(binding) {
val cardImageShape = ShapeAppearanceModel.Builder()
previewCardWrapper.orientation = VERTICAL
cardImage.layoutParams.height =
cardImage.resources.getDimensionPixelSize(R.dimen.card_image_vertical_height)
cardImage.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
cardInfo.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
cardInfo.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius)
cardImageShape.setTopRightCorner(CornerFamily.ROUNDED, radius)
return@with cardImageShape
}
/** Adjusts the layout parameters to place the image on the left, the information on the right */
private fun setLeftRightLayout() = with(binding) {
val cardImageShape = ShapeAppearanceModel.Builder()
previewCardWrapper.orientation = HORIZONTAL
cardImage.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
cardImage.layoutParams.width =
cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width)
cardInfo.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
cardInfo.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius)
cardImageShape.setBottomLeftCorner(CornerFamily.ROUNDED, radius)
return@with cardImageShape
}
}

View File

@ -155,78 +155,11 @@
app:layout_constraintTop_toBottomOf="@id/status_content_warning_button"
tools:text="This is a status" />
<LinearLayout
android:id="@+id/status_card_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="14dp"
android:background="@drawable/card_frame"
android:clipChildren="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="80dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@+id/button_toggle_content"
tools:visibility="gone">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/card_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="1dp"
android:importantForAccessibility="no"
android:scaleType="center" />
<LinearLayout
android:id="@+id/card_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dp"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingBottom="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:textSize="?attr/status_text_medium" />
<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:textSize="?attr/status_text_medium" />
<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:attr/textColorLink"
android:textSize="?attr/status_text_medium" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/button_toggle_content"
style="@style/AppButton.Outlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:importantForAccessibility="no"
android:minWidth="150dp"
android:minHeight="0dp"
@ -238,10 +171,22 @@
android:textSize="?attr/status_text_medium"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_content"
app:layout_constraintTop_toBottomOf="@+id/status_content"
tools:text="@string/post_content_show_less"
tools:visibility="visible" />
<app.pachli.view.PreviewCardView
android:id="@+id/status_card_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="14dp"
android:minHeight="80dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/button_toggle_content"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/status_media_preview_container"
android:layout_width="0dp"
@ -253,7 +198,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_card_view"
tools:visibility="visible">
tools:visibility="gone">
<include layout="@layout/item_media_preview" />
@ -267,7 +212,8 @@
android:layout_marginEnd="14dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container" />
app:layout_constraintTop_toBottomOf="@id/status_media_preview_container"
tools:visibility="gone" />
<ImageButton
android:id="@+id/status_reply"

View File

@ -137,72 +137,18 @@
app:layout_constraintTop_toBottomOf="@+id/status_content_warning_button"
tools:text="Status content. Can be pretty long. " />
<LinearLayout
<app.pachli.view.PreviewCardView
android:id="@+id/status_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="14dp"
android:background="@drawable/card_frame"
android:clipChildren="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="80dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_content"
tools:visibility="gone">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/card_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="1dp"
android:importantForAccessibility="no"
android:scaleType="center" />
<LinearLayout
android:id="@+id/card_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dp"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingBottom="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:textColorSecondary"
android:textSize="?attr/status_text_medium" />
<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"
android:textSize="?attr/status_text_medium" />
<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"
android:textSize="?attr/status_text_medium" />
</LinearLayout>
</LinearLayout>
tools:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/status_media_preview_container"

View File

@ -15,79 +15,11 @@
~ see <http://www.gnu.org/licenses>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<app.pachli.view.PreviewCardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingStart="14dp"
android:paddingEnd="14dp">
<LinearLayout
android:id="@+id/status_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/card_frame"
android:clipChildren="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="80dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/card_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="1dp"
android:importantForAccessibility="no"
android:scaleType="center" />
<LinearLayout
android:id="@+id/card_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dp"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingBottom="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:textColorSecondary"
android:textSize="?attr/status_text_medium"
tools:ignore="SelectableText" />
<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"
android:textSize="?attr/status_text_medium"
tools:ignore="SelectableText" />
<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:attr/textColorLink"
android:textSize="?attr/status_text_medium"
tools:ignore="SelectableText" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
android:paddingEnd="14dp" />

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2023 Pachli Association
~
~ This file is a part of Pachli.
~
~ 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.
~
~ Pachli 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 Pachli; if not,
~ see <http://www.gnu.org/licenses>.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/preview_card_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/card_frame"
android:clipChildren="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="80dp"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/card_image"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_margin="1dp"
android:importantForAccessibility="no"
android:scaleType="center" />
<LinearLayout
android:id="@+id/card_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dp"
android:paddingTop="6dp"
android:paddingRight="6dp"
android:paddingBottom="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:textColorSecondary"
android:textSize="?attr/status_text_medium"
tools:ignore="SelectableText" />
<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"
android:textSize="?attr/status_text_medium"
tools:ignore="SelectableText" />
<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:attr/textColorLink"
android:textSize="?attr/status_text_small"
tools:ignore="SelectableText" />
</LinearLayout>
</LinearLayout>
</merge>