mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 18:43:49 +00:00
Merge branch 'ui' of https://github.com/oxen-io/session-android into ui
This commit is contained in:
commit
ba1099d276
@ -61,6 +61,7 @@ import org.session.libsession.utilities.MediaTypes
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.concurrent.SimpleTask
|
import org.session.libsession.utilities.concurrent.SimpleTask
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
||||||
import org.session.libsignal.utilities.ListenableFuture
|
import org.session.libsignal.utilities.ListenableFuture
|
||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
@ -75,6 +76,7 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.mentions.MentionCand
|
|||||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
|
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallback
|
||||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
|
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCallbackDelegate
|
||||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
|
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate
|
||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
||||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
||||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
||||||
@ -113,7 +115,8 @@ import kotlin.math.*
|
|||||||
|
|
||||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||||
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher,
|
||||||
ConversationActionModeCallbackDelegate, SearchBottomBar.EventListener {
|
ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener,
|
||||||
|
SearchBottomBar.EventListener {
|
||||||
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
|
||||||
@ -136,6 +139,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
var searchViewModel: SearchViewModel? = null
|
var searchViewModel: SearchViewModel? = null
|
||||||
var searchViewItem: MenuItem? = null
|
var searchViewItem: MenuItem? = null
|
||||||
|
|
||||||
|
private val isScrolledToBottom: Boolean
|
||||||
|
get() {
|
||||||
|
val position = layoutManager.findFirstCompletelyVisibleItemPosition()
|
||||||
|
return position == 0
|
||||||
|
}
|
||||||
|
|
||||||
private val layoutManager: LinearLayoutManager
|
private val layoutManager: LinearLayoutManager
|
||||||
get() { return conversationRecyclerView.layoutManager as LinearLayoutManager }
|
get() { return conversationRecyclerView.layoutManager as LinearLayoutManager }
|
||||||
|
|
||||||
@ -155,6 +164,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
},
|
},
|
||||||
glide
|
glide
|
||||||
)
|
)
|
||||||
|
adapter.visibleMessageContentViewDelegate = this
|
||||||
adapter
|
adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +204,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
setUpTypingObserver()
|
setUpTypingObserver()
|
||||||
|
setUpRecipientObserver()
|
||||||
updateSubtitle()
|
updateSubtitle()
|
||||||
getLatestOpenGroupInfoIfNeeded()
|
getLatestOpenGroupInfoIfNeeded()
|
||||||
setUpBlockedBanner()
|
setUpBlockedBanner()
|
||||||
@ -309,7 +320,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
private fun setUpTypingObserver() {
|
private fun setUpTypingObserver() {
|
||||||
ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state ->
|
ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state ->
|
||||||
val recipients = if (state != null) state.typists else listOf()
|
val recipients = if (state != null) state.typists else listOf()
|
||||||
typingIndicatorViewContainer.isVisible = recipients.isNotEmpty()
|
// FIXME: Also checking isScrolledToBottom is a quick fix for an issue where the
|
||||||
|
// typing indicator overlays the recycler view when scrolled up
|
||||||
|
typingIndicatorViewContainer.isVisible = recipients.isNotEmpty() && isScrolledToBottom
|
||||||
typingIndicatorViewContainer.setTypists(recipients)
|
typingIndicatorViewContainer.setTypists(recipients)
|
||||||
inputBarHeightChanged(inputBar.height)
|
inputBarHeightChanged(inputBar.height)
|
||||||
}
|
}
|
||||||
@ -323,6 +336,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setUpRecipientObserver() {
|
||||||
|
thread.addListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getLatestOpenGroupInfoIfNeeded() {
|
private fun getLatestOpenGroupInfoIfNeeded() {
|
||||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return
|
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return
|
||||||
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
||||||
@ -376,7 +393,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating & Animation
|
override fun onModified(recipient: Recipient) {
|
||||||
|
if (thread.isContactRecipient) {
|
||||||
|
blockedBanner.isVisible = thread.isBlocked
|
||||||
|
}
|
||||||
|
updateSubtitle()
|
||||||
|
}
|
||||||
|
|
||||||
private fun markAllAsRead() {
|
private fun markAllAsRead() {
|
||||||
val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true)
|
val messages = DatabaseFactory.getThreadDatabase(this).setRead(threadID, true)
|
||||||
if (thread.isGroupRecipient) {
|
if (thread.isGroupRecipient) {
|
||||||
@ -577,8 +600,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRecyclerViewScrolled() {
|
private fun handleRecyclerViewScrolled() {
|
||||||
val position = layoutManager.findFirstCompletelyVisibleItemPosition()
|
val alpha = if (!isScrolledToBottom) 1.0f else 0.0f
|
||||||
val alpha = if (position > 0) 1.0f else 0.0f
|
// FIXME: Checking isScrolledToBottom is a quick fix for an issue where the
|
||||||
|
// typing indicator overlays the recycler view when scrolled up
|
||||||
|
val wasTypingIndicatorVisibleBefore = typingIndicatorViewContainer.isVisible
|
||||||
|
typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
|
||||||
|
val isTypingIndicatorVisibleAfter = typingIndicatorViewContainer.isVisible
|
||||||
|
if (isTypingIndicatorVisibleAfter != wasTypingIndicatorVisibleBefore) {
|
||||||
|
inputBarHeightChanged(inputBar.height)
|
||||||
|
}
|
||||||
scrollToBottomButton.alpha = alpha
|
scrollToBottomButton.alpha = alpha
|
||||||
unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition())
|
unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition())
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
@ -748,6 +778,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
this.previousText = newText
|
this.previousText = newText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun scrollToMessageIfPossible(timestamp: Long) {
|
||||||
|
val lastSeenItemPosition = adapter.getItemPositionForTimestamp(timestamp) ?: return
|
||||||
|
conversationRecyclerView.scrollToPosition(lastSeenItemPosition)
|
||||||
|
}
|
||||||
|
|
||||||
override fun sendMessage() {
|
override fun sendMessage() {
|
||||||
if (thread.isContactRecipient && thread.isBlocked) {
|
if (thread.isContactRecipient && thread.isBlocked) {
|
||||||
BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog")
|
BlockedDialog(thread).show(supportFragmentManager, "Blocked Dialog")
|
||||||
@ -916,10 +951,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun startRecordingVoiceMessage() {
|
override fun startRecordingVoiceMessage() {
|
||||||
showVoiceMessageUI()
|
if (Permissions.hasAll(this, Manifest.permission.RECORD_AUDIO)) {
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
showVoiceMessageUI()
|
||||||
audioRecorder.startRecording()
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each
|
audioRecorder.startRecording()
|
||||||
|
stopAudioHandler.postDelayed(stopVoiceMessageRecordingTask, 60000) // Limit voice messages to 1 minute each
|
||||||
|
} else {
|
||||||
|
Permissions.with(this)
|
||||||
|
.request(Manifest.permission.RECORD_AUDIO)
|
||||||
|
.withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48)
|
||||||
|
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages))
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendVoiceMessage() {
|
override fun sendVoiceMessage() {
|
||||||
|
@ -8,6 +8,7 @@ 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 org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
import org.thoughtcrime.securesms.conversation.v2.messages.ControlMessageView
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentViewDelegate
|
||||||
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
|
||||||
@ -21,6 +22,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
private val messageDB = DatabaseFactory.getMmsSmsDatabase(context)
|
private val messageDB = DatabaseFactory.getMmsSmsDatabase(context)
|
||||||
var selectedItems = mutableSetOf<MessageRecord>()
|
var selectedItems = mutableSetOf<MessageRecord>()
|
||||||
private var searchQuery: String? = null
|
private var searchQuery: String? = null
|
||||||
|
var visibleMessageContentViewDelegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
sealed class ViewType(val rawValue: Int) {
|
sealed class ViewType(val rawValue: Int) {
|
||||||
object Visible : ViewType(0)
|
object Visible : ViewType(0)
|
||||||
@ -73,6 +75,7 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
view.onPress = { rawX, rawY -> onItemPress(message, viewHolder.adapterPosition, view, Rect(rawX, rawY, rawX, rawY)) }
|
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) }
|
||||||
|
view.contentViewDelegate = visibleMessageContentViewDelegate
|
||||||
}
|
}
|
||||||
is ControlMessageViewHolder -> viewHolder.view.bind(message)
|
is ControlMessageViewHolder -> viewHolder.view.bind(message)
|
||||||
}
|
}
|
||||||
@ -114,8 +117,19 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
|||||||
if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null
|
if (lastSeenTimestamp <= 0L || cursor == null || !isActiveCursor) return null
|
||||||
for (i in 0 until itemCount) {
|
for (i in 0 until itemCount) {
|
||||||
cursor.moveToPosition(i)
|
cursor.moveToPosition(i)
|
||||||
val messageRecord = messageDB.readerFor(cursor).current
|
val message = messageDB.readerFor(cursor).current
|
||||||
if (messageRecord.isOutgoing || messageRecord.dateReceived <= lastSeenTimestamp) { return i }
|
if (message.isOutgoing || message.dateReceived <= lastSeenTimestamp) { return i }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItemPositionForTimestamp(timestamp: Long): Int? {
|
||||||
|
val cursor = this.cursor
|
||||||
|
if (timestamp <= 0L || cursor == null || !isActiveCursor) return null
|
||||||
|
for (i in 0 until itemCount) {
|
||||||
|
cursor.moveToPosition(i)
|
||||||
|
val message = messageDB.readerFor(cursor).current
|
||||||
|
if (message.dateSent == timestamp) { return i }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import android.text.SpannableStringBuilder
|
|||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||||
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||||
@ -33,8 +35,11 @@ class JoinOpenGroupDialog(private val name: String, private val url: String) : B
|
|||||||
|
|
||||||
private fun join() {
|
private fun join() {
|
||||||
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
val openGroup = OpenGroupUrlParser.parseUrl(url)
|
||||||
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, requireContext())
|
val activity = requireContext() as AppCompatActivity
|
||||||
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(requireContext())
|
ThreadUtils.queue {
|
||||||
|
OpenGroupManager.add(openGroup.server, openGroup.room, openGroup.serverPublicKey, activity)
|
||||||
|
MultiDeviceProtocol.forceSyncConfigurationNowIfNeeded(activity)
|
||||||
|
}
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -318,13 +318,11 @@ object ConversationMenuHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun unmute(context: Context, thread: Recipient) {
|
private fun unmute(context: Context, thread: Recipient) {
|
||||||
thread.setMuted(0)
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, 0)
|
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mute(context: Context, thread: Recipient) {
|
private fun mute(context: Context, thread: Recipient) {
|
||||||
MuteDialog.show(context) { until: Long ->
|
MuteDialog.show(context) { until: Long ->
|
||||||
thread.setMuted(until)
|
|
||||||
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until)
|
DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,9 @@ class ControlMessageView : LinearLayout {
|
|||||||
if (message.isExpirationTimerUpdate) {
|
if (message.isExpirationTimerUpdate) {
|
||||||
iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme))
|
iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_timer, context.theme))
|
||||||
iconImageView.visibility = View.VISIBLE
|
iconImageView.visibility = View.VISIBLE
|
||||||
|
} else if (message.isMediaSavedNotification) {
|
||||||
|
iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme))
|
||||||
|
iconImageView.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
textView.text = message.getDisplayBody(context)
|
textView.text = message.getDisplayBody(context)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ import androidx.core.graphics.BlendModeColorFilterCompat
|
|||||||
import androidx.core.graphics.BlendModeCompat
|
import androidx.core.graphics.BlendModeCompat
|
||||||
import androidx.core.text.getSpans
|
import androidx.core.text.getSpans
|
||||||
import androidx.core.text.toSpannable
|
import androidx.core.text.toSpannable
|
||||||
|
import androidx.core.text.util.LinkifyCompat
|
||||||
|
import kotlinx.android.synthetic.main.view_link_preview.view.*
|
||||||
import kotlinx.android.synthetic.main.view_visible_message_content.view.*
|
import kotlinx.android.synthetic.main.view_visible_message_content.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.ThemeUtil
|
import org.session.libsession.utilities.ThemeUtil
|
||||||
@ -43,6 +45,7 @@ import kotlin.math.roundToInt
|
|||||||
class VisibleMessageContentView : LinearLayout {
|
class VisibleMessageContentView : LinearLayout {
|
||||||
var onContentClick: ((rawRect: Rect) -> Unit)? = null
|
var onContentClick: ((rawRect: Rect) -> Unit)? = null
|
||||||
var onContentDoubleTap: (() -> Unit)? = null
|
var onContentDoubleTap: (() -> Unit)? = null
|
||||||
|
var delegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor(context: Context) : super(context) { initialize() }
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
@ -89,6 +92,13 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
val bodyTextView = VisibleMessageContentView.getBodyTextView(context, message, searchQuery)
|
||||||
ViewUtil.setPaddingTop(bodyTextView, 0)
|
ViewUtil.setPaddingTop(bodyTextView, 0)
|
||||||
mainContainer.addView(bodyTextView)
|
mainContainer.addView(bodyTextView)
|
||||||
|
onContentClick = { rect ->
|
||||||
|
val r = Rect()
|
||||||
|
quoteView.getGlobalVisibleRect(r)
|
||||||
|
if (r.contains(rect)) {
|
||||||
|
delegate?.scrollToMessageIfPossible(quote.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) {
|
} else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) {
|
||||||
val voiceMessageView = VoiceMessageView(context)
|
val voiceMessageView = VoiceMessageView(context)
|
||||||
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
voiceMessageView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster)
|
||||||
@ -192,4 +202,9 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VisibleMessageContentViewDelegate {
|
||||||
|
|
||||||
|
fun scrollToMessageIfPossible(timestamp: Long)
|
||||||
}
|
}
|
@ -48,6 +48,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
var onPress: ((rawX: Int, rawY: Int) -> 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
|
||||||
|
var contentViewDelegate: VisibleMessageContentViewDelegate? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val swipeToReplyThreshold = 80.0f // dp
|
const val swipeToReplyThreshold = 80.0f // dp
|
||||||
@ -139,6 +140,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
if (profilePictureContainer.visibility != View.GONE) { maxWidth -= profilePictureContainer.width }
|
||||||
// Populate content view
|
// Populate content view
|
||||||
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery)
|
messageContentView.bind(message, isStartOfMessageCluster, isEndOfMessageCluster, glide, maxWidth, thread, searchQuery)
|
||||||
|
messageContentView.delegate = contentViewDelegate
|
||||||
onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() }
|
onDoubleTap = { messageContentView.onContentDoubleTap?.invoke() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +241,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
} else {
|
} else {
|
||||||
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
longPressCallback?.let { gestureHandler.removeCallbacks(it) }
|
||||||
}
|
}
|
||||||
|
if (translationX > 0) { return } // Only allow swipes to the left
|
||||||
// The idea here is to asymptotically approach a maximum drag distance
|
// The idea here is to asymptotically approach a maximum drag distance
|
||||||
val damping = 50.0f
|
val damping = 50.0f
|
||||||
val sign = -1.0f
|
val sign = -1.0f
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/menu_badge_icon"
|
android:id="@+id/menu_badge_icon"
|
||||||
android:layout_width="20dp"
|
android:layout_width="16dp"
|
||||||
android:layout_height="20dp"
|
android:layout_height="16dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_timer"
|
android:src="@drawable/ic_timer"
|
||||||
android:background="@color/transparent"
|
android:background="@color/transparent"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingVertical="@dimen/medium_spacing"
|
android:paddingVertical="@dimen/medium_spacing"
|
||||||
android:paddingHorizontal="@dimen/massive_spacing"
|
android:paddingHorizontal="@dimen/massive_spacing"
|
||||||
@ -12,7 +13,8 @@
|
|||||||
android:id="@+id/iconImageView"
|
android:id="@+id/iconImageView"
|
||||||
android:layout_width="12dp"
|
android:layout_width="12dp"
|
||||||
android:layout_height="12dp"
|
android:layout_height="12dp"
|
||||||
android:layout_marginBottom="@dimen/small_spacing" />
|
android:layout_marginBottom="@dimen/small_spacing"
|
||||||
|
app:tint="@color/text" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/textView"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user