Add option to not crop image previews (#2832)
* Don't crop image previews with aspects between 2:1 & 1:2 Fixes #1995 * Custom media preview layout for handling various aspect ratios
This commit is contained in:
parent
6b95790457
commit
cc790ccf69
10 changed files with 402 additions and 298 deletions
|
@ -1,5 +1,7 @@
|
|||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
|
@ -23,6 +25,7 @@ import androidx.appcompat.app.AlertDialog;
|
|||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.text.HtmlCompat;
|
||||
import androidx.core.view.ViewKt;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -53,6 +56,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions;
|
|||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.util.TimestampUtils;
|
||||
import com.keylesspalace.tusky.view.MediaPreviewImageView;
|
||||
import com.keylesspalace.tusky.view.MediaPreviewLayout;
|
||||
import com.keylesspalace.tusky.viewdata.PollOptionViewData;
|
||||
import com.keylesspalace.tusky.viewdata.PollViewData;
|
||||
import com.keylesspalace.tusky.viewdata.PollViewDataKt;
|
||||
|
@ -66,12 +70,11 @@ import at.connyduck.sparkbutton.SparkButton;
|
|||
import at.connyduck.sparkbutton.helpers.Utils;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
|
||||
import static com.keylesspalace.tusky.viewdata.PollViewDataKt.buildDescription;
|
||||
|
||||
public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||
public static class Key {
|
||||
public static final String KEY_CREATED = "created";
|
||||
}
|
||||
|
||||
private TextView displayName;
|
||||
private TextView username;
|
||||
private ImageButton replyButton;
|
||||
|
@ -81,8 +84,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
private SparkButton bookmarkButton;
|
||||
private ImageButton moreButton;
|
||||
private ConstraintLayout mediaContainer;
|
||||
protected MediaPreviewImageView[] mediaPreviews;
|
||||
private ImageView[] mediaOverlays;
|
||||
protected MediaPreviewLayout mediaPreview;
|
||||
private TextView sensitiveMediaWarning;
|
||||
private View sensitiveMediaShow;
|
||||
protected TextView[] mediaLabels;
|
||||
|
@ -132,19 +134,8 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
mediaContainer = itemView.findViewById(R.id.status_media_preview_container);
|
||||
mediaContainer.setClipToOutline(true);
|
||||
mediaPreview = itemView.findViewById(R.id.status_media_preview);
|
||||
|
||||
mediaPreviews = new MediaPreviewImageView[]{
|
||||
itemView.findViewById(R.id.status_media_preview_0),
|
||||
itemView.findViewById(R.id.status_media_preview_1),
|
||||
itemView.findViewById(R.id.status_media_preview_2),
|
||||
itemView.findViewById(R.id.status_media_preview_3)
|
||||
};
|
||||
mediaOverlays = new ImageView[]{
|
||||
itemView.findViewById(R.id.status_media_overlay_0),
|
||||
itemView.findViewById(R.id.status_media_overlay_1),
|
||||
itemView.findViewById(R.id.status_media_overlay_2),
|
||||
itemView.findViewById(R.id.status_media_overlay_3)
|
||||
};
|
||||
sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning);
|
||||
sensitiveMediaShow = itemView.findViewById(R.id.status_sensitive_media_button);
|
||||
mediaLabels = new TextView[]{
|
||||
|
@ -181,8 +172,6 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
mediaPreviewUnloaded = new ColorDrawable(ThemeUtils.getColor(itemView.getContext(), R.attr.colorBackgroundAccent));
|
||||
}
|
||||
|
||||
protected abstract int getMediaPreviewHeight(Context context);
|
||||
|
||||
protected void setDisplayName(String name, List<Emoji> customEmojis, StatusDisplayOptions statusDisplayOptions) {
|
||||
CharSequence emojifiedName = CustomEmojiHelper.emojify(
|
||||
name, customEmojis, displayName, statusDisplayOptions.animateEmojis()
|
||||
|
@ -420,64 +409,51 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
Drawable placeholder = blurhash != null ? decodeBlurHash(blurhash) : mediaPreviewUnloaded;
|
||||
|
||||
if (TextUtils.isEmpty(previewUrl)) {
|
||||
imageView.removeFocalPoint();
|
||||
|
||||
Glide.with(imageView)
|
||||
.load(placeholder)
|
||||
.centerInside()
|
||||
.into(imageView);
|
||||
|
||||
} else {
|
||||
Focus focus = meta != null ? meta.getFocus() : null;
|
||||
|
||||
if (focus != null) { // If there is a focal point for this attachment:
|
||||
imageView.setFocalPoint(focus);
|
||||
|
||||
Glide.with(imageView)
|
||||
.load(previewUrl)
|
||||
.placeholder(placeholder)
|
||||
.centerInside()
|
||||
.addListener(imageView)
|
||||
.into(imageView);
|
||||
} else {
|
||||
ViewKt.doOnLayout(imageView, view -> {
|
||||
if (TextUtils.isEmpty(previewUrl)) {
|
||||
imageView.removeFocalPoint();
|
||||
|
||||
Glide.with(imageView)
|
||||
.load(previewUrl)
|
||||
.placeholder(placeholder)
|
||||
.load(placeholder)
|
||||
.centerInside()
|
||||
.into(imageView);
|
||||
|
||||
} else {
|
||||
Focus focus = meta != null ? meta.getFocus() : null;
|
||||
|
||||
if (focus != null) { // If there is a focal point for this attachment:
|
||||
imageView.setFocalPoint(focus);
|
||||
|
||||
Glide.with(imageView)
|
||||
.load(previewUrl)
|
||||
.placeholder(placeholder)
|
||||
.centerInside()
|
||||
.addListener(imageView)
|
||||
.into(imageView);
|
||||
} else {
|
||||
imageView.removeFocalPoint();
|
||||
|
||||
Glide.with(imageView)
|
||||
.load(previewUrl)
|
||||
.placeholder(placeholder)
|
||||
.centerInside()
|
||||
.into(imageView);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected void setMediaPreviews(final List<Attachment> attachments, boolean sensitive,
|
||||
final StatusActionListener listener, boolean showingContent,
|
||||
boolean useBlurhash) {
|
||||
Context context = itemView.getContext();
|
||||
final int n = Math.min(attachments.size(), Status.MAX_MEDIA_ATTACHMENTS);
|
||||
|
||||
mediaPreview.setAspectRatios(AttachmentHelper.aspectRatios(attachments));
|
||||
|
||||
final int mediaPreviewHeight = getMediaPreviewHeight(context);
|
||||
|
||||
if (n <= 2) {
|
||||
mediaPreviews[0].getLayoutParams().height = mediaPreviewHeight * 2;
|
||||
mediaPreviews[1].getLayoutParams().height = mediaPreviewHeight * 2;
|
||||
} else {
|
||||
mediaPreviews[0].getLayoutParams().height = mediaPreviewHeight;
|
||||
mediaPreviews[1].getLayoutParams().height = mediaPreviewHeight;
|
||||
mediaPreviews[2].getLayoutParams().height = mediaPreviewHeight;
|
||||
mediaPreviews[3].getLayoutParams().height = mediaPreviewHeight;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
mediaPreview.forEachIndexed((i, imageView) -> {
|
||||
Attachment attachment = attachments.get(i);
|
||||
String previewUrl = attachment.getPreviewUrl();
|
||||
String description = attachment.getDescription();
|
||||
MediaPreviewImageView imageView = mediaPreviews[i];
|
||||
|
||||
imageView.setVisibility(View.VISIBLE);
|
||||
|
||||
if (TextUtils.isEmpty(description)) {
|
||||
imageView.setContentDescription(imageView.getContext()
|
||||
|
@ -495,42 +471,38 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
|
||||
final Attachment.Type type = attachment.getType();
|
||||
if (showingContent && (type == Attachment.Type.VIDEO || type == Attachment.Type.GIFV)) {
|
||||
mediaOverlays[i].setVisibility(View.VISIBLE);
|
||||
imageView.setForeground(ContextCompat.getDrawable(itemView.getContext(), R.drawable.play_indicator_overlay));
|
||||
} else {
|
||||
mediaOverlays[i].setVisibility(View.GONE);
|
||||
imageView.setForeground(null);
|
||||
}
|
||||
|
||||
setAttachmentClickListener(imageView, listener, i, attachment, true);
|
||||
}
|
||||
|
||||
if (sensitive) {
|
||||
sensitiveMediaWarning.setText(R.string.post_sensitive_media_title);
|
||||
} else {
|
||||
sensitiveMediaWarning.setText(R.string.post_media_hidden_title);
|
||||
}
|
||||
|
||||
sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
|
||||
sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
|
||||
sensitiveMediaShow.setOnClickListener(v -> {
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(false, getBindingAdapterPosition());
|
||||
if (sensitive) {
|
||||
sensitiveMediaWarning.setText(R.string.post_sensitive_media_title);
|
||||
} else {
|
||||
sensitiveMediaWarning.setText(R.string.post_media_hidden_title);
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||
});
|
||||
sensitiveMediaWarning.setOnClickListener(v -> {
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(true, getBindingAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaShow.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
sensitiveMediaWarning.setVisibility(showingContent ? View.GONE : View.VISIBLE);
|
||||
sensitiveMediaShow.setVisibility(showingContent ? View.VISIBLE : View.GONE);
|
||||
sensitiveMediaShow.setOnClickListener(v -> {
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(false, getBindingAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||
});
|
||||
sensitiveMediaWarning.setOnClickListener(v -> {
|
||||
if (getBindingAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(true, getBindingAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
sensitiveMediaShow.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
// Hide any of the placeholder previews beyond the ones set.
|
||||
for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
|
||||
mediaPreviews[i].setVisibility(View.GONE);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
|
@ -751,10 +723,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
|||
} else {
|
||||
setMediaLabel(attachments, sensitive, listener, status.isShowingContent());
|
||||
// Hide all unused views.
|
||||
mediaPreviews[0].setVisibility(View.GONE);
|
||||
mediaPreviews[1].setVisibility(View.GONE);
|
||||
mediaPreviews[2].setVisibility(View.GONE);
|
||||
mediaPreviews[3].setVisibility(View.GONE);
|
||||
mediaPreview.setVisibility(View.GONE);
|
||||
hideSensitiveMediaWarning();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.keylesspalace.tusky.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
|
@ -33,11 +32,6 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
|||
infoDivider = view.findViewById(R.id.status_info_divider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMediaPreviewHeight(Context context) {
|
||||
return context.getResources().getDimensionPixelSize(R.dimen.status_detail_media_preview_height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setCreatedAt(Date createdAt, StatusDisplayOptions statusDisplayOptions) {
|
||||
if (createdAt == null) {
|
||||
|
|
|
@ -53,11 +53,6 @@ public class StatusViewHolder extends StatusBaseViewHolder {
|
|||
contentCollapseButton = itemView.findViewById(R.id.button_toggle_content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMediaPreviewHeight(Context context) {
|
||||
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupWithStatus(@NonNull StatusViewData.Concrete status,
|
||||
@NonNull final StatusActionListener listener,
|
||||
|
|
|
@ -68,11 +68,6 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMediaPreviewHeight(Context context) {
|
||||
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
|
||||
}
|
||||
|
||||
void setupWithConversation(
|
||||
@NonNull ConversationViewData conversation,
|
||||
@Nullable Object payloads
|
||||
|
@ -108,10 +103,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
} else {
|
||||
setMediaLabel(attachments, sensitive, listener, statusViewData.isShowingContent());
|
||||
// Hide all unused views.
|
||||
mediaPreviews[0].setVisibility(View.GONE);
|
||||
mediaPreviews[1].setVisibility(View.GONE);
|
||||
mediaPreviews[2].setVisibility(View.GONE);
|
||||
mediaPreviews[3].setVisibility(View.GONE);
|
||||
mediaPreview.setVisibility(View.GONE);
|
||||
hideSensitiveMediaWarning();
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,9 @@ data class Attachment(
|
|||
@Parcelize
|
||||
data class MetaData(
|
||||
val focus: Focus?,
|
||||
val duration: Float?
|
||||
val duration: Float?,
|
||||
val original: Size?,
|
||||
val small: Size?,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
|
@ -82,4 +84,14 @@ data class Attachment(
|
|||
val x: Float,
|
||||
val y: Float
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* The size of an image, used to specify the width/height.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Size(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val aspect: Double
|
||||
) : Parcelable
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@file:JvmName("AttachmentHelper")
|
||||
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.content.Context
|
||||
|
@ -24,3 +25,12 @@ private fun formatDuration(durationInSeconds: Double): String {
|
|||
val hours = durationInSeconds.toInt() / 3600
|
||||
return "%d:%02d:%02d".format(hours, minutes, seconds)
|
||||
}
|
||||
|
||||
fun List<Attachment>.aspectRatios(): List<Double> {
|
||||
return map { attachment ->
|
||||
// clamp ratio between 2:1 & 1:2, defaulting to 16:9
|
||||
val size = (attachment.meta?.small ?: attachment.meta?.original) ?: return@map 1.7778
|
||||
val aspect = if (size.aspect > 0) size.aspect else size.width.toDouble() / size.height
|
||||
aspect.coerceIn(0.5, 2.0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
package com.keylesspalace.tusky.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import com.keylesspalace.tusky.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Lays out a set of [MediaPreviewImageView]s keeping their aspect ratios into account.
|
||||
*/
|
||||
class MediaPreviewLayout(context: Context, attrs: AttributeSet? = null) :
|
||||
ViewGroup(context, attrs) {
|
||||
|
||||
private val spacing = context.resources.getDimensionPixelOffset(R.dimen.preview_image_spacing)
|
||||
|
||||
/**
|
||||
* An ordered list of aspect ratios used for layout. An image view for each aspect ratio passed
|
||||
* will be attached. Supports up to 4, additional ones will be ignored.
|
||||
*/
|
||||
var aspectRatios: List<Double> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
attachImageViews()
|
||||
}
|
||||
|
||||
private val imageViewCache = Array(4) { MediaPreviewImageView(context) }
|
||||
|
||||
private var measuredOrientation = LinearLayout.VERTICAL
|
||||
|
||||
private fun attachImageViews() {
|
||||
removeAllViews()
|
||||
for (i in 0 until aspectRatios.size.coerceAtMost(imageViewCache.size)) {
|
||||
addView(imageViewCache[i])
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||
val halfWidth = width / 2 - spacing / 2
|
||||
var totalHeight = 0
|
||||
|
||||
when (childCount) {
|
||||
1 -> {
|
||||
val aspect = aspectRatios[0]
|
||||
totalHeight += getChildAt(0).measureToAspect(width, aspect)
|
||||
}
|
||||
2 -> {
|
||||
val aspect1 = aspectRatios[0]
|
||||
val aspect2 = aspectRatios[1]
|
||||
|
||||
if ((aspect1 + aspect2) / 2 > 1.2) {
|
||||
// stack vertically
|
||||
measuredOrientation = LinearLayout.VERTICAL
|
||||
totalHeight += getChildAt(0).measureToAspect(width, aspect1.coerceAtLeast(1.8))
|
||||
totalHeight += spacing
|
||||
totalHeight += getChildAt(1).measureToAspect(width, aspect2.coerceAtLeast(1.8))
|
||||
} else {
|
||||
// stack horizontally
|
||||
measuredOrientation = LinearLayout.HORIZONTAL
|
||||
val height = rowHeight(halfWidth, aspect1, aspect2)
|
||||
totalHeight += height
|
||||
getChildAt(0).measureExactly(halfWidth, height)
|
||||
getChildAt(1).measureExactly(halfWidth, height)
|
||||
}
|
||||
}
|
||||
3 -> {
|
||||
val aspect1 = aspectRatios[0]
|
||||
val aspect2 = aspectRatios[1]
|
||||
val aspect3 = aspectRatios[2]
|
||||
if (aspect1 >= 1) {
|
||||
// | 1 |
|
||||
// -------------
|
||||
// | 2 | 3 |
|
||||
measuredOrientation = LinearLayout.VERTICAL
|
||||
totalHeight += getChildAt(0).measureToAspect(width, aspect1.coerceAtLeast(1.8))
|
||||
totalHeight += spacing
|
||||
val bottomHeight = rowHeight(halfWidth, aspect2, aspect3)
|
||||
totalHeight += bottomHeight
|
||||
getChildAt(1).measureExactly(halfWidth, bottomHeight)
|
||||
getChildAt(2).measureExactly(halfWidth, bottomHeight)
|
||||
} else {
|
||||
// | | 2 |
|
||||
// | 1 |-----|
|
||||
// | | 3 |
|
||||
measuredOrientation = LinearLayout.HORIZONTAL
|
||||
val colHeight = getChildAt(0).measureToAspect(halfWidth, aspect1)
|
||||
totalHeight += colHeight
|
||||
val halfHeight = colHeight / 2 - spacing / 2
|
||||
getChildAt(1).measureExactly(halfWidth, halfHeight)
|
||||
getChildAt(2).measureExactly(halfWidth, halfHeight)
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
val aspect1 = aspectRatios[0]
|
||||
val aspect2 = aspectRatios[1]
|
||||
val aspect3 = aspectRatios[2]
|
||||
val aspect4 = aspectRatios[3]
|
||||
val topHeight = rowHeight(halfWidth, aspect1, aspect2)
|
||||
totalHeight += topHeight
|
||||
getChildAt(0).measureExactly(halfWidth, topHeight)
|
||||
getChildAt(1).measureExactly(halfWidth, topHeight)
|
||||
totalHeight += spacing
|
||||
val bottomHeight = rowHeight(halfWidth, aspect3, aspect4)
|
||||
totalHeight += bottomHeight
|
||||
getChildAt(2).measureExactly(halfWidth, bottomHeight)
|
||||
getChildAt(3).measureExactly(halfWidth, bottomHeight)
|
||||
}
|
||||
}
|
||||
|
||||
super.onMeasure(
|
||||
widthMeasureSpec,
|
||||
MeasureSpec.makeMeasureSpec(totalHeight, MeasureSpec.EXACTLY)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
val width = r - l
|
||||
val height = b - t
|
||||
val halfWidth = width / 2 - spacing / 2
|
||||
when (childCount) {
|
||||
1 -> {
|
||||
getChildAt(0).layout(0, 0, width, height)
|
||||
}
|
||||
2 -> {
|
||||
if (measuredOrientation == LinearLayout.VERTICAL) {
|
||||
val y = imageViewCache[0].measuredHeight
|
||||
getChildAt(0).layout(0, 0, width, y)
|
||||
getChildAt(1).layout(
|
||||
0,
|
||||
y + spacing,
|
||||
width,
|
||||
y + spacing + getChildAt(1).measuredHeight
|
||||
)
|
||||
} else {
|
||||
getChildAt(0).layout(0, 0, halfWidth, height)
|
||||
getChildAt(1).layout(halfWidth + spacing, 0, width, height)
|
||||
}
|
||||
}
|
||||
3 -> {
|
||||
if (measuredOrientation == LinearLayout.VERTICAL) {
|
||||
val y = getChildAt(0).measuredHeight
|
||||
getChildAt(0).layout(0, 0, width, y)
|
||||
getChildAt(1).layout(0, y + spacing, halfWidth, height)
|
||||
getChildAt(2).layout(halfWidth + spacing, y + spacing, width, height)
|
||||
} else {
|
||||
val colHeight = getChildAt(0).measuredHeight
|
||||
getChildAt(0).layout(0, 0, halfWidth, colHeight)
|
||||
val halfHeight = colHeight / 2 - spacing / 2
|
||||
getChildAt(1).layout(halfWidth + spacing, 0, width, halfHeight)
|
||||
getChildAt(2).layout(
|
||||
halfWidth + spacing,
|
||||
halfHeight + spacing,
|
||||
width,
|
||||
colHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
4 -> {
|
||||
val topHeight = (getChildAt(0).measuredHeight + getChildAt(1).measuredHeight) / 2
|
||||
getChildAt(0).layout(0, 0, halfWidth, topHeight)
|
||||
getChildAt(1).layout(halfWidth + spacing, 0, width, topHeight)
|
||||
val bottomHeight =
|
||||
(imageViewCache[2].measuredHeight + imageViewCache[3].measuredHeight) / 2
|
||||
getChildAt(2).layout(
|
||||
0,
|
||||
topHeight + spacing,
|
||||
halfWidth,
|
||||
topHeight + spacing + bottomHeight
|
||||
)
|
||||
getChildAt(3).layout(
|
||||
halfWidth + spacing,
|
||||
topHeight + spacing,
|
||||
width,
|
||||
topHeight + spacing + bottomHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun forEachIndexed(action: (Int, MediaPreviewImageView) -> Unit) {
|
||||
for (index in 0 until childCount) {
|
||||
action(index, getChildAt(index) as MediaPreviewImageView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas?) {
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rowHeight(halfWidth: Int, aspect1: Double, aspect2: Double): Int {
|
||||
return ((halfWidth / aspect1 + halfWidth / aspect2) / 2).roundToInt()
|
||||
}
|
||||
|
||||
private fun View.measureToAspect(width: Int, aspect: Double): Int {
|
||||
val height = (width / aspect).roundToInt()
|
||||
measureExactly(width, height)
|
||||
return height
|
||||
}
|
||||
|
||||
private fun View.measureExactly(width: Int, height: Int) {
|
||||
measure(
|
||||
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
||||
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
|
||||
)
|
||||
}
|
6
app/src/main/res/drawable/play_indicator_overlay.xml
Normal file
6
app/src/main/res/drawable/play_indicator_overlay.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:drawable="@drawable/ic_play_indicator"
|
||||
android:gravity="center" />
|
||||
</layer-list>
|
|
@ -1,198 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
android:id="@+id/status_media_preview_0"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/status_media_preview_height"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
<com.keylesspalace.tusky.view.MediaPreviewLayout
|
||||
android:id="@+id/status_media_preview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
android:id="@+id/status_media_preview_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/status_media_preview_height"
|
||||
android:layout_marginStart="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/status_media_preview_0"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
<ImageView
|
||||
android:id="@+id/status_sensitive_media_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.7"
|
||||
android:contentDescription="@null"
|
||||
android:padding="@dimen/status_sensitive_media_button_padding"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
|
||||
app:srcCompat="@drawable/ic_eye_24dp"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
android:id="@+id/status_media_preview_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/status_media_preview_height"
|
||||
android:layout_marginTop="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status_media_preview_3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_0"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/status_sensitive_media_warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/media_warning_bg"
|
||||
android:gravity="center"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.keylesspalace.tusky.view.MediaPreviewImageView
|
||||
android:id="@+id/status_media_preview_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/status_media_preview_height"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="@drawable/media_preview_outline"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/status_media_preview_2"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status_media_preview_1"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_0"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:maxLines="10"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_media_overlay_0"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_0"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_0"
|
||||
app:layout_constraintStart_toStartOf="@+id/status_media_preview_0"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_0"
|
||||
app:srcCompat="@drawable/ic_play_indicator"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:maxLines="10"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_0" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_media_overlay_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_1"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_1"
|
||||
app:layout_constraintStart_toStartOf="@+id/status_media_preview_1"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_1"
|
||||
app:srcCompat="@drawable/ic_play_indicator"
|
||||
tools:ignore="ContentDescription" />
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:maxLines="10"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_media_overlay_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_2"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_2"
|
||||
app:layout_constraintStart_toStartOf="@+id/status_media_preview_2"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_2"
|
||||
app:srcCompat="@drawable/ic_play_indicator"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_media_overlay_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_media_preview_3"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status_media_preview_3"
|
||||
app:layout_constraintStart_toStartOf="@+id/status_media_preview_3"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_3"
|
||||
app:srcCompat="@drawable/ic_play_indicator"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/status_sensitive_media_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0.7"
|
||||
android:contentDescription="@null"
|
||||
android:padding="@dimen/status_sensitive_media_button_padding"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="@+id/status_media_preview_container"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_media_preview_container"
|
||||
app:srcCompat="@drawable/ic_eye_24dp"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_sensitive_media_warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/media_warning_bg"
|
||||
android:gravity="center"
|
||||
android:lineSpacingMultiplier="1.2"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_0"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
android:maxLines="10"
|
||||
android:ellipsize="end"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
android:maxLines="10"
|
||||
android:ellipsize="end"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
android:maxLines="10"
|
||||
android:ellipsize="end"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
android:maxLines="10"
|
||||
android:ellipsize="end"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_2" />
|
||||
<TextView
|
||||
android:id="@+id/status_media_label_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawablePadding="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical"
|
||||
android:importantForAccessibility="no"
|
||||
android:maxLines="10"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
android:visibility="gone"
|
||||
app:drawableTint="?android:attr/textColorTertiary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_media_label_2" />
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -58,4 +58,5 @@
|
|||
|
||||
<dimen name="profile_media_audio_icon_padding">16dp</dimen>
|
||||
|
||||
<dimen name="preview_image_spacing">4dp</dimen>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue