mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-21 21:11:29 +00:00
Merge remote-tracking branch 'upstream/ui' into ui
This commit is contained in:
commit
4a8c5f5946
@ -52,6 +52,10 @@ import org.thoughtcrime.securesms.util.DateUtils
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
// Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually
|
||||||
|
// part of the conversation activity layout. This is just because it makes the layout a lot simpler. The
|
||||||
|
// 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, ConversationRecyclerViewDelegate {
|
InputBarRecordingViewDelegate, ConversationRecyclerViewDelegate {
|
||||||
private val scrollButtonFullVisibilityThreshold by lazy { toPx(120.0f, resources) }
|
private val scrollButtonFullVisibilityThreshold by lazy { toPx(120.0f, resources) }
|
||||||
@ -120,6 +124,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
getLatestOpenGroupInfoIfNeeded()
|
getLatestOpenGroupInfoIfNeeded()
|
||||||
setUpBlockedBanner()
|
setUpBlockedBanner()
|
||||||
setUpLinkPreviewObserver()
|
setUpLinkPreviewObserver()
|
||||||
|
scrollToFirstUnreadMessage()
|
||||||
|
markAllAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -234,12 +240,20 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
if (previewState == null) return@observe
|
if (previewState == null) return@observe
|
||||||
if (previewState.isLoading) {
|
if (previewState.isLoading) {
|
||||||
inputBar.draftLinkPreview()
|
inputBar.draftLinkPreview()
|
||||||
} else {
|
} else if (previewState.linkPreview.isPresent) {
|
||||||
inputBar.updateLinkPreviewDraft(glide, previewState.linkPreview.get())
|
inputBar.updateLinkPreviewDraft(glide, previewState.linkPreview.get())
|
||||||
|
} else {
|
||||||
|
inputBar.cancelLinkPreviewDraft()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scrollToFirstUnreadMessage() {
|
||||||
|
val lastSeenTimestamp = DatabaseFactory.getThreadDatabase(this).getLastSeenAndHasSent(threadID).first()
|
||||||
|
val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return
|
||||||
|
conversationRecyclerView.scrollToPosition(lastSeenItemPosition)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||||
ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) }
|
ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) }
|
||||||
super.onPrepareOptionsMenu(menu)
|
super.onPrepareOptionsMenu(menu)
|
||||||
@ -253,6 +267,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating & Animation
|
// region Updating & Animation
|
||||||
|
private fun markAllAsRead() {
|
||||||
|
DatabaseFactory.getThreadDatabase(this).setRead(threadID, true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun inputBarHeightChanged(newValue: Int) {
|
override fun inputBarHeightChanged(newValue: Int) {
|
||||||
@Suppress("NAME_SHADOWING") val newValue = max(newValue, resources.getDimension(R.dimen.input_bar_height).roundToInt())
|
@Suppress("NAME_SHADOWING") val newValue = max(newValue, resources.getDimension(R.dimen.input_bar_height).roundToInt())
|
||||||
// 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message
|
// 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message
|
||||||
|
@ -2,20 +2,16 @@ 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.drawable.ColorDrawable
|
|
||||||
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
|
||||||
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
import kotlinx.android.synthetic.main.view_visible_message.view.*
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import java.lang.IllegalStateException
|
|
||||||
|
|
||||||
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) -> 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,
|
||||||
@ -110,4 +106,15 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
if (selectedItems.contains(message)) selectedItems.remove(message) else selectedItems.add(message)
|
if (selectedItems.contains(message)) selectedItems.remove(message) else selectedItems.add(message)
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findLastSeenItemPosition(lastSeenTimestamp: Long): Int? {
|
||||||
|
val cursor = this.cursor
|
||||||
|
if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null
|
||||||
|
for (i in 0 until itemCount) {
|
||||||
|
cursor.moveToPosition(i)
|
||||||
|
val messageRecord = messageDB.readerFor(cursor).current
|
||||||
|
if (messageRecord.isOutgoing || messageRecord.dateReceived <= lastSeenTimestamp) { return i }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
@ -8,31 +8,43 @@ import androidx.core.view.isVisible
|
|||||||
import kotlinx.android.synthetic.main.view_link_preview_draft.view.*
|
import kotlinx.android.synthetic.main.view_link_preview_draft.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||||
|
|
||||||
class LinkPreviewDraftView : LinearLayout {
|
class LinkPreviewDraftView : LinearLayout {
|
||||||
|
var delegate: LinkPreviewDraftViewDelegate? = null
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
constructor(context: Context) : super(context) { initialize() }
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
|
// Start out with the loader showing and the content view hidden
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_link_preview_draft, this)
|
LayoutInflater.from(context).inflate(R.layout.view_link_preview_draft, this)
|
||||||
linkPreviewDraftContainer.isVisible = false
|
linkPreviewDraftContainer.isVisible = false
|
||||||
thumbnailImageView.clipToOutline = true
|
thumbnailImageView.clipToOutline = true
|
||||||
|
linkPreviewDraftCancelButton.setOnClickListener { cancel() }
|
||||||
}
|
}
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
fun update(glide: GlideRequests, linkPreview: LinkPreview) {
|
fun update(glide: GlideRequests, linkPreview: LinkPreview) {
|
||||||
|
// Hide the loader and show the content view
|
||||||
linkPreviewDraftContainer.isVisible = true
|
linkPreviewDraftContainer.isVisible = true
|
||||||
linkPreviewDraftLoader.isVisible = false
|
linkPreviewDraftLoader.isVisible = false
|
||||||
|
thumbnailImageView.radius = toPx(4, resources)
|
||||||
if (linkPreview.getThumbnail().isPresent) {
|
if (linkPreview.getThumbnail().isPresent) {
|
||||||
|
// This internally fetches the thumbnail
|
||||||
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
||||||
}
|
}
|
||||||
linkPreviewDraftTitleTextView.text = linkPreview.title
|
linkPreviewDraftTitleTextView.text = linkPreview.title
|
||||||
}
|
}
|
||||||
// endregion
|
|
||||||
|
private fun cancel() {
|
||||||
|
delegate?.cancelLinkPreviewDraft()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LinkPreviewDraftViewDelegate {
|
||||||
|
|
||||||
|
fun cancelLinkPreviewDraft()
|
||||||
}
|
}
|
@ -14,6 +14,7 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
|
||||||
|
/** Shown upon sending a message to a user that's blocked. */
|
||||||
class BlockedDialog(private val recipient: Recipient) : BaseDialog() {
|
class BlockedDialog(private val recipient: Recipient) : BaseDialog() {
|
||||||
|
|
||||||
override fun setContentView(builder: AlertDialog.Builder) {
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
@ -13,6 +13,8 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
|
||||||
|
/** Shown when receiving media from a contact for the first time, to confirm that
|
||||||
|
* they are to be trusted and files sent by them are to be downloaded. */
|
||||||
class DownloadDialog(private val recipient: Recipient) : BaseDialog() {
|
class DownloadDialog(private val recipient: Recipient) : BaseDialog() {
|
||||||
|
|
||||||
override fun setContentView(builder: AlertDialog.Builder) {
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
@ -11,6 +11,7 @@ import network.loki.messenger.R
|
|||||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
|
|
||||||
|
/** Shown upon tapping an open group invitation. */
|
||||||
class JoinOpenGroupDialog(private val openGroup: OpenGroupV2) : BaseDialog() {
|
class JoinOpenGroupDialog(private val openGroup: OpenGroupV2) : BaseDialog() {
|
||||||
|
|
||||||
override fun setContentView(builder: AlertDialog.Builder) {
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
@ -6,6 +6,8 @@ import kotlinx.android.synthetic.main.dialog_link_preview.view.*
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
|
|
||||||
|
/** Shown the first time the user inputs a URL that could generate a link preview, to
|
||||||
|
* let them know that Session offers the ability to send and receive link previews. */
|
||||||
class LinkPreviewDialog() : BaseDialog() {
|
class LinkPreviewDialog() : BaseDialog() {
|
||||||
|
|
||||||
override fun setContentView(builder: AlertDialog.Builder) {
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
@ -10,6 +10,7 @@ import kotlinx.android.synthetic.main.dialog_open_url.view.*
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
|
|
||||||
|
/** Shown upon tapping a URL. */
|
||||||
class OpenURLDialog(private val url: String) : BaseDialog() {
|
class OpenURLDialog(private val url: String) : BaseDialog() {
|
||||||
|
|
||||||
override fun setContentView(builder: AlertDialog.Builder) {
|
override fun setContentView(builder: AlertDialog.Builder) {
|
||||||
|
@ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.view_quote.view.*
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||||
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView
|
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftViewDelegate
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
|
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
|
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
@ -22,7 +23,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, LinkPreviewDraftViewDelegate {
|
||||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||||
private val vMargin by lazy { toDp(4, resources) }
|
private val vMargin by lazy { toDp(4, resources) }
|
||||||
private val minHeight by lazy { toPx(56, resources) }
|
private val minHeight by lazy { toPx(56, resources) }
|
||||||
@ -95,6 +96,9 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
|||||||
delegate?.showVoiceMessageUI()
|
delegate?.showVoiceMessageUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drafting quotes and drafting link previews is mutually exclusive, i.e. you can't draft
|
||||||
|
// a quote and a link preview at the same time.
|
||||||
|
|
||||||
fun draftQuote(message: MessageRecord) {
|
fun draftQuote(message: MessageRecord) {
|
||||||
linkPreviewDraftView = null
|
linkPreviewDraftView = null
|
||||||
inputBarAdditionalContentContainer.removeAllViews()
|
inputBarAdditionalContentContainer.removeAllViews()
|
||||||
@ -127,6 +131,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
|||||||
val linkPreviewDraftHeight = toPx(88, resources)
|
val linkPreviewDraftHeight = toPx(88, resources)
|
||||||
inputBarAdditionalContentContainer.removeAllViews()
|
inputBarAdditionalContentContainer.removeAllViews()
|
||||||
val linkPreviewDraftView = LinkPreviewDraftView(context)
|
val linkPreviewDraftView = LinkPreviewDraftView(context)
|
||||||
|
linkPreviewDraftView.delegate = this
|
||||||
this.linkPreviewDraftView = linkPreviewDraftView
|
this.linkPreviewDraftView = linkPreviewDraftView
|
||||||
inputBarAdditionalContentContainer.addView(linkPreviewDraftView)
|
inputBarAdditionalContentContainer.addView(linkPreviewDraftView)
|
||||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + linkPreviewDraftHeight
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + linkPreviewDraftHeight
|
||||||
@ -138,6 +143,13 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
|||||||
val linkPreviewDraftView = this.linkPreviewDraftView ?: return
|
val linkPreviewDraftView = this.linkPreviewDraftView ?: return
|
||||||
linkPreviewDraftView.update(glide, linkPreview)
|
linkPreviewDraftView.update(glide, linkPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun cancelLinkPreviewDraft() {
|
||||||
|
inputBarAdditionalContentContainer.removeAllViews()
|
||||||
|
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight)
|
||||||
|
additionalContentHeight = 0
|
||||||
|
setHeight(newHeight)
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class LinkPreviewView : LinearLayout {
|
|||||||
// Thumbnail
|
// Thumbnail
|
||||||
val linkPreview = message.linkPreviews.first()
|
val linkPreview = message.linkPreviews.first()
|
||||||
if (linkPreview.getThumbnail().isPresent) {
|
if (linkPreview.getThumbnail().isPresent) {
|
||||||
|
// This internally fetches the thumbnail
|
||||||
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
||||||
}
|
}
|
||||||
// Title
|
// Title
|
||||||
|
@ -70,7 +70,7 @@ public class ThumbnailView extends FrameLayout {
|
|||||||
private SlidesClickedListener downloadClickListener = null;
|
private SlidesClickedListener downloadClickListener = null;
|
||||||
private Slide slide = null;
|
private Slide slide = null;
|
||||||
|
|
||||||
private int radius;
|
public int radius;
|
||||||
|
|
||||||
public ThumbnailView(Context context) {
|
public ThumbnailView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user