package org.thoughtcrime.securesms.components; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.SlidesClickedListener; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Locale; import okhttp3.HttpUrl; /** * The view shown in the compose box or conversation that represents the state of the link preview. */ public class LinkPreviewView extends FrameLayout { private static final int TYPE_CONVERSATION = 0; private static final int TYPE_COMPOSE = 1; private ViewGroup container; private OutlinedThumbnailView thumbnail; private TextView title; private TextView description; private TextView site; private View divider; private View closeButton; private View spinner; private TextView noPreview; private int type; private int defaultRadius; private CornerMask cornerMask; private CloseClickedListener closeClickedListener; public LinkPreviewView(Context context) { super(context); init(null); } public LinkPreviewView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(attrs); } private void init(@Nullable AttributeSet attrs) { inflate(getContext(), R.layout.link_preview, this); container = findViewById(R.id.linkpreview_container); thumbnail = findViewById(R.id.linkpreview_thumbnail); title = findViewById(R.id.linkpreview_title); description = findViewById(R.id.linkpreview_description); site = findViewById(R.id.linkpreview_site); divider = findViewById(R.id.linkpreview_divider); spinner = findViewById(R.id.linkpreview_progress_wheel); closeButton = findViewById(R.id.linkpreview_close); noPreview = findViewById(R.id.linkpreview_no_preview); defaultRadius = getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius); cornerMask = new CornerMask(this); if (attrs != null) { TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.LinkPreviewView, 0, 0); type = typedArray.getInt(R.styleable.LinkPreviewView_linkpreview_type, 0); typedArray.recycle(); } if (type == TYPE_COMPOSE) { container.setBackgroundColor(Color.TRANSPARENT); container.setPadding(0, 0, 0, 0); divider.setVisibility(VISIBLE); closeButton.setVisibility(VISIBLE); title.setMaxLines(2); description.setMaxLines(2); closeButton.setOnClickListener(v -> { if (closeClickedListener != null) { closeClickedListener.onCloseClicked(); } }); } setWillNotDraw(false); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (type == TYPE_COMPOSE) return; cornerMask.mask(canvas); } public void setLoading() { title.setVisibility(GONE); site.setVisibility(GONE); description.setVisibility(GONE); thumbnail.setVisibility(GONE); spinner.setVisibility(VISIBLE); noPreview.setVisibility(INVISIBLE); } public void setNoPreview(@Nullable LinkPreviewRepository.Error customError) { title.setVisibility(GONE); site.setVisibility(GONE); thumbnail.setVisibility(GONE); spinner.setVisibility(GONE); noPreview.setVisibility(VISIBLE); noPreview.setText(getLinkPreviewErrorString(customError)); } public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) { setLinkPreview(glideRequests, linkPreview, showThumbnail, true); } public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showDescription) { spinner.setVisibility(GONE); noPreview.setVisibility(GONE); if (!Util.isEmpty(linkPreview.getTitle())) { title.setText(linkPreview.getTitle()); title.setVisibility(VISIBLE); } else { title.setVisibility(GONE); } if (showDescription && !Util.isEmpty(linkPreview.getDescription())) { description.setText(linkPreview.getDescription()); description.setVisibility(VISIBLE); } else { description.setVisibility(GONE); } String domain = null; if (!Util.isEmpty(linkPreview.getUrl())) { HttpUrl url = HttpUrl.parse(linkPreview.getUrl()); if (url != null) { domain = url.topPrivateDomain(); } } if (domain != null && linkPreview.getDate() > 0) { site.setText(getContext().getString(R.string.LinkPreviewView_domain_date, domain, formatDate(linkPreview.getDate()))); site.setVisibility(VISIBLE); } else if (domain != null) { site.setText(domain); site.setVisibility(VISIBLE); } else if (linkPreview.getDate() > 0) { site.setText(formatDate(linkPreview.getDate())); site.setVisibility(VISIBLE); } else { site.setVisibility(GONE); } if (showThumbnail && linkPreview.getThumbnail().isPresent()) { thumbnail.setVisibility(VISIBLE); thumbnail.setImageResource(glideRequests, new ImageSlide(getContext(), linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); thumbnail.showDownloadText(false); } else { thumbnail.setVisibility(GONE); } } public void setCorners(int topStart, int topEnd) { if (ViewUtil.isRtl(this)) { cornerMask.setRadii(topEnd, topStart, 0, 0); thumbnail.setCorners(defaultRadius, topEnd, defaultRadius, defaultRadius); } else { cornerMask.setRadii(topStart, topEnd, 0, 0); thumbnail.setCorners(topStart, defaultRadius, defaultRadius, defaultRadius); } postInvalidate(); } public void setCloseClickedListener(@Nullable CloseClickedListener closeClickedListener) { this.closeClickedListener = closeClickedListener; } public void setDownloadClickedListener(SlidesClickedListener listener) { thumbnail.setDownloadClickListener(listener); } private @StringRes static int getLinkPreviewErrorString(@Nullable LinkPreviewRepository.Error customError) { return customError == LinkPreviewRepository.Error.GROUP_LINK_INACTIVE ? R.string.LinkPreviewView_this_group_link_is_not_active : R.string.LinkPreviewView_no_link_preview_available; } private static String formatDate(long date) { DateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()); return dateFormat.format(date); } public interface CloseClickedListener { void onCloseClicked(); } }