Make the 'see replies' bottom sheet respond to new/deleted messages.

This commit is contained in:
Greyson Parrelli
2022-07-02 14:39:44 -04:00
parent 358d9ca58c
commit 66886dfd7b
5 changed files with 110 additions and 40 deletions

View File

@@ -257,7 +257,7 @@ public class ConversationDataSource implements PagedDataSource<MessageId, Conver
private Collection<MessageId> messageIds = new LinkedList<>();
private Map<MessageId, List<ReactionRecord>> messageIdToReactions = new HashMap<>();
void add(MessageRecord record) {
public void add(MessageRecord record) {
messageIds.add(new MessageId(record.getId(), record.isMms()));
}

View File

@@ -48,6 +48,7 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {
)
private val disposables: LifecycleDisposable = LifecycleDisposable()
private var firstRender: Boolean = true
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.message_quotes_bottom_sheet, container, false)
@@ -86,7 +87,12 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() {
}
messageAdapter.submitList(messages) {
(list.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(messages.size - 1, 100)
if (firstRender) {
(list.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(messages.size - 1, 100)
firstRender = false
} else if (!list.canScrollVertically(1)) {
list.layoutManager?.scrollToPosition(0)
}
}
recyclerViewColorizer.setChatColors(conversationRecipient.chatColors)
}

View File

@@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.conversation.quotes
import android.app.Application
import androidx.annotation.WorkerThread
import io.reactivex.rxjava3.core.Observable
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.util.getQuote
class MessageQuotesRepository {
companion object {
private val TAG = Log.tag(MessageQuotesRepository::class.java)
}
/**
* Retrieves all messages that quote the target message, as well as any messages that quote _those_ messages, recursively.
*/
fun getMessagesInQuoteChain(application: Application, messageId: MessageId): Observable<List<ConversationMessage>> {
return Observable.create { emitter ->
val threadId: Long = SignalDatabase.mmsSms.getThreadId(messageId)
if (threadId < 0) {
Log.w(TAG, "Could not find a threadId for $messageId!")
emitter.onNext(emptyList())
return@create
}
val databaseObserver: DatabaseObserver = ApplicationDependencies.getDatabaseObserver()
val observer = DatabaseObserver.Observer { emitter.onNext(getMessageInQuoteChainSync(application, messageId)) }
databaseObserver.registerConversationObserver(threadId, observer)
emitter.setCancellable { databaseObserver.unregisterObserver(observer) }
emitter.onNext(getMessageInQuoteChainSync(application, messageId))
}
}
@WorkerThread
private fun getMessageInQuoteChainSync(application: Application, messageId: MessageId): List<ConversationMessage> {
val originalRecord: MessageRecord? = if (messageId.mms) {
SignalDatabase.mms.getMessageRecordOrNull(messageId.id)
} else {
SignalDatabase.sms.getMessageRecordOrNull(messageId.id)
}
if (originalRecord == null) {
return emptyList()
}
val replyRecords: List<MessageRecord> = SignalDatabase.mmsSms.getAllMessagesThatQuote(messageId)
val replies: List<ConversationMessage> = ConversationDataSource.ReactionHelper()
.apply {
addAll(replyRecords)
fetchReactions()
}
.buildUpdatedModels(replyRecords)
.map { replyRecord ->
val replyQuote: Quote? = replyRecord.getQuote()
if (replyQuote != null && replyQuote.id == originalRecord.dateSent) {
(replyRecord as MediaMmsMessageRecord).withoutQuote()
} else {
replyRecord
}
}
.map { ConversationMessageFactory.createWithUnresolvedData(application, it) }
val originalMessage: List<ConversationMessage> = ConversationDataSource.ReactionHelper()
.apply {
add(originalRecord)
fetchReactions()
}
.buildUpdatedModels(listOf(originalRecord))
.map { ConversationMessageFactory.createWithUnresolvedData(application, it, it.getDisplayBody(application), 0) }
return replies + originalMessage
}
}

View File

@@ -7,18 +7,12 @@ import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.ConversationDataSource
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.GroupAuthorNameColorHelper
import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.getQuote
class MessageQuotesViewModel(
application: Application,
@@ -27,40 +21,11 @@ class MessageQuotesViewModel(
) : AndroidViewModel(application) {
private val groupAuthorNameColorHelper = GroupAuthorNameColorHelper()
private val repository = MessageQuotesRepository()
fun getMessages(): Observable<List<ConversationMessage>> {
return Observable.create<List<ConversationMessage>> { emitter ->
val originalRecord: MessageRecord? = if (messageId.mms) {
SignalDatabase.mms.getMessageRecordOrNull(messageId.id)
} else {
SignalDatabase.sms.getMessageRecordOrNull(messageId.id)
}
if (originalRecord == null) {
emitter.onNext(emptyList())
return@create
}
val replyRecords: List<MessageRecord> = SignalDatabase.mmsSms.getAllMessagesThatQuote(messageId)
val reactionHelper = ConversationDataSource.ReactionHelper()
reactionHelper.addAll(replyRecords)
reactionHelper.fetchReactions()
val replies = reactionHelper.buildUpdatedModels(replyRecords)
.map { replyRecord ->
val replyQuote: Quote? = replyRecord.getQuote()
if (replyQuote != null && replyQuote.id == originalRecord.dateSent) {
(replyRecord as MediaMmsMessageRecord).withoutQuote()
} else {
replyRecord
}
}
.map { ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(getApplication(), it) }
val originalMessage: ConversationMessage = ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(getApplication(), originalRecord, originalRecord.getDisplayBody(getApplication()), 0)
emitter.onNext(replies + listOf(originalMessage))
}
return repository
.getMessagesInQuoteChain(getApplication(), messageId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}

View File

@@ -426,6 +426,18 @@ public class MmsSmsDatabase extends Database {
SignalDatabase.mms().hasMeaningfulMessage(threadId);
}
public long getThreadId(MessageId messageId) {
if (messageId.isMms()) {
return SignalDatabase.mms().getThreadIdForMessage(messageId.getId());
} else {
return SignalDatabase.sms().getThreadIdForMessage(messageId.getId());
}
}
/**
* This is currently only used in an old migration and shouldn't be used by anyone else, just because it flat-out isn't correct.
*/
@Deprecated
public long getThreadForMessageId(long messageId) {
long id = SignalDatabase.sms().getThreadIdForMessage(messageId);