mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 20:15:21 +00:00
Merge pull request #619 from hjubb/ui
Album, Thumbnail, Progress, Long Message UI for Conversation Screen 2.0
This commit is contained in:
commit
d38b81f222
@ -64,10 +64,12 @@ import org.session.libsignal.utilities.Log;
|
|||||||
import org.thoughtcrime.securesms.components.MediaView;
|
import org.thoughtcrime.securesms.components.MediaView;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||||
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
|
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
|
||||||
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
@ -116,6 +118,22 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
|
|
||||||
private int restartItem = -1;
|
private int restartItem = -1;
|
||||||
|
|
||||||
|
public static Intent getPreviewIntent(Context context, Slide slide, MmsMessageRecord mms) {
|
||||||
|
Intent previewIntent = null;
|
||||||
|
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||||
|
previewIntent = new Intent(context, MediaPreviewActivity.class);
|
||||||
|
previewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
.setDataAndType(slide.getUri(), slide.getContentType())
|
||||||
|
.putExtra(ADDRESS_EXTRA, mms.getRecipient().getAddress())
|
||||||
|
.putExtra(OUTGOING_EXTRA, mms.isOutgoing())
|
||||||
|
.putExtra(DATE_EXTRA, mms.getTimestamp())
|
||||||
|
.putExtra(SIZE_EXTRA, slide.asAttachment().getSize())
|
||||||
|
.putExtra(CAPTION_EXTRA, slide.getCaption().orNull())
|
||||||
|
.putExtra(LEFT_IS_RECENT_EXTRA, false);
|
||||||
|
}
|
||||||
|
return previewIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
package org.thoughtcrime.securesms.components;
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.ColorInt;
|
|
||||||
import androidx.annotation.IdRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
import org.session.libsession.utilities.Stub;
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||||
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
|
||||||
import org.session.libsession.utilities.Stub;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class AlbumThumbnailView extends FrameLayout {
|
public class AlbumThumbnailView extends FrameLayout {
|
||||||
|
|
||||||
private @Nullable SlideClickListener thumbnailClickListener;
|
private @Nullable SlideClickListener thumbnailClickListener;
|
||||||
@ -53,8 +53,8 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
private void initialize() {
|
private void initialize() {
|
||||||
inflate(getContext(), R.layout.album_thumbnail_view, this);
|
inflate(getContext(), R.layout.album_thumbnail_view, this);
|
||||||
|
|
||||||
albumCellContainer = findViewById(R.id.album_cell_container);
|
albumCellContainer = findViewById(R.id.albumCellContainer);
|
||||||
transferControls = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
|
transferControls = new Stub<>(findViewById(R.id.albumTransferControlsStub));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides, boolean showControls) {
|
public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides, boolean showControls) {
|
||||||
@ -149,9 +149,8 @@ public class AlbumThumbnailView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) {
|
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) {
|
||||||
ThumbnailView cell = findViewById(id);
|
KThumbnailView cell = findViewById(id);
|
||||||
cell.setImageResource(glideRequests, slide, false, false);
|
cell.setImageResource(glideRequests, slide, false);
|
||||||
cell.setLoadIndicatorVisibile(slide.isInProgress());
|
|
||||||
cell.setThumbnailClickListener(defaultThumbnailClickListener);
|
cell.setThumbnailClickListener(defaultThumbnailClickListener);
|
||||||
cell.setOnLongClickListener(defaultLongClickListener);
|
cell.setOnLongClickListener(defaultLongClickListener);
|
||||||
}
|
}
|
||||||
|
@ -65,15 +65,10 @@ public class ConversationItemThumbnail extends FrameLayout {
|
|||||||
|
|
||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ConversationItemThumbnail, 0, 0);
|
||||||
thumbnail.setBounds(typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minWidth, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxWidth, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_minHeight, 0),
|
|
||||||
typedArray.getDimensionPixelSize(R.styleable.ConversationItemThumbnail_conversationThumbnail_maxHeight, 0));
|
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SuspiciousNameCombination")
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispatchDraw(Canvas canvas) {
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
super.dispatchDraw(canvas);
|
super.dispatchDraw(canvas);
|
||||||
|
@ -29,7 +29,6 @@ public class OutlinedThumbnailView extends ThumbnailView {
|
|||||||
outliner = new Outliner();
|
outliner = new Outliner();
|
||||||
|
|
||||||
outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color));
|
outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color));
|
||||||
setRadius(0);
|
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import kotlinx.android.synthetic.main.album_thumbnail_view.view.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.MediaPreviewActivity
|
||||||
|
import org.thoughtcrime.securesms.components.CornerMask
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher
|
||||||
|
import org.thoughtcrime.securesms.longmessage.LongMessageActivity
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide
|
||||||
|
|
||||||
|
class AlbumThumbnailView : FrameLayout {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_ALBUM_DISPLAY_SIZE = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Lifecycle
|
||||||
|
constructor(context: Context) : super(context) {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val cornerMask by lazy { CornerMask(this) }
|
||||||
|
private var slides: List<Slide> = listOf()
|
||||||
|
private var slideSize: Int = 0
|
||||||
|
|
||||||
|
private fun initialize() {
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.album_thumbnail_view, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchDraw(canvas: Canvas?) {
|
||||||
|
super.dispatchDraw(canvas)
|
||||||
|
cornerMask.mask(canvas)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Interaction
|
||||||
|
|
||||||
|
fun calculateHitObject(rawRect: Rect, mms: MmsMessageRecord) {
|
||||||
|
// Z-check in specific order
|
||||||
|
val testRect = Rect()
|
||||||
|
// test "Read More"
|
||||||
|
albumCellBodyTextReadMore.getGlobalVisibleRect(testRect)
|
||||||
|
if (Rect.intersects(rawRect, testRect)) {
|
||||||
|
// dispatch to activity view
|
||||||
|
ActivityDispatcher.get(context)?.dispatchIntent { context ->
|
||||||
|
LongMessageActivity.getIntent(context, mms.recipient.address, mms.getId(), true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// test each album child
|
||||||
|
albumCellContainer.findViewById<ViewGroup>(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child ->
|
||||||
|
child.getGlobalVisibleRect(testRect)
|
||||||
|
if (Rect.intersects(rawRect, testRect)) {
|
||||||
|
// hit intersects with this particular child
|
||||||
|
val slide = slides.getOrNull(index) ?: return
|
||||||
|
// only open to downloaded images
|
||||||
|
if (slide.isInProgress) return
|
||||||
|
|
||||||
|
ActivityDispatcher.get(context)?.dispatchIntent { context ->
|
||||||
|
MediaPreviewActivity.getPreviewIntent(context, slide, mms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(glideRequests: GlideRequests, message: MmsMessageRecord,
|
||||||
|
isStart: Boolean, isEnd: Boolean) {
|
||||||
|
slides = message.slideDeck.thumbnailSlides
|
||||||
|
if (slides.isEmpty()) {
|
||||||
|
// this should never be encountered because it's checked by parent
|
||||||
|
return
|
||||||
|
}
|
||||||
|
calculateRadius(isStart, isEnd, message.isOutgoing)
|
||||||
|
|
||||||
|
// recreate cell views if different size to what we have already (for recycling)
|
||||||
|
if (slides.size != this.slideSize) {
|
||||||
|
albumCellContainer.removeAllViews()
|
||||||
|
LayoutInflater.from(context).inflate(layoutRes(slides.size), albumCellContainer)
|
||||||
|
val overflowed = slides.size > MAX_ALBUM_DISPLAY_SIZE
|
||||||
|
albumCellContainer.findViewById<TextView>(R.id.album_cell_overflow_text)?.let { overflowText ->
|
||||||
|
// overflowText will be null if !overflowed
|
||||||
|
overflowText.isVisible = overflowed // more than max album size
|
||||||
|
overflowText.text = context.getString(R.string.AlbumThumbnailView_plus, slides.size - MAX_ALBUM_DISPLAY_SIZE)
|
||||||
|
}
|
||||||
|
this.slideSize = slides.size
|
||||||
|
}
|
||||||
|
// iterate binding
|
||||||
|
slides.take(5).forEachIndexed { position, slide ->
|
||||||
|
val thumbnailView = getThumbnailView(position)
|
||||||
|
thumbnailView.setImageResource(glideRequests, slide, isPreview = false)
|
||||||
|
}
|
||||||
|
albumCellBodyParent.isVisible = message.body.isNotEmpty()
|
||||||
|
albumCellBodyText.text = message.body
|
||||||
|
post {
|
||||||
|
// post to await layout of text
|
||||||
|
albumCellBodyText.layout?.let { layout ->
|
||||||
|
val maxEllipsis = (0 until layout.lineCount).maxByOrNull { lineNum -> layout.getEllipsisCount(lineNum) }
|
||||||
|
?: 0
|
||||||
|
// show read more text if at least one line is ellipsized
|
||||||
|
albumCellBodyTextReadMore.isVisible = maxEllipsis > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
|
||||||
|
fun layoutRes(slideCount: Int) = when (slideCount) {
|
||||||
|
1 -> R.layout.album_thumbnail_1 // single
|
||||||
|
2 -> R.layout.album_thumbnail_2// two sidebyside
|
||||||
|
3 -> R.layout.album_thumbnail_3// three stacked
|
||||||
|
4 -> R.layout.album_thumbnail_4// four square
|
||||||
|
5 -> R.layout.album_thumbnail_5//
|
||||||
|
else -> R.layout.album_thumbnail_many// five or more
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getThumbnailView(position: Int): KThumbnailView = when (position) {
|
||||||
|
0 -> albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_1)
|
||||||
|
1 -> albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_2)
|
||||||
|
2 -> albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_3)
|
||||||
|
3 -> albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_4)
|
||||||
|
4 -> albumCellContainer.findViewById<ViewGroup>(R.id.albumCellContainer).findViewById(R.id.album_cell_5)
|
||||||
|
else -> throw Exception("Can't get thumbnail view for non-existent thumbnail at position: $position")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateRadius(isStart: Boolean, isEnd: Boolean, outgoing: Boolean) {
|
||||||
|
val roundedDimen = context.resources.getDimension(R.dimen.message_corner_radius).toInt()
|
||||||
|
val collapsedDimen = context.resources.getDimension(R.dimen.message_corner_collapse_radius).toInt()
|
||||||
|
val (startTop, endTop, startBottom, endBottom) = when {
|
||||||
|
// single message, consistent dimen
|
||||||
|
isStart && isEnd -> intArrayOf(roundedDimen, roundedDimen, roundedDimen, roundedDimen)
|
||||||
|
// start of message cluster, collapsed BL
|
||||||
|
isStart -> intArrayOf(roundedDimen, roundedDimen, collapsedDimen, roundedDimen)
|
||||||
|
// end of message cluster, collapsed TL
|
||||||
|
isEnd -> intArrayOf(collapsedDimen, roundedDimen, roundedDimen, roundedDimen)
|
||||||
|
// else in the middle, no rounding left side
|
||||||
|
else -> intArrayOf(collapsedDimen, roundedDimen, collapsedDimen, roundedDimen)
|
||||||
|
}
|
||||||
|
// TL, TR, BR, BL (CW direction)
|
||||||
|
cornerMask.setRadii(
|
||||||
|
if (!outgoing) startTop else endTop, // TL
|
||||||
|
if (!outgoing) endTop else startTop, // TR
|
||||||
|
if (!outgoing) endBottom else startBottom, // BR
|
||||||
|
if (!outgoing) startBottom else endBottom // BL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.animation.FloatEvaluator
|
import android.animation.FloatEvaluator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -10,6 +11,7 @@ import android.content.res.Resources
|
|||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.os.Bundle
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
@ -81,6 +83,8 @@ import org.thoughtcrime.securesms.giph.ui.GiphyActivity
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.ActivityDispatcher
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.push
|
||||||
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity
|
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity
|
||||||
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity.Companion.selectedContactsKey
|
import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity.Companion.selectedContactsKey
|
||||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities
|
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities
|
||||||
@ -102,7 +106,8 @@ import kotlin.math.*
|
|||||||
// price we pay is a bit of back and forth between the input bar and the conversation activity.
|
// price we pay is a bit of back and forth between the input bar and the conversation activity.
|
||||||
|
|
||||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||||
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ConversationActionModeCallbackDelegate {
|
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
||||||
|
ConversationActionModeCallbackDelegate {
|
||||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||||
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
||||||
private var threadID: Long = -1
|
private var threadID: Long = -1
|
||||||
@ -130,8 +135,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val adapter = ConversationAdapter(
|
val adapter = ConversationAdapter(
|
||||||
this,
|
this,
|
||||||
cursor,
|
cursor,
|
||||||
onItemPress = { message, position, view ->
|
onItemPress = { message, position, view, rawRect ->
|
||||||
handlePress(message, position, view)
|
handlePress(message, position, view, rawRect)
|
||||||
},
|
},
|
||||||
onItemSwipeToReply = { message, position ->
|
onItemSwipeToReply = { message, position ->
|
||||||
handleSwipeToReply(message, position)
|
handleSwipeToReply(message, position)
|
||||||
@ -188,6 +193,28 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
markAllAsRead()
|
markAllAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSystemService(name: String): Any? {
|
||||||
|
if (name == ActivityDispatcher.SERVICE) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
return super.getSystemService(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchIntent(body: (Context) -> Intent?) {
|
||||||
|
val intent = body(this) ?: return
|
||||||
|
push(intent, false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setUpRecyclerView() {
|
private fun setUpRecyclerView() {
|
||||||
conversationRecyclerView.adapter = adapter
|
conversationRecyclerView.adapter = adapter
|
||||||
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
|
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
|
||||||
@ -563,7 +590,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// `position` is the adapter position; not the visual position
|
// `position` is the adapter position; not the visual position
|
||||||
private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView) {
|
private fun handlePress(message: MessageRecord, position: Int, view: VisibleMessageView, rawRect: Rect) {
|
||||||
val actionMode = this.actionMode
|
val actionMode = this.actionMode
|
||||||
if (actionMode != null) {
|
if (actionMode != null) {
|
||||||
adapter.toggleSelection(message, position)
|
adapter.toggleSelection(message, position)
|
||||||
@ -579,7 +606,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
// We have to use onContentClick (rather than a click listener directly on
|
// We have to use onContentClick (rather than a click listener directly on
|
||||||
// the view) so as to not interfere with all the other gestures. Do not add
|
// the view) so as to not interfere with all the other gestures. Do not add
|
||||||
// onClickListeners directly to message content views.
|
// onClickListeners directly to message content views.
|
||||||
view.onContentClick()
|
view.onContentClick(rawRect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import android.graphics.Rect
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
@ -13,7 +14,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
|
||||||
class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView) -> Unit,
|
class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPress: (MessageRecord, Int, VisibleMessageView, Rect) -> Unit,
|
||||||
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit,
|
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int) -> Unit,
|
||||||
private val glide: GlideRequests)
|
private val glide: GlideRequests)
|
||||||
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
|
: CursorRecyclerViewAdapter<ViewHolder>(context, cursor) {
|
||||||
@ -68,7 +69,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
view.messageTimestampTextView.isVisible = isSelected
|
view.messageTimestampTextView.isVisible = isSelected
|
||||||
val position = viewHolder.adapterPosition
|
val position = viewHolder.adapterPosition
|
||||||
view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide)
|
view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide)
|
||||||
view.onPress = { onItemPress(message, viewHolder.adapterPosition, view) }
|
view.onPress = { rawX, rawY -> onItemPress(message, viewHolder.adapterPosition, view, Rect(rawX, rawY, rawX, rawY)) }
|
||||||
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||||
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
@ -19,6 +22,7 @@ import network.loki.messenger.R
|
|||||||
import org.session.libsession.utilities.ThemeUtil
|
import org.session.libsession.utilities.ThemeUtil
|
||||||
import org.session.libsession.utilities.ViewUtil
|
import org.session.libsession.utilities.ViewUtil
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.AlbumThumbnailView
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
@ -27,7 +31,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class VisibleMessageContentView : LinearLayout {
|
class VisibleMessageContentView : LinearLayout {
|
||||||
var onContentClick: (() -> Unit)? = null
|
var onContentClick: ((rawRect: Rect) -> Unit)? = null
|
||||||
var onContentDoubleTap: (() -> Unit)? = null
|
var onContentDoubleTap: (() -> Unit)? = null
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
@ -85,9 +89,17 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
documentView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
documentView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
||||||
mainContainer.addView(documentView)
|
mainContainer.addView(documentView)
|
||||||
} else if (message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty()) {
|
} else if (message is MmsMessageRecord && message.slideDeck.asAttachments().isNotEmpty()) {
|
||||||
val dummyTextView = TextView(context)
|
val albumThumbnailView = AlbumThumbnailView(context)
|
||||||
dummyTextView.text = "asifuygaihsfo"
|
mainContainer.addView(albumThumbnailView)
|
||||||
mainContainer.addView(dummyTextView)
|
// isStart and isEnd of cluster needed for calculating the mask for full bubble image groups
|
||||||
|
// bind after add view because views are inflated and calculated during bind
|
||||||
|
albumThumbnailView.bind(
|
||||||
|
glideRequests = glide,
|
||||||
|
message = message,
|
||||||
|
isStart = isStartOfMessageCluster,
|
||||||
|
isEnd = isEndOfMessageCluster
|
||||||
|
)
|
||||||
|
onContentClick = { albumThumbnailView.calculateHitObject(it, message) }
|
||||||
} else if (message.isOpenGroupInvitation) {
|
} else if (message.isOpenGroupInvitation) {
|
||||||
val openGroupInvitationView = OpenGroupInvitationView(context)
|
val openGroupInvitationView = OpenGroupInvitationView(context)
|
||||||
openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
openGroupInvitationView.bind(message, VisibleMessageContentView.getTextColor(context, message))
|
||||||
|
@ -9,12 +9,10 @@ import android.os.Build
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import kotlinx.android.synthetic.main.view_conversation.view.*
|
|
||||||
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
||||||
import kotlinx.android.synthetic.main.view_visible_message.view.profilePictureView
|
import kotlinx.android.synthetic.main.view_visible_message.view.profilePictureView
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
@ -47,7 +45,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
private var onDoubleTap: (() -> Unit)? = null
|
private var onDoubleTap: (() -> Unit)? = null
|
||||||
var snIsSelected = false
|
var snIsSelected = false
|
||||||
set(value) { field = value; handleIsSelectedChanged()}
|
set(value) { field = value; handleIsSelectedChanged()}
|
||||||
var onPress: (() -> Unit)? = null
|
var onPress: ((rawX: Int, rawY: Int) -> Unit)? = null
|
||||||
var onSwipeToReply: (() -> Unit)? = null
|
var onSwipeToReply: (() -> Unit)? = null
|
||||||
var onLongPress: (() -> Unit)? = null
|
var onLongPress: (() -> Unit)? = null
|
||||||
|
|
||||||
@ -279,7 +277,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
this.pressCallback = null
|
this.pressCallback = null
|
||||||
onDoubleTap?.invoke()
|
onDoubleTap?.invoke()
|
||||||
} else {
|
} else {
|
||||||
val newPressCallback = Runnable { onPress() }
|
val newPressCallback = Runnable { onPress(event.rawX.toInt(), event.rawY.toInt()) }
|
||||||
this.pressCallback = newPressCallback
|
this.pressCallback = newPressCallback
|
||||||
gestureHandler.postDelayed(newPressCallback, VisibleMessageView.maxDoubleTapInterval)
|
gestureHandler.postDelayed(newPressCallback, VisibleMessageView.maxDoubleTapInterval)
|
||||||
}
|
}
|
||||||
@ -307,13 +305,13 @@ class VisibleMessageView : LinearLayout {
|
|||||||
onLongPress?.invoke()
|
onLongPress?.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPress() {
|
fun onContentClick(rawRect: Rect) {
|
||||||
onPress?.invoke()
|
messageContentView.onContentClick?.invoke(rawRect)
|
||||||
pressCallback = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onContentClick() {
|
private fun onPress(rawX: Int, rawY: Int) {
|
||||||
messageContentView.onContentClick?.invoke()
|
onPress?.invoke(rawX, rawY)
|
||||||
|
pressCallback = null
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,197 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import kotlinx.android.synthetic.main.thumbnail_view.view.*
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||||
|
import org.session.libsession.utilities.Util.equals
|
||||||
|
import org.session.libsignal.utilities.ListenableFuture
|
||||||
|
import org.session.libsignal.utilities.SettableFuture
|
||||||
|
import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget
|
||||||
|
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget
|
||||||
|
import org.thoughtcrime.securesms.mms.*
|
||||||
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||||
|
|
||||||
|
open class KThumbnailView: FrameLayout {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val WIDTH = 0
|
||||||
|
private const val HEIGHT = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Lifecycle
|
||||||
|
constructor(context: Context) : super(context) { initialize(null) }
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) }
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) }
|
||||||
|
|
||||||
|
private val image by lazy { thumbnail_image }
|
||||||
|
private val playOverlay by lazy { play_overlay }
|
||||||
|
private val captionIcon by lazy { thumbnail_caption_icon }
|
||||||
|
val loadIndicator: View by lazy { thumbnail_load_indicator }
|
||||||
|
|
||||||
|
private val dimensDelegate = ThumbnailDimensDelegate()
|
||||||
|
|
||||||
|
var thumbnailClickListener: SlideClickListener? = null
|
||||||
|
|
||||||
|
private var slide: Slide? = null
|
||||||
|
private var radius: Int = 0
|
||||||
|
|
||||||
|
private fun initialize(attrs: AttributeSet?) {
|
||||||
|
inflate(context, R.layout.thumbnail_view, this)
|
||||||
|
if (attrs != null) {
|
||||||
|
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0)
|
||||||
|
|
||||||
|
dimensDelegate.setBounds(typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0),
|
||||||
|
typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0))
|
||||||
|
|
||||||
|
radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0)
|
||||||
|
|
||||||
|
typedArray.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
val adjustedDimens = dimensDelegate.resourceSize()
|
||||||
|
if (adjustedDimens[WIDTH] == 0 && adjustedDimens[HEIGHT] == 0) {
|
||||||
|
return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
val finalWidth: Int = adjustedDimens[WIDTH] + paddingLeft + paddingRight
|
||||||
|
val finalHeight: Int = adjustedDimens[HEIGHT] + paddingTop + paddingBottom
|
||||||
|
|
||||||
|
super.onMeasure(
|
||||||
|
MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDefaultWidth() = maxOf(layoutParams?.width ?: 0, 0)
|
||||||
|
private fun getDefaultHeight() = maxOf(layoutParams?.height ?: 0, 0)
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Interaction
|
||||||
|
fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean): ListenableFuture<Boolean> {
|
||||||
|
return setImageResource(glide, slide, isPreview, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setImageResource(glide: GlideRequests, slide: Slide,
|
||||||
|
isPreview: Boolean, naturalWidth: Int,
|
||||||
|
naturalHeight: Int): ListenableFuture<Boolean> {
|
||||||
|
|
||||||
|
val currentSlide = this.slide
|
||||||
|
|
||||||
|
playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() &&
|
||||||
|
(slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview))
|
||||||
|
|
||||||
|
if (equals(currentSlide, slide)) {
|
||||||
|
// don't re-load slide
|
||||||
|
return SettableFuture(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (currentSlide != null && currentSlide.fastPreflightId != null && currentSlide.fastPreflightId == slide.fastPreflightId) {
|
||||||
|
// not reloading slide for fast preflight
|
||||||
|
this.slide = slide
|
||||||
|
}
|
||||||
|
|
||||||
|
this.slide = slide
|
||||||
|
|
||||||
|
captionIcon.isVisible = slide.caption.isPresent
|
||||||
|
loadIndicator.isVisible = slide.isInProgress
|
||||||
|
|
||||||
|
dimensDelegate.setDimens(naturalWidth, naturalHeight)
|
||||||
|
invalidate()
|
||||||
|
|
||||||
|
val result = SettableFuture<Boolean>()
|
||||||
|
|
||||||
|
when {
|
||||||
|
slide.thumbnailUri != null -> {
|
||||||
|
buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(image, result))
|
||||||
|
}
|
||||||
|
slide.hasPlaceholder() -> {
|
||||||
|
buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(image, result))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
glide.clear(image)
|
||||||
|
result.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildThumbnailGlideRequest(glide: GlideRequests, slide: Slide): GlideRequest<Drawable> {
|
||||||
|
|
||||||
|
val dimens = dimensDelegate.resourceSize()
|
||||||
|
|
||||||
|
val request = glide.load(DecryptableUri(slide.thumbnailUri!!))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
|
.let { request ->
|
||||||
|
if (dimens[WIDTH] == 0 || dimens[HEIGHT] == 0) {
|
||||||
|
request.override(getDefaultWidth(), getDefaultHeight())
|
||||||
|
} else {
|
||||||
|
request.override(dimens[WIDTH], dimens[HEIGHT])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
.centerCrop()
|
||||||
|
|
||||||
|
return if (slide.isInProgress) request else request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildPlaceholderGlideRequest(glide: GlideRequests, slide: Slide): GlideRequest<Bitmap> {
|
||||||
|
|
||||||
|
val dimens = dimensDelegate.resourceSize()
|
||||||
|
|
||||||
|
return glide.asBitmap()
|
||||||
|
.load(slide.getPlaceholderRes(context.theme))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.let { request ->
|
||||||
|
if (dimens[WIDTH] == 0 || dimens[HEIGHT] == 0) {
|
||||||
|
request.override(getDefaultWidth(), getDefaultHeight())
|
||||||
|
} else {
|
||||||
|
request.override(dimens[WIDTH], dimens[HEIGHT])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fitCenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun clear(glideRequests: GlideRequests) {
|
||||||
|
glideRequests.clear(image)
|
||||||
|
slide = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setImageResource(glideRequests: GlideRequests, uri: Uri): ListenableFuture<Boolean> {
|
||||||
|
val future = SettableFuture<Boolean>()
|
||||||
|
|
||||||
|
var request: GlideRequest<Drawable> = glideRequests.load(DecryptableUri(uri))
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
|
||||||
|
request = if (radius > 0) {
|
||||||
|
request.transforms(CenterCrop(), RoundedCorners(radius))
|
||||||
|
} else {
|
||||||
|
request.transforms(CenterCrop())
|
||||||
|
}
|
||||||
|
|
||||||
|
request.into(GlideDrawableListeningTarget(image, future))
|
||||||
|
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||||
|
|
||||||
|
class ThumbnailDimensDelegate {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// dimens array constants
|
||||||
|
private const val WIDTH = 0
|
||||||
|
private const val HEIGHT = 1
|
||||||
|
private const val DIMENS_ARRAY_SIZE = 2
|
||||||
|
|
||||||
|
// bounds array constants
|
||||||
|
private const val MIN_WIDTH = 0
|
||||||
|
private const val MIN_HEIGHT = 1
|
||||||
|
private const val MAX_WIDTH = 2
|
||||||
|
private const val MAX_HEIGHT = 3
|
||||||
|
private const val BOUNDS_ARRAY_SIZE = 4
|
||||||
|
|
||||||
|
// const zero int array
|
||||||
|
private val EMPTY_DIMENS = intArrayOf(0,0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val measured: IntArray = IntArray(DIMENS_ARRAY_SIZE)
|
||||||
|
private val dimens: IntArray = IntArray(DIMENS_ARRAY_SIZE)
|
||||||
|
private val bounds: IntArray = IntArray(BOUNDS_ARRAY_SIZE)
|
||||||
|
|
||||||
|
fun resourceSize(): IntArray {
|
||||||
|
if (dimens.all { it == 0 }) {
|
||||||
|
// dimens are (0, 0), don't go any further
|
||||||
|
return EMPTY_DIMENS
|
||||||
|
}
|
||||||
|
|
||||||
|
val naturalWidth = dimens[WIDTH].toDouble()
|
||||||
|
val naturalHeight = dimens[HEIGHT].toDouble()
|
||||||
|
val minWidth = dimens[MIN_WIDTH]
|
||||||
|
val maxWidth = dimens[MAX_WIDTH]
|
||||||
|
val minHeight = dimens[MIN_HEIGHT]
|
||||||
|
val maxHeight = dimens[MAX_HEIGHT]
|
||||||
|
|
||||||
|
// calculate actual measured
|
||||||
|
var measuredWidth: Double = naturalWidth
|
||||||
|
var measuredHeight: Double = naturalHeight
|
||||||
|
|
||||||
|
val widthInBounds = measuredWidth >= minWidth && measuredWidth <= maxWidth
|
||||||
|
val heightInBounds = measuredHeight >= minHeight && measuredHeight <= maxHeight
|
||||||
|
|
||||||
|
if (!widthInBounds || !heightInBounds) {
|
||||||
|
val minWidthRatio: Double = naturalWidth / minWidth
|
||||||
|
val maxWidthRatio: Double = naturalWidth / maxWidth
|
||||||
|
val minHeightRatio: Double = naturalHeight / minHeight
|
||||||
|
val maxHeightRatio: Double = naturalHeight / maxHeight
|
||||||
|
if (maxWidthRatio > 1 || maxHeightRatio > 1) {
|
||||||
|
if (maxWidthRatio >= maxHeightRatio) {
|
||||||
|
measuredWidth /= maxWidthRatio
|
||||||
|
measuredHeight /= maxWidthRatio
|
||||||
|
} else {
|
||||||
|
measuredWidth /= maxHeightRatio
|
||||||
|
measuredHeight /= maxHeightRatio
|
||||||
|
}
|
||||||
|
measuredWidth = Math.max(measuredWidth, minWidth.toDouble())
|
||||||
|
measuredHeight = Math.max(measuredHeight, minHeight.toDouble())
|
||||||
|
} else if (minWidthRatio < 1 || minHeightRatio < 1) {
|
||||||
|
if (minWidthRatio <= minHeightRatio) {
|
||||||
|
measuredWidth /= minWidthRatio
|
||||||
|
measuredHeight /= minWidthRatio
|
||||||
|
} else {
|
||||||
|
measuredWidth /= minHeightRatio
|
||||||
|
measuredHeight /= minHeightRatio
|
||||||
|
}
|
||||||
|
measuredWidth = Math.min(measuredWidth, maxWidth.toDouble())
|
||||||
|
measuredHeight = Math.min(measuredHeight, maxHeight.toDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
measured[WIDTH] = measuredWidth.toInt()
|
||||||
|
measured[HEIGHT] = measuredHeight.toInt()
|
||||||
|
return measured
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBounds(minWidth: Int, minHeight: Int, maxWidth: Int, maxHeight: Int) {
|
||||||
|
bounds[MIN_WIDTH] = minWidth
|
||||||
|
bounds[MIN_HEIGHT] = minHeight
|
||||||
|
bounds[MAX_WIDTH] = maxWidth
|
||||||
|
bounds[MAX_HEIGHT] = maxHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDimens(width: Int, height: Int) {
|
||||||
|
dimens[WIDTH] = width
|
||||||
|
dimens[HEIGHT] = height
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Interpolator
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationSet
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
class ThumbnailProgressBar: View {
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||||
|
|
||||||
|
private val firstX: Double
|
||||||
|
get() = sin(SystemClock.elapsedRealtime() / 300.0) * 1.5
|
||||||
|
|
||||||
|
private val secondX: Double
|
||||||
|
get() = sin(SystemClock.elapsedRealtime() / 300.0 + (Math.PI/4)) * 1.5
|
||||||
|
|
||||||
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
style = Paint.Style.FILL
|
||||||
|
color = ResourcesCompat.getColor(resources, R.color.accent, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val objectRect = Rect()
|
||||||
|
private val drawingRect = Rect()
|
||||||
|
|
||||||
|
override fun dispatchDraw(canvas: Canvas?) {
|
||||||
|
if (canvas == null) return
|
||||||
|
|
||||||
|
getDrawingRect(objectRect)
|
||||||
|
drawingRect.set(objectRect)
|
||||||
|
|
||||||
|
val coercedFX = firstX
|
||||||
|
val coercedSX = secondX
|
||||||
|
|
||||||
|
val firstMeasuredX = objectRect.left + (objectRect.width() * coercedFX)
|
||||||
|
val secondMeasuredX = objectRect.left + (objectRect.width() * coercedSX)
|
||||||
|
|
||||||
|
drawingRect.set(
|
||||||
|
(if (firstMeasuredX < secondMeasuredX) firstMeasuredX else secondMeasuredX).toInt(),
|
||||||
|
objectRect.top,
|
||||||
|
(if (firstMeasuredX < secondMeasuredX) secondMeasuredX else firstMeasuredX).toInt(),
|
||||||
|
objectRect.bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas.drawRect(drawingRect, paint)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||||
|
radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0);
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
} else {
|
} else {
|
||||||
radius = 0;
|
radius = 0;
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package org.thoughtcrime.securesms.loki.utilities
|
package org.thoughtcrime.securesms.loki.utilities
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
@ -52,4 +56,13 @@ fun AppCompatActivity.show(intent: Intent, isForResult: Boolean = false) {
|
|||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out)
|
overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ActivityDispatcher {
|
||||||
|
companion object {
|
||||||
|
const val SERVICE = "ActivityDispatcher_SERVICE"
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
fun get(context: Context) = context.getSystemService(SERVICE) as? ActivityDispatcher
|
||||||
|
}
|
||||||
|
fun dispatchIntent(body: (Context)->Intent?)
|
||||||
}
|
}
|
@ -2,36 +2,20 @@ package org.thoughtcrime.securesms.longmessage;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.style.URLSpan;
|
|
||||||
import android.text.util.Linkify;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
|
||||||
import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|
||||||
|
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
|
||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
import org.session.libsession.utilities.Stub;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
import java.util.Locale;
|
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -43,8 +27,7 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
|
|
||||||
private static final int MAX_DISPLAY_LENGTH = 64 * 1024;
|
private static final int MAX_DISPLAY_LENGTH = 64 * 1024;
|
||||||
|
|
||||||
private Stub<ViewGroup> sentBubble;
|
private TextView textBody;
|
||||||
private Stub<ViewGroup> receivedBubble;
|
|
||||||
|
|
||||||
private LongMessageViewModel viewModel;
|
private LongMessageViewModel viewModel;
|
||||||
|
|
||||||
@ -60,9 +43,7 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||||
super.onCreate(savedInstanceState, ready);
|
super.onCreate(savedInstanceState, ready);
|
||||||
setContentView(R.layout.longmessage_activity);
|
setContentView(R.layout.longmessage_activity);
|
||||||
|
textBody = findViewById(R.id.longmessage_text);
|
||||||
sentBubble = new Stub<>(findViewById(R.id.longmessage_sent_stub));
|
|
||||||
receivedBubble = new Stub<>(findViewById(R.id.longmessage_received_stub));
|
|
||||||
|
|
||||||
initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1), getIntent().getBooleanExtra(KEY_IS_MMS, false));
|
initViewModel(getIntent().getLongExtra(KEY_MESSAGE_ID, -1), getIntent().getBooleanExtra(KEY_IS_MMS, false));
|
||||||
}
|
}
|
||||||
@ -93,36 +74,19 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (message.get().getMessageRecord().isOutgoing()) {
|
if (message.get().getMessageRecord().isOutgoing()) {
|
||||||
getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_your_message));
|
getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_your_message));
|
||||||
} else {
|
} else {
|
||||||
Recipient recipient = message.get().getMessageRecord().getRecipient();
|
Recipient recipient = message.get().getMessageRecord().getRecipient();
|
||||||
String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getAddress().serialize()) ;
|
String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getAddress().serialize());
|
||||||
getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name));
|
getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewGroup bubble;
|
String trimmedBody = getTrimmedBody(message.get().getFullBody());
|
||||||
|
String mentionBody = MentionUtilities.highlightMentions(trimmedBody, message.get().getMessageRecord().getThreadId(), this);
|
||||||
|
|
||||||
if (message.get().getMessageRecord().isOutgoing()) {
|
textBody.setText(mentionBody);
|
||||||
bubble = sentBubble.get();
|
textBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
bubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.message_sent_background_color), PorterDuff.Mode.MULTIPLY);
|
|
||||||
} else {
|
|
||||||
bubble = receivedBubble.get();
|
|
||||||
bubble.getBackground().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.message_received_background_color), PorterDuff.Mode.MULTIPLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextView text = bubble.findViewById(R.id.longmessage_text);
|
|
||||||
ConversationItemFooter footer = bubble.findViewById(R.id.longmessage_footer);
|
|
||||||
|
|
||||||
String trimmedBody = getTrimmedBody(message.get().getFullBody());
|
|
||||||
SpannableString styledBody = linkifyMessageBody(new SpannableString(trimmedBody));
|
|
||||||
|
|
||||||
bubble.setVisibility(View.VISIBLE);
|
|
||||||
text.setText(styledBody);
|
|
||||||
text.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(this));
|
|
||||||
footer.setMessageRecord(message.get().getMessageRecord(), Locale.getDefault());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,15 +95,4 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
: text.substring(0, MAX_DISPLAY_LENGTH);
|
: text.substring(0, MAX_DISPLAY_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpannableString linkifyMessageBody(SpannableString messageBody) {
|
|
||||||
int linkPattern = Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS;
|
|
||||||
boolean hasLinks = Linkify.addLinks(messageBody, linkPattern);
|
|
||||||
|
|
||||||
if (hasLinks) {
|
|
||||||
Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class))
|
|
||||||
.filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL()))
|
|
||||||
.forEach(messageBody::removeSpan);
|
|
||||||
}
|
|
||||||
return messageBody;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
19
app/src/main/res/layout/album_thumbnail_1.xml
Normal file
19
app/src/main/res/layout/album_thumbnail_1.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/album_thumbnail_root"
|
||||||
|
android:layout_width="@dimen/media_bubble_default_dimens"
|
||||||
|
android:layout_height="@dimen/media_bubble_default_dimens">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
|
android:id="@+id/album_cell_1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:minWidth="@dimen/media_bubble_min_width"
|
||||||
|
app:maxWidth="@dimen/media_bubble_max_width"
|
||||||
|
app:minHeight="@dimen/media_bubble_min_height"
|
||||||
|
app:maxHeight="@dimen/media_bubble_max_height"
|
||||||
|
app:thumbnail_radius="1dp"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
@ -7,13 +7,13 @@
|
|||||||
android:layout_width="@dimen/album_total_width"
|
android:layout_width="@dimen/album_total_width"
|
||||||
android:layout_height="@dimen/album_2_total_height">
|
android:layout_height="@dimen/album_2_total_height">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_1"
|
android:id="@+id/album_cell_1"
|
||||||
android:layout_width="@dimen/album_2_cell_width"
|
android:layout_width="@dimen/album_2_cell_width"
|
||||||
android:layout_height="@dimen/album_2_total_height"
|
android:layout_height="@dimen/album_2_total_height"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_2"
|
android:id="@+id/album_cell_2"
|
||||||
android:layout_width="@dimen/album_2_cell_width"
|
android:layout_width="@dimen/album_2_cell_width"
|
||||||
android:layout_height="@dimen/album_2_total_height"
|
android:layout_height="@dimen/album_2_total_height"
|
||||||
|
@ -6,20 +6,20 @@
|
|||||||
android:layout_width="@dimen/album_total_width"
|
android:layout_width="@dimen/album_total_width"
|
||||||
android:layout_height="@dimen/album_3_total_height">
|
android:layout_height="@dimen/album_3_total_height">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_1"
|
android:id="@+id/album_cell_1"
|
||||||
android:layout_width="@dimen/album_3_cell_width_big"
|
android:layout_width="@dimen/album_3_cell_width_big"
|
||||||
android:layout_height="@dimen/album_3_total_height"
|
android:layout_height="@dimen/album_3_total_height"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_2"
|
android:id="@+id/album_cell_2"
|
||||||
android:layout_width="@dimen/album_3_cell_size_small"
|
android:layout_width="@dimen/album_3_cell_size_small"
|
||||||
android:layout_height="@dimen/album_3_cell_size_small"
|
android:layout_height="@dimen/album_3_cell_size_small"
|
||||||
android:layout_gravity="right|end|top"
|
android:layout_gravity="right|end|top"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_3"
|
android:id="@+id/album_cell_3"
|
||||||
android:layout_width="@dimen/album_3_cell_size_small"
|
android:layout_width="@dimen/album_3_cell_size_small"
|
||||||
android:layout_height="@dimen/album_3_cell_size_small"
|
android:layout_height="@dimen/album_3_cell_size_small"
|
||||||
|
@ -6,27 +6,27 @@
|
|||||||
android:layout_width="@dimen/album_total_width"
|
android:layout_width="@dimen/album_total_width"
|
||||||
android:layout_height="@dimen/album_4_total_height">
|
android:layout_height="@dimen/album_4_total_height">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_1"
|
android:id="@+id/album_cell_1"
|
||||||
android:layout_width="@dimen/album_4_cell_size"
|
android:layout_width="@dimen/album_4_cell_size"
|
||||||
android:layout_height="@dimen/album_4_cell_size"
|
android:layout_height="@dimen/album_4_cell_size"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_2"
|
android:id="@+id/album_cell_2"
|
||||||
android:layout_width="@dimen/album_4_cell_size"
|
android:layout_width="@dimen/album_4_cell_size"
|
||||||
android:layout_height="@dimen/album_4_cell_size"
|
android:layout_height="@dimen/album_4_cell_size"
|
||||||
android:layout_gravity="right|end|top"
|
android:layout_gravity="right|end|top"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_3"
|
android:id="@+id/album_cell_3"
|
||||||
android:layout_width="@dimen/album_4_cell_size"
|
android:layout_width="@dimen/album_4_cell_size"
|
||||||
android:layout_height="@dimen/album_4_cell_size"
|
android:layout_height="@dimen/album_4_cell_size"
|
||||||
android:layout_gravity="left|start|bottom"
|
android:layout_gravity="left|start|bottom"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_4"
|
android:id="@+id/album_cell_4"
|
||||||
android:layout_width="@dimen/album_4_cell_size"
|
android:layout_width="@dimen/album_4_cell_size"
|
||||||
android:layout_height="@dimen/album_4_cell_size"
|
android:layout_height="@dimen/album_4_cell_size"
|
||||||
|
@ -6,34 +6,34 @@
|
|||||||
android:layout_width="@dimen/album_total_width"
|
android:layout_width="@dimen/album_total_width"
|
||||||
android:layout_height="@dimen/album_5_total_height">
|
android:layout_height="@dimen/album_5_total_height">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_1"
|
android:id="@+id/album_cell_1"
|
||||||
android:layout_width="@dimen/album_5_cell_size_big"
|
android:layout_width="@dimen/album_5_cell_size_big"
|
||||||
android:layout_height="@dimen/album_5_cell_size_big"
|
android:layout_height="@dimen/album_5_cell_size_big"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_2"
|
android:id="@+id/album_cell_2"
|
||||||
android:layout_width="@dimen/album_5_cell_size_big"
|
android:layout_width="@dimen/album_5_cell_size_big"
|
||||||
android:layout_height="@dimen/album_5_cell_size_big"
|
android:layout_height="@dimen/album_5_cell_size_big"
|
||||||
android:layout_gravity="right|end|top"
|
android:layout_gravity="right|end|top"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_3"
|
android:id="@+id/album_cell_3"
|
||||||
android:layout_width="@dimen/album_5_cell_size_small"
|
android:layout_width="@dimen/album_5_cell_size_small"
|
||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
android:layout_gravity="left|start|bottom"
|
android:layout_gravity="left|start|bottom"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_4"
|
android:id="@+id/album_cell_4"
|
||||||
android:layout_width="@dimen/album_5_cell_size_small"
|
android:layout_width="@dimen/album_5_cell_size_small"
|
||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:layout_gravity="center_horizontal|bottom"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_5"
|
android:id="@+id/album_cell_5"
|
||||||
android:layout_width="@dimen/album_5_cell_size_small"
|
android:layout_width="@dimen/album_5_cell_size_small"
|
||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
|
@ -7,27 +7,27 @@
|
|||||||
android:layout_width="@dimen/album_total_width"
|
android:layout_width="@dimen/album_total_width"
|
||||||
android:layout_height="@dimen/album_5_total_height">
|
android:layout_height="@dimen/album_5_total_height">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_1"
|
android:id="@+id/album_cell_1"
|
||||||
android:layout_width="@dimen/album_5_cell_size_big"
|
android:layout_width="@dimen/album_5_cell_size_big"
|
||||||
android:layout_height="@dimen/album_5_cell_size_big"
|
android:layout_height="@dimen/album_5_cell_size_big"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_2"
|
android:id="@+id/album_cell_2"
|
||||||
android:layout_width="@dimen/album_5_cell_size_big"
|
android:layout_width="@dimen/album_5_cell_size_big"
|
||||||
android:layout_height="@dimen/album_5_cell_size_big"
|
android:layout_height="@dimen/album_5_cell_size_big"
|
||||||
android:layout_gravity="right|end|top"
|
android:layout_gravity="right|end|top"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_3"
|
android:id="@+id/album_cell_3"
|
||||||
android:layout_width="@dimen/album_5_cell_size_small"
|
android:layout_width="@dimen/album_5_cell_size_small"
|
||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
android:layout_gravity="left|start|bottom"
|
android:layout_gravity="left|start|bottom"
|
||||||
app:thumbnail_radius="0dp"/>
|
app:thumbnail_radius="0dp"/>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_4"
|
android:id="@+id/album_cell_4"
|
||||||
android:layout_width="@dimen/album_5_cell_size_small"
|
android:layout_width="@dimen/album_5_cell_size_small"
|
||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
@ -39,7 +39,7 @@
|
|||||||
android:layout_height="@dimen/album_5_cell_size_small"
|
android:layout_height="@dimen/album_5_cell_size_small"
|
||||||
android:layout_gravity="right|end|bottom">
|
android:layout_gravity="right|end|bottom">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView
|
<org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||||
android:id="@+id/album_cell_5"
|
android:id="@+id/album_cell_5"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -51,7 +51,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:textSize="28dp"
|
android:textSize="@dimen/text_size"
|
||||||
android:textColor="@color/core_white"
|
android:textColor="@color/core_white"
|
||||||
android:background="@color/transparent_black_40"
|
android:background="@color/transparent_black_40"
|
||||||
tools:text="+2" />
|
tools:text="+2" />
|
||||||
|
@ -1,21 +1,80 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<merge
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/album_cell_container"
|
android:id="@+id/albumCellContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?conversation_background"/>
|
/>
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/album_transfer_controls_stub"
|
android:layout_alignTop="@+id/albumCellContainer"
|
||||||
|
android:layout_alignStart="@+id/albumCellContainer"
|
||||||
|
android:layout_alignEnd="@+id/albumCellContainer"
|
||||||
|
android:layout_alignBottom="@+id/albumCellContainer"
|
||||||
|
android:id="@+id/albumTransferControlsStub"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout="@layout/transfer_controls_stub" />
|
android:layout="@layout/transfer_controls_stub" />
|
||||||
|
|
||||||
</merge>
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_alignTop="@+id/albumCellContainer"
|
||||||
|
android:layout_alignStart="@+id/albumCellContainer"
|
||||||
|
android:layout_alignEnd="@+id/albumCellContainer"
|
||||||
|
android:layout_alignBottom="@+id/albumCellContainer"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:id="@+id/albumCellBodyParent"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/albumCellShade"
|
||||||
|
android:src="@drawable/image_shade"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/albumCellBodyTextParent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
/>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/albumCellBodyTextParent"
|
||||||
|
android:paddingHorizontal="@dimen/small_spacing"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/albumCellBodyTextReadMore"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<View
|
||||||
|
android:layout_marginStart="@dimen/small_spacing"
|
||||||
|
android:layout_width="@dimen/accent_line_thickness"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginVertical="@dimen/small_spacing"
|
||||||
|
android:background="@color/accent"/>
|
||||||
|
<TextView
|
||||||
|
android:maxLines="4"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:id="@+id/albumCellBodyText"
|
||||||
|
android:textColor="@color/core_white"
|
||||||
|
android:layout_margin="@dimen/small_spacing"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</LinearLayout>
|
||||||
|
<TextView
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
android:id="@+id/albumCellBodyTextReadMore"
|
||||||
|
android:textColor="@color/core_white"
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/ConversationItem_read_more"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -4,23 +4,18 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<ViewStub
|
<TextView
|
||||||
android:id="@+id/longmessage_sent_stub"
|
android:textSize="@dimen/text_size"
|
||||||
|
android:id="@+id/longmessage_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"/>
|
||||||
android:layout="@layout/longmessage_bubble_sent"/>
|
|
||||||
|
|
||||||
<ViewStub
|
</LinearLayout>
|
||||||
android:id="@+id/longmessage_received_stub"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout="@layout/longmessage_bubble_received"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
@ -22,15 +22,12 @@
|
|||||||
android:src="@drawable/ic_caption_28"
|
android:src="@drawable/ic_caption_28"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<ProgressBar
|
<org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailProgressBar
|
||||||
android:id="@+id/thumbnail_load_indicator"
|
android:id="@+id/thumbnail_load_indicator"
|
||||||
android:layout_width="32dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="32dp"
|
android:layout_height="@dimen/accent_line_thickness"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="bottom"
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:indeterminateTint="@android:color/white"
|
|
||||||
android:indeterminateTintMode="src_in"
|
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
@ -23,7 +23,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
override val maxFailureCount: Int = 20
|
override val maxFailureCount: Int = 100
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val KEY: String = "AttachmentDownloadJob"
|
val KEY: String = "AttachmentDownloadJob"
|
||||||
|
Loading…
Reference in New Issue
Block a user