mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
feat: Added an unread marker and search result focus highlighting
This commit is contained in:
parent
dce89a0f5f
commit
a689e38f33
@ -299,6 +299,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
val adapter = ConversationAdapter(
|
||||
this,
|
||||
cursor,
|
||||
storage.getLastSeen(viewModel.threadId),
|
||||
reverseMessageList,
|
||||
onItemPress = { message, position, view, event ->
|
||||
handlePress(message, position, view, event)
|
||||
@ -342,7 +343,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
private val messageToScrollTimestamp = AtomicLong(-1)
|
||||
private val messageToScrollAuthor = AtomicReference<Address?>(null)
|
||||
private val firstLoad = AtomicBoolean(true)
|
||||
private val forceHighlightNextLoad = AtomicInteger(-1)
|
||||
|
||||
private lateinit var reactionDelegate: ConversationReactionDelegate
|
||||
private val reactWithAnyEmojiStartPage = -1
|
||||
@ -426,15 +426,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
// transitioning to the activity
|
||||
weakActivity.get()?.adapter ?: return@launch
|
||||
|
||||
// 'Get' instead of 'GetAndSet' here because we want to trigger the highlight in 'onFirstLoad'
|
||||
// by triggering 'jumpToMessage' using these values
|
||||
val messageTimestamp = messageToScrollTimestamp.get()
|
||||
val author = messageToScrollAuthor.get()
|
||||
val targetPosition = if (author != null && messageTimestamp >= 0) mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, messageTimestamp, author, reverseMessageList) else -1
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
setUpRecyclerView()
|
||||
setUpTypingObserver()
|
||||
setUpRecipientObserver()
|
||||
getLatestOpenGroupInfoIfNeeded()
|
||||
setUpSearchResultObserver()
|
||||
|
||||
if (author != null && messageTimestamp >= 0 && targetPosition >= 0) {
|
||||
binding?.conversationRecyclerView?.scrollToPosition(targetPosition)
|
||||
}
|
||||
else {
|
||||
scrollToFirstUnreadMessageIfNeeded(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val reactionOverlayStub: Stub<ConversationReactionOverlay> =
|
||||
ViewUtil.findStubById(this, R.id.conversation_reaction_scrubber_stub)
|
||||
@ -514,33 +526,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
|
||||
if (author != null && messageTimestamp >= 0) {
|
||||
jumpToMessage(author, messageTimestamp, null)
|
||||
jumpToMessage(author, messageTimestamp, true, null)
|
||||
}
|
||||
else if (firstLoad.getAndSet(false)) {
|
||||
// We can't actually just 'shouldHighlight = true' here because any unread messages will
|
||||
// immediately be marked as ready triggering a reload of the cursor
|
||||
val lastSeenItemPosition = scrollToFirstUnreadMessageIfNeeded(true)
|
||||
scrollToFirstUnreadMessageIfNeeded(true)
|
||||
handleRecyclerViewScrolled()
|
||||
|
||||
if (initialUnreadCount > 0 && lastSeenItemPosition != null) {
|
||||
forceHighlightNextLoad.set(lastSeenItemPosition)
|
||||
}
|
||||
}
|
||||
else if (oldCount != newCount) {
|
||||
handleRecyclerViewScrolled()
|
||||
}
|
||||
else {
|
||||
// Really annoying but if a message gets marked as read during the initial load it'll
|
||||
// immediately result in a subsequent load of the cursor, if we trigger the highlight
|
||||
// within the 'firstLoad' it generally ends up getting repositioned as the views get
|
||||
// recycled and the wrong view is highlighted - by doing it on the subsequent load the
|
||||
// correct view is highlighted
|
||||
val forceHighlightPosition = forceHighlightNextLoad.getAndSet(-1)
|
||||
|
||||
if (forceHighlightPosition != -1) {
|
||||
highlightViewAtPosition(forceHighlightPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePlaceholder()
|
||||
}
|
||||
@ -2064,7 +2058,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
if (result == null) return@Observer
|
||||
if (result.getResults().isNotEmpty()) {
|
||||
result.getResults()[result.position]?.let {
|
||||
jumpToMessage(it.messageRecipient.address, it.sentTimestampMs) {
|
||||
jumpToMessage(it.messageRecipient.address, it.sentTimestampMs, true) {
|
||||
searchViewModel.onMissingResult() }
|
||||
}
|
||||
}
|
||||
@ -2101,15 +2095,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
this.searchViewModel.onMoveDown()
|
||||
}
|
||||
|
||||
private fun jumpToMessage(author: Address, timestamp: Long, onMessageNotFound: Runnable?) {
|
||||
private fun jumpToMessage(author: Address, timestamp: Long, highlight: Boolean, onMessageNotFound: Runnable?) {
|
||||
SimpleTask.run(lifecycle, {
|
||||
mmsSmsDb.getMessagePositionInConversation(viewModel.threadId, timestamp, author, reverseMessageList)
|
||||
}) { p: Int -> moveToMessagePosition(p, onMessageNotFound) }
|
||||
}) { p: Int -> moveToMessagePosition(p, highlight, onMessageNotFound) }
|
||||
}
|
||||
|
||||
private fun moveToMessagePosition(position: Int, onMessageNotFound: Runnable?) {
|
||||
private fun moveToMessagePosition(position: Int, highlight: Boolean, onMessageNotFound: Runnable?) {
|
||||
if (position >= 0) {
|
||||
binding?.conversationRecyclerView?.scrollToPosition(position)
|
||||
|
||||
if (highlight) {
|
||||
runOnUiThread {
|
||||
highlightViewAtPosition(position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onMessageNotFound?.run()
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
|
||||
class ConversationAdapter(
|
||||
context: Context,
|
||||
cursor: Cursor,
|
||||
private val originalLastSeen: Long,
|
||||
private val isReversed: Boolean,
|
||||
private val onItemPress: (MessageRecord, Int, VisibleMessageView, MotionEvent) -> Unit,
|
||||
private val onItemSwipeToReply: (MessageRecord, Int) -> Unit,
|
||||
@ -130,6 +131,7 @@ class ConversationAdapter(
|
||||
searchQuery,
|
||||
contact,
|
||||
senderId,
|
||||
originalLastSeen,
|
||||
visibleMessageViewDelegate,
|
||||
onAttachmentNeedsDownload
|
||||
)
|
||||
|
@ -127,6 +127,7 @@ class VisibleMessageView : LinearLayout {
|
||||
searchQuery: String?,
|
||||
contact: Contact?,
|
||||
senderSessionID: String,
|
||||
originalLastSeen: Long,
|
||||
delegate: VisibleMessageViewDelegate?,
|
||||
onAttachmentNeedsDownload: (Long, Long) -> Unit
|
||||
) {
|
||||
@ -193,6 +194,8 @@ class VisibleMessageView : LinearLayout {
|
||||
val contactContext =
|
||||
if (thread.isOpenGroupRecipient) ContactContext.OPEN_GROUP else ContactContext.REGULAR
|
||||
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
||||
// Unread marker
|
||||
binding.unreadMarkerContainer.isVisible = message.timestamp > originalLastSeen && (previous == null || previous.timestamp <= originalLastSeen)
|
||||
// Date break
|
||||
val showDateBreak = isStartOfMessageCluster || snIsSelected
|
||||
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
|
||||
|
@ -8,6 +8,46 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/unreadMarkerContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/small_spacing"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="@dimen/medium_spacing"
|
||||
android:layout_marginEnd="@dimen/small_spacing"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/unreadMarker"
|
||||
android:background="?android:colorAccent" />
|
||||
<TextView
|
||||
android:id="@+id/unreadMarker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:text="@string/unread_marker"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:colorAccent"
|
||||
android:textSize="@dimen/small_font_size"
|
||||
android:textStyle="bold" />
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginStart="@dimen/small_spacing"
|
||||
android:layout_marginEnd="@dimen/medium_spacing"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/unreadMarker"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:background="?android:colorAccent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateBreakTextView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1026,4 +1026,6 @@
|
||||
<string name="activity_conversation_empty_state_note_to_self">You have no messages in Note to Self.</string>
|
||||
<string name="activity_conversation_empty_state_default">You have no messages from <b>%s</b>.\nSend a message to start the conversation!</string>
|
||||
|
||||
<string name="unread_marker">Unread Messages</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user