mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 08:09:12 +01:00
Turn on collapsing chat events for internal users.
This commit is contained in:
committed by
Cody Henthorne
parent
94e3dabc20
commit
c3b8768570
@@ -148,5 +148,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||
void onViewPollClicked(long messageId);
|
||||
void onToggleVote(@NonNull PollRecord poll, @NonNull PollOption pollOption, Boolean isChecked);
|
||||
void onViewPinnedMessage(long messageId);
|
||||
void onExpandEvents(long messageId);
|
||||
void onCollapseEvents(long messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.Multiselect;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection;
|
||||
import org.thoughtcrime.securesms.conversation.v2.computed.FormattedDate;
|
||||
import org.thoughtcrime.securesms.database.BodyRangeUtil;
|
||||
import org.thoughtcrime.securesms.database.CollapsedState;
|
||||
import org.thoughtcrime.securesms.database.MentionUtil;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
@@ -54,6 +55,7 @@ public class ConversationMessage {
|
||||
@Nullable private final MemberLabel memberLabel;
|
||||
@Nullable private final MemberLabel quoteMemberLabel;
|
||||
@Nullable private final Recipient deletedByRecipient;
|
||||
private final int collapsedSize;
|
||||
|
||||
private ConversationMessage(@NonNull MessageRecord messageRecord,
|
||||
@Nullable CharSequence body,
|
||||
@@ -65,7 +67,8 @@ public class ConversationMessage {
|
||||
@NonNull ComputedProperties computedProperties,
|
||||
@Nullable MemberLabel memberLabel,
|
||||
@Nullable MemberLabel quoteMemberLabel,
|
||||
@Nullable Recipient deletedByRecipient)
|
||||
@Nullable Recipient deletedByRecipient,
|
||||
int collapsedSize)
|
||||
{
|
||||
this.messageRecord = messageRecord;
|
||||
this.hasBeenQuoted = hasBeenQuoted;
|
||||
@@ -77,6 +80,7 @@ public class ConversationMessage {
|
||||
this.memberLabel = memberLabel;
|
||||
this.quoteMemberLabel = quoteMemberLabel;
|
||||
this.deletedByRecipient = deletedByRecipient;
|
||||
this.collapsedSize = collapsedSize;
|
||||
|
||||
if (body != null) {
|
||||
this.body = SpannableString.valueOf(body);
|
||||
@@ -125,6 +129,10 @@ public class ConversationMessage {
|
||||
return deletedByRecipient;
|
||||
}
|
||||
|
||||
public int getCollapsedSize() {
|
||||
return collapsedSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@@ -282,6 +290,11 @@ public class ConversationMessage {
|
||||
MemberLabel quoteMemberLabel = getQuoteMemberLabel(messageRecord, threadRecipient, prefetchedLabels);
|
||||
Recipient deletedBy = messageRecord.getDeletedBy() != null ? Recipient.resolved(messageRecord.getDeletedBy()) : null;
|
||||
|
||||
int collapsedSize = 0;
|
||||
if (CollapsedState.isHead(messageRecord.getCollapsedState())) {
|
||||
collapsedSize = SignalDatabase.messages().getCollapsedCount(messageRecord.getId());
|
||||
}
|
||||
|
||||
return new ConversationMessage(messageRecord,
|
||||
styledAndMentionBody != null ? styledAndMentionBody : mentionsUpdate != null ? mentionsUpdate.getBody() : body,
|
||||
mentionsUpdate != null ? mentionsUpdate.getMentions() : null,
|
||||
@@ -292,7 +305,8 @@ public class ConversationMessage {
|
||||
new ComputedProperties(formattedDate),
|
||||
memberLabel,
|
||||
quoteMemberLabel,
|
||||
deletedBy);
|
||||
deletedBy,
|
||||
collapsedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,11 +12,13 @@ import android.text.method.LinkMovementMethod;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
@@ -36,6 +38,8 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversation.colors.Colorizer;
|
||||
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog;
|
||||
import org.thoughtcrime.securesms.database.CollapsibleEvents;
|
||||
import org.thoughtcrime.securesms.database.CollapsedState;
|
||||
import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil;
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
|
||||
@@ -43,6 +47,7 @@ import org.thoughtcrime.securesms.database.model.LiveUpdateMessage;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.UpdateDescription;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
|
||||
import org.thoughtcrime.securesms.fonts.SignalSymbols;
|
||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipient;
|
||||
@@ -93,6 +98,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
private MessageRecord messageRecord;
|
||||
private boolean isMessageRequestAccepted;
|
||||
private EventListener eventListener;
|
||||
private Button collapsedButton;
|
||||
|
||||
private final UpdateObserver updateObserver = new UpdateObserver();
|
||||
|
||||
@@ -124,9 +130,10 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.actionButton = findViewById(R.id.conversation_update_action);
|
||||
this.background = findViewById(R.id.conversation_update_background);
|
||||
this.body = findViewById(R.id.conversation_update_body);
|
||||
this.actionButton = findViewById(R.id.conversation_update_action);
|
||||
this.background = findViewById(R.id.conversation_update_background);
|
||||
this.collapsedButton = findViewById(R.id.conversation_update_collapsed);
|
||||
|
||||
body.setOnClickListener(v -> performClick());
|
||||
body.setOnLongClickListener(v -> performLongClick());
|
||||
@@ -210,6 +217,7 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
hasWallpaper);
|
||||
|
||||
presentActionButton(hasWallpaper, conversationMessage.getMessageRecord().isReleaseChannelDonationRequest());
|
||||
presentCollapsedHead(conversationMessage.getMessageRecord().getCollapsedState());
|
||||
|
||||
updateSelectedState();
|
||||
}
|
||||
@@ -442,7 +450,9 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
}
|
||||
|
||||
private void setBodyText(@Nullable CharSequence text) {
|
||||
if (text == null) {
|
||||
if (CollapsedState.isCollapsed(conversationMessage.getMessageRecord().getCollapsedState()) && conversationMessage.getCollapsedSize() > 1) {
|
||||
body.setVisibility(GONE);
|
||||
} else if (text == null) {
|
||||
body.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
body.setText(text);
|
||||
@@ -459,7 +469,10 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
|
||||
setSelected(!Sets.intersection(multiselectParts, batchSelected).isEmpty());
|
||||
|
||||
if (conversationMessage.getMessageRecord().isGroupV1MigrationEvent() &&
|
||||
if (CollapsedState.isCollapsed(conversationMessage.getMessageRecord().getCollapsedState()) && conversationMessage.getCollapsedSize() > 1) {
|
||||
actionButton.setVisibility(GONE);
|
||||
actionButton.setOnClickListener(null);
|
||||
} else if (conversationMessage.getMessageRecord().isGroupV1MigrationEvent() &&
|
||||
(!nextMessageRecord.isPresent() || !nextMessageRecord.get().isGroupV1MigrationEvent()))
|
||||
{
|
||||
actionButton.setText(R.string.ConversationUpdateItem_learn_more);
|
||||
@@ -807,6 +820,49 @@ public final class ConversationUpdateItem extends FrameLayout
|
||||
}
|
||||
}
|
||||
|
||||
private void presentCollapsedHead(CollapsedState collapsedState) {
|
||||
CollapsibleEvents.CollapsibleType collapsibleType = CollapsibleEvents.getCollapsibleType(messageRecord.getType(), messageRecord.getMessageExtras());
|
||||
if (CollapsedState.isHead(collapsedState) && conversationMessage.getCollapsedSize() > 1 && collapsibleType != null) {
|
||||
SpannableStringBuilder text = new SpannableStringBuilder()
|
||||
.append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.BOLD, getCollapsibleSymbol(collapsibleType), org.signal.core.ui.R.color.signal_colorOnSurfaceVariant))
|
||||
.append(" ")
|
||||
.append(getContext().getString(getCollapsibleString(collapsibleType), conversationMessage.getCollapsedSize()))
|
||||
.append(" ")
|
||||
.append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.BOLD, collapsedState == CollapsedState.HEAD_EXPANDED ? SignalSymbols.Glyph.CHEVRON_UP : SignalSymbols.Glyph.CHEVRON_DOWN, org.signal.core.ui.R.color.signal_colorOnSurfaceVariant));
|
||||
collapsedButton.setText(text);
|
||||
collapsedButton.setOnClickListener(v -> {
|
||||
if (eventListener != null) {
|
||||
if (CollapsedState.isCollapsed(collapsedState)) {
|
||||
eventListener.onExpandEvents(conversationMessage.getMessageRecord().getId());
|
||||
} else {
|
||||
eventListener.onCollapseEvents(conversationMessage.getMessageRecord().getId());
|
||||
}
|
||||
} else {
|
||||
passthroughClickListener.onClick(v);
|
||||
}
|
||||
});
|
||||
collapsedButton.setVisibility(VISIBLE);
|
||||
} else {
|
||||
collapsedButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private @StringRes int getCollapsibleString(CollapsibleEvents.CollapsibleType type) {
|
||||
return switch (type) {
|
||||
case CALL_EVENT -> R.string.CollapsedEvent__call_event;
|
||||
case DISAPPEARING_TIMER -> R.string.CollapsedEvent__disappearing_timer;
|
||||
case GROUP_UPDATE -> R.string.CollapsedEvent__group_update;
|
||||
};
|
||||
}
|
||||
|
||||
private SignalSymbols.Glyph getCollapsibleSymbol(CollapsibleEvents.CollapsibleType type) {
|
||||
return switch (type) {
|
||||
case CALL_EVENT -> SignalSymbols.Glyph.PHONE;
|
||||
case DISAPPEARING_TIMER -> SignalSymbols.Glyph.TIMER;
|
||||
case GROUP_UPDATE -> SignalSymbols.Glyph.GROUP;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isSameType(@NonNull MessageRecord current, @NonNull MessageRecord candidate) {
|
||||
return (current.isGroupUpdate() && candidate.isGroupUpdate()) ||
|
||||
(current.isProfileChange() && candidate.isProfileChange()) ||
|
||||
|
||||
@@ -94,4 +94,6 @@ object EmptyConversationAdapterListener : ConversationAdapter.ItemClickListener
|
||||
override fun onViewPollClicked(messageId: Long) = Unit
|
||||
override fun onToggleVote(poll: PollRecord, pollOption: PollOption, isChecked: Boolean?) = Unit
|
||||
override fun onViewPinnedMessage(messageId: Long) = Unit
|
||||
override fun onExpandEvents(messageId: Long) = Unit
|
||||
override fun onCollapseEvents(messageId: Long) = Unit
|
||||
}
|
||||
|
||||
@@ -811,6 +811,7 @@ class ConversationFragment :
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
viewModel.collapseAllEvents()
|
||||
keyboardEvents?.let {
|
||||
container.removeInputListener(it)
|
||||
container.removeKeyboardStateListener(it)
|
||||
@@ -3767,6 +3768,14 @@ class ConversationFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExpandEvents(messageId: Long) {
|
||||
viewModel.onExpandEvents(messageId)
|
||||
}
|
||||
|
||||
override fun onCollapseEvents(messageId: Long) {
|
||||
viewModel.onCollapseEvents(messageId)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: MultiselectPart) {
|
||||
if (isActionModeStarted()) {
|
||||
adapter.toggleSelection(item)
|
||||
|
||||
@@ -842,6 +842,18 @@ class ConversationRepository(
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun collapseEvents(messageId: Long) {
|
||||
SignalDatabase.messages.collapseEvents(messageId)
|
||||
}
|
||||
|
||||
fun collapseAllEvents() {
|
||||
SignalDatabase.messages.collapseAllEvents()
|
||||
}
|
||||
|
||||
fun expandEvents(messageId: Long) {
|
||||
SignalDatabase.messages.expandEvents(messageId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Glide target for a contact photo which expects an error drawable, and publishes
|
||||
* the result to the given emitter.
|
||||
|
||||
@@ -43,6 +43,7 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.rx3.asFlow
|
||||
import org.signal.core.models.ServiceId
|
||||
import org.signal.core.util.concurrent.SignalDispatchers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.paging.ProxyPagingController
|
||||
@@ -433,6 +434,20 @@ class ConversationViewModel(
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
fun onCollapseEvents(messageId: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.collapseEvents(messageId)
|
||||
pagingController.onDataInvalidated()
|
||||
}
|
||||
}
|
||||
|
||||
fun onExpandEvents(messageId: Long) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
repository.expandEvents(messageId)
|
||||
pagingController.onDataInvalidated()
|
||||
}
|
||||
}
|
||||
|
||||
fun onChatBoundsChanged(bounds: Rect) {
|
||||
chatBounds.onNext(bounds)
|
||||
}
|
||||
@@ -785,6 +800,12 @@ class ConversationViewModel(
|
||||
_plaintextExportState.value = PlaintextExportState.None
|
||||
}
|
||||
|
||||
fun collapseAllEvents() {
|
||||
viewModelScope.launch(SignalDispatchers.IO) {
|
||||
repository.collapseAllEvents()
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface PlaintextExportState {
|
||||
data object None : PlaintextExportState
|
||||
data object Preparing : PlaintextExportState
|
||||
|
||||
@@ -97,7 +97,7 @@ class ConversationDataSource(
|
||||
val stopwatch = Stopwatch(title = "load($start, $length), thread $threadId", decimalPlaces = 2)
|
||||
var records: MutableList<MessageRecord> = ArrayList(length)
|
||||
|
||||
MessageTable.mmsReaderFor(SignalDatabase.messages.getConversation(threadId, start.toLong(), length.toLong()))
|
||||
MessageTable.mmsReaderFor(SignalDatabase.messages.getConversation(threadId, start.toLong(), length.toLong(), filterCollapsed = true))
|
||||
.use { reader ->
|
||||
reader.forEach { record ->
|
||||
if (cancellationSignal.isCanceled) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import org.signal.core.util.LongSerializer
|
||||
|
||||
/**
|
||||
* Tracks the collapsed state of a message. Non-update messages are always NONE, while
|
||||
* update messages can either be the first update message of a collapsed set (HEAD_*)
|
||||
* or part of the collapsed set (COLLAPSED/EXPANDED)
|
||||
*
|
||||
* eg in the message table:
|
||||
* id | msg | collapsed_state | collapsed_head_id
|
||||
* 1 | [Group Update 1] | HEAD_COLLAPSED | 1
|
||||
* 2 | [Group Update 3] | COLLAPSED | 1
|
||||
* 3 | Regular message | NONE | null
|
||||
* 4 | [Group Update 4] | HEAD_COLLAPSED | 4
|
||||
*
|
||||
* and when expanded,
|
||||
* id | msg | collapsed_state | collapsed_head_id
|
||||
* 1 | [Group Update 1] | HEAD_EXPANDED | 1
|
||||
* 2 | [Group Update 3] | EXPANDED | 1
|
||||
* 3 | Regular message | NONE | null
|
||||
* 4 | [Group Update 4] | HEAD_COLLAPSED | 4
|
||||
*/
|
||||
enum class CollapsedState(val id: Long) {
|
||||
NONE(0),
|
||||
HEAD_COLLAPSED(1),
|
||||
HEAD_EXPANDED(2),
|
||||
COLLAPSED(3),
|
||||
EXPANDED(4),
|
||||
PENDING_COLLAPSED(5);
|
||||
|
||||
companion object Serializer : LongSerializer<CollapsedState> {
|
||||
override fun serialize(data: CollapsedState): Long {
|
||||
return data.id
|
||||
}
|
||||
|
||||
override fun deserialize(input: Long): CollapsedState {
|
||||
return CollapsedState.entries.firstOrNull { it.id == input } ?: NONE
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isHead(state: CollapsedState): Boolean {
|
||||
return state == HEAD_COLLAPSED || state == HEAD_EXPANDED
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isCollapsed(state: CollapsedState): Boolean {
|
||||
return state == HEAD_COLLAPSED || state == COLLAPSED
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.thoughtcrime.securesms.database
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
|
||||
|
||||
/**
|
||||
* Utility functions to track the different collapsing types and what type a message is
|
||||
*/
|
||||
object CollapsibleEvents {
|
||||
|
||||
@JvmStatic
|
||||
fun isCollapsibleType(type: Long, messageExtras: MessageExtras?): Boolean {
|
||||
return getCollapsibleType(type, messageExtras) != null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getCollapsibleType(type: Long, messageExtras: MessageExtras?): CollapsibleType? {
|
||||
if (MessageTypes.isCallLog(type)) {
|
||||
return CollapsibleType.CALL_EVENT
|
||||
}
|
||||
|
||||
if (MessageTypes.isExpirationTimerUpdate(type)) {
|
||||
return CollapsibleType.DISAPPEARING_TIMER
|
||||
}
|
||||
|
||||
if (messageExtras?.gv2UpdateDescription != null) {
|
||||
val groupChangeUpdate = messageExtras.gv2UpdateDescription.groupChangeUpdate
|
||||
return if (groupChangeUpdate?.updates?.any { it.groupExpirationTimerUpdate != null } == true) {
|
||||
CollapsibleType.DISAPPEARING_TIMER
|
||||
} else if (groupChangeUpdate?.updates?.none { it.groupTerminateChangeUpdate != null } == true) {
|
||||
CollapsibleType.GROUP_UPDATE
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (MessageTypes.isProfileChange(type)) {
|
||||
return CollapsibleType.GROUP_UPDATE
|
||||
}
|
||||
|
||||
if (MessageTypes.isIdentityUpdate(type) || MessageTypes.isIdentityVerified(type) || MessageTypes.isIdentityDefault(type)) {
|
||||
return CollapsibleType.GROUP_UPDATE
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
enum class CollapsibleType {
|
||||
DISAPPEARING_TIMER,
|
||||
GROUP_UPDATE,
|
||||
CALL_EVENT
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceUtil
|
||||
import org.thoughtcrime.securesms.sms.GroupV2UpdateMessageUtil
|
||||
import org.thoughtcrime.securesms.stories.Stories.isFeatureEnabled
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
|
||||
@@ -228,6 +229,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val DELETED_BY = "deleted_by"
|
||||
const val STORY_ARCHIVED = "story_archived"
|
||||
const val STARRED = "starred"
|
||||
const val COLLAPSED_STATE = "collapsed_state"
|
||||
const val COLLAPSED_HEAD_ID = "collapsed_head_id"
|
||||
|
||||
const val QUOTE_NOT_PRESENT_ID = 0L
|
||||
const val QUOTE_TARGET_MISSING_ID = -1L
|
||||
@@ -301,7 +304,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
$PINNED_AT INTEGER DEFAULT 0,
|
||||
$DELETED_BY INTEGER DEFAULT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
|
||||
$STORY_ARCHIVED INTEGER DEFAULT 0,
|
||||
$STARRED INTEGER DEFAULT 0
|
||||
$STARRED INTEGER DEFAULT 0,
|
||||
$COLLAPSED_STATE INTEGER DEFAULT 0,
|
||||
$COLLAPSED_HEAD_ID INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -329,7 +334,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
"CREATE INDEX IF NOT EXISTS message_to_recipient_id_index ON $TABLE_NAME ($TO_RECIPIENT_ID)",
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS message_unique_sent_from_thread ON $TABLE_NAME ($DATE_SENT, $FROM_RECIPIENT_ID, $THREAD_ID)",
|
||||
// This index is created specifically for getting the number of messages in a thread and therefore needs to be kept in sync with that query
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL",
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}",
|
||||
// This index is created specifically for getting the number of unread messages in a thread and therefore needs to be kept in sync with that query
|
||||
"CREATE INDEX IF NOT EXISTS $INDEX_THREAD_UNREAD_COUNT ON $TABLE_NAME ($THREAD_ID) WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $ORIGINAL_MESSAGE_ID IS NULL AND $READ = 0",
|
||||
"CREATE INDEX IF NOT EXISTS message_votes_unread_index ON $TABLE_NAME ($VOTES_UNREAD)",
|
||||
@@ -337,7 +342,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
"CREATE INDEX IF NOT EXISTS message_pinned_at_index ON $TABLE_NAME ($PINNED_AT)",
|
||||
"CREATE INDEX IF NOT EXISTS message_deleted_by_index ON $TABLE_NAME ($DELETED_BY)",
|
||||
"CREATE INDEX IF NOT EXISTS message_story_archived_index ON $TABLE_NAME ($STORY_ARCHIVED, $STORY_TYPE, $DATE_SENT) WHERE $STORY_TYPE > 0 AND $STORY_ARCHIVED > 0",
|
||||
"CREATE INDEX IF NOT EXISTS message_starred_index ON $TABLE_NAME ($STARRED) WHERE $STARRED > 0"
|
||||
"CREATE INDEX IF NOT EXISTS message_starred_index ON $TABLE_NAME ($STARRED) WHERE $STARRED > 0",
|
||||
"CREATE INDEX IF NOT EXISTS message_collapsed_state_index ON $TABLE_NAME ($COLLAPSED_STATE)",
|
||||
"CREATE INDEX IF NOT EXISTS message_collapsed_head_id_index ON $TABLE_NAME ($COLLAPSED_HEAD_ID)"
|
||||
)
|
||||
|
||||
private val MMS_PROJECTION_BASE = arrayOf(
|
||||
@@ -394,7 +401,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
VOTES_LAST_SEEN,
|
||||
PINNED_UNTIL,
|
||||
DELETED_BY,
|
||||
STARRED
|
||||
STARRED,
|
||||
COLLAPSED_STATE,
|
||||
COLLAPSED_HEAD_ID
|
||||
)
|
||||
|
||||
private val MMS_PROJECTION: Array<String> = MMS_PROJECTION_BASE
|
||||
@@ -862,12 +871,13 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val recipient = Recipient.resolved(recipientId)
|
||||
val threadIdResult = threads.getOrCreateThreadIdResultFor(recipient.id, recipient.isGroup)
|
||||
val threadId = threadIdResult.threadId
|
||||
val dateReceived = System.currentTimeMillis()
|
||||
|
||||
val values = contentValuesOf(
|
||||
FROM_RECIPIENT_ID to if (outgoing) Recipient.self().id.serialize() else recipientId.serialize(),
|
||||
FROM_DEVICE_ID to 1,
|
||||
TO_RECIPIENT_ID to if (outgoing) recipientId.serialize() else Recipient.self().id.serialize(),
|
||||
DATE_RECEIVED to System.currentTimeMillis(),
|
||||
DATE_RECEIVED to dateReceived,
|
||||
DATE_SENT to timestamp,
|
||||
READ to 1,
|
||||
TYPE to type,
|
||||
@@ -876,6 +886,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
val messageId = writableDatabase.insert(TABLE_NAME, null, values)
|
||||
|
||||
maybeCollapseMessage(db = writableDatabase, messageId = messageId, threadId = threadId, dateReceived = dateReceived, messageExtras = null, messageType = type)
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
@@ -889,6 +901,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
|
||||
fun updateCallLog(messageId: Long, type: Long) {
|
||||
val message = getMessageRecordOrNull(messageId = messageId)
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
@@ -902,6 +915,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
threads.update(threadId, true)
|
||||
|
||||
if (message?.collapsedState == CollapsedState.NONE) {
|
||||
maybeCollapseMessage(db = writableDatabase, messageId = messageId, threadId = threadId, dateReceived = message.dateReceived, messageExtras = message.messageExtras, messageType = type)
|
||||
}
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
AppDependencies.databaseObserver.notifyMessageUpdateObservers(MessageId(messageId))
|
||||
}
|
||||
@@ -943,6 +960,12 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
)
|
||||
|
||||
val messageId = MessageId(db.insert(TABLE_NAME, null, values))
|
||||
|
||||
val isActiveCall = joinedUuids.isNotEmpty() || isIncomingGroupCallRingingOnLocalDevice
|
||||
if (!isActiveCall) {
|
||||
maybeCollapseMessage(db = db, messageId = messageId.id, threadId = threadId, dateReceived = timestamp, messageExtras = null, messageType = MessageTypes.GROUP_CALL_TYPE)
|
||||
}
|
||||
|
||||
threads.incrementUnread(threadId, 1, 0)
|
||||
threads.update(threadId, true)
|
||||
|
||||
@@ -1044,6 +1067,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val query = buildTrueUpdateQuery(ID_WHERE, buildArgs(messageId), contentValues)
|
||||
val updated = db.update(TABLE_NAME, contentValues, query.where, query.whereArgs) > 0
|
||||
|
||||
if (inCallUuids.isEmpty() && message.collapsedState == CollapsedState.NONE) {
|
||||
maybeCollapseMessage(db = db, messageId = messageId, threadId = message.threadId, dateReceived = message.dateReceived, messageExtras = message.messageExtras, messageType = message.type)
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
notifyConversationListeners(message.threadId)
|
||||
}
|
||||
@@ -1091,6 +1118,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val query = buildTrueUpdateQuery(ID_WHERE, buildArgs(record.id), contentValues)
|
||||
val updated = db.update(TABLE_NAME, contentValues, query.where, query.whereArgs) > 0
|
||||
|
||||
if (inCallUuids.isEmpty() && record.collapsedState == CollapsedState.NONE) {
|
||||
maybeCollapseMessage(db = db, messageId = record.id, threadId = record.threadId, dateReceived = record.dateReceived, messageExtras = record.messageExtras, messageType = record.type)
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
notifyConversationListeners(threadId)
|
||||
}
|
||||
@@ -1142,18 +1173,20 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
threadIdsToUpdate
|
||||
.filterNotNull()
|
||||
.forEach { threadId ->
|
||||
val now = System.currentTimeMillis()
|
||||
val values = contentValuesOf(
|
||||
FROM_RECIPIENT_ID to recipient.id.serialize(),
|
||||
FROM_DEVICE_ID to 1,
|
||||
TO_RECIPIENT_ID to Recipient.self().id.serialize(),
|
||||
DATE_RECEIVED to System.currentTimeMillis(),
|
||||
DATE_SENT to System.currentTimeMillis(),
|
||||
DATE_RECEIVED to now,
|
||||
DATE_SENT to now,
|
||||
READ to 1,
|
||||
TYPE to MessageTypes.PROFILE_CHANGE_TYPE,
|
||||
THREAD_ID to threadId,
|
||||
MESSAGE_EXTRAS to extras.encode()
|
||||
)
|
||||
db.insert(TABLE_NAME, null, values)
|
||||
val messageId = db.insert(TABLE_NAME, null, values)
|
||||
maybeCollapseMessage(db = db, messageId = messageId, threadId = threadId, dateReceived = now, messageExtras = extras, messageType = MessageTypes.PROFILE_CHANGE_TYPE)
|
||||
notifyConversationListeners(threadId)
|
||||
TrimThreadJob.enqueueAsync(threadId)
|
||||
}
|
||||
@@ -1173,18 +1206,19 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val threadId: Long? = SignalDatabase.threads.getThreadIdFor(recipient.id)
|
||||
|
||||
if (threadId != null) {
|
||||
val now = System.currentTimeMillis()
|
||||
val extras = MessageExtras(
|
||||
profileChangeDetails = ProfileChangeDetails(learnedProfileName = ProfileChangeDetails.LearnedProfileName(e164 = e164, username = username))
|
||||
)
|
||||
|
||||
writableDatabase
|
||||
val messageId = writableDatabase
|
||||
.insertInto(TABLE_NAME)
|
||||
.values(
|
||||
FROM_RECIPIENT_ID to recipient.id.serialize(),
|
||||
FROM_DEVICE_ID to 1,
|
||||
TO_RECIPIENT_ID to Recipient.self().id.serialize(),
|
||||
DATE_RECEIVED to System.currentTimeMillis(),
|
||||
DATE_SENT to System.currentTimeMillis(),
|
||||
DATE_RECEIVED to now,
|
||||
DATE_SENT to now,
|
||||
READ to 1,
|
||||
TYPE to MessageTypes.PROFILE_CHANGE_TYPE,
|
||||
THREAD_ID to threadId,
|
||||
@@ -1192,6 +1226,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
)
|
||||
.run()
|
||||
|
||||
maybeCollapseMessage(db = writableDatabase, messageId = messageId, threadId = threadId, dateReceived = now, messageExtras = extras, messageType = MessageTypes.PROFILE_CHANGE_TYPE)
|
||||
|
||||
notifyConversationListeners(threadId)
|
||||
}
|
||||
}
|
||||
@@ -1946,7 +1982,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_COUNT")
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -3002,6 +3038,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
maybeCollapseMessage(db = writableDatabase, messageId = messageId, threadId = threadId, dateReceived = retrieved.receivedTimeMillis, messageExtras = retrieved.messageExtras, messageType = type)
|
||||
|
||||
if (editedMessage != null) {
|
||||
writableDatabase.update(TABLE_NAME)
|
||||
.values(QUOTE_ID to retrieved.sentTimeMillis)
|
||||
@@ -3512,10 +3550,14 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
movePinnedDetailsToNewMessage(newMessageId = messageId, previousId = message.messageToEdit)
|
||||
}
|
||||
|
||||
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId, dateReceived)
|
||||
val hasCollapsed = maybeCollapseMessage(db = writableDatabase, messageId = messageId, threadId = threadId, dateReceived = dateReceived, messageExtras = message.messageExtras, messageType = type)
|
||||
|
||||
if (!message.isIdentityVerified && !message.isIdentityDefault) {
|
||||
threads.updateLastSeenAndMarkSentAndLastScrolledSilenty(threadId, dateReceived)
|
||||
}
|
||||
|
||||
if (!message.storyType.isStory) {
|
||||
if (message.outgoingQuote == null && editedMessage == null) {
|
||||
if (message.outgoingQuote == null && editedMessage == null && !hasCollapsed) {
|
||||
AppDependencies.databaseObserver.notifyMessageInsertObservers(threadId, MessageId(messageId))
|
||||
} else {
|
||||
AppDependencies.databaseObserver.notifyConversationListeners(threadId)
|
||||
@@ -3543,6 +3585,54 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally collapses a new message if it is the same [CollapsibleEvents.CollapsibleType] as the previous message on the same day.
|
||||
* If it is not, but the new message is a collapsing type, mark it as a new collapsed head. Returns whether a message was collapsed.
|
||||
*/
|
||||
fun maybeCollapseMessage(db: SQLiteDatabase, messageId: Long, threadId: Long, dateReceived: Long, messageExtras: MessageExtras?, messageType: Long): Boolean {
|
||||
if (!RemoteConfig.collapseEvents || !CollapsibleEvents.isCollapsibleType(messageType, messageExtras)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val currentType = CollapsibleEvents.getCollapsibleType(messageType, messageExtras)!!
|
||||
val previousMessage = getMessageDirectlyBefore(messageId, threadId, dateReceived)
|
||||
val previousType = previousMessage?.let { CollapsibleEvents.getCollapsibleType(previousMessage.type, previousMessage.messageExtras) }
|
||||
|
||||
return if (previousType == currentType) {
|
||||
db.update(TABLE_NAME)
|
||||
.values(
|
||||
COLLAPSED_STATE to CollapsedState.PENDING_COLLAPSED.id,
|
||||
COLLAPSED_HEAD_ID to previousMessage.collapsedHeadId
|
||||
)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
true
|
||||
} else {
|
||||
db.update(TABLE_NAME)
|
||||
.values(
|
||||
COLLAPSED_STATE to CollapsedState.HEAD_COLLAPSED.id,
|
||||
COLLAPSED_HEAD_ID to messageId
|
||||
)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(michelle): Maybe reduce to the fields you actually need instead of everything
|
||||
private fun getMessageDirectlyBefore(messageId: Long, threadId: Long, dateReceived: Long): MessageRecord? {
|
||||
val message = readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from(TABLE_NAME)
|
||||
.where("$ID < ? AND $THREAD_ID = ?", messageId, threadId)
|
||||
.orderBy("$DATE_RECEIVED DESC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.readToSingleObject { MmsReader(it).getCurrent() }
|
||||
|
||||
return message?.takeIf { DateUtils.isSameDay(message.dateReceived, dateReceived) }
|
||||
}
|
||||
|
||||
private fun hasAudioAttachment(attachments: List<Attachment>): Boolean {
|
||||
return attachments.any { MediaUtil.isAudio(it) }
|
||||
}
|
||||
@@ -3761,6 +3851,44 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return deleteMessage(messageId, threadId)
|
||||
}
|
||||
|
||||
/**
|
||||
* When an update gets deleted, check if it was the head of a set of collapsed events. If so,
|
||||
* set the next element to be the new head, and change all elements' head reference to the new one.
|
||||
*/
|
||||
private fun reassignCollapsedHead(messageId: Long) {
|
||||
val collapsedState = readableDatabase
|
||||
.select(COLLAPSED_STATE)
|
||||
.from(TABLE_NAME)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
.readToSingleObject { cursor -> CollapsedState.deserialize(cursor.requireLong(COLLAPSED_STATE)) } ?: CollapsedState.NONE
|
||||
|
||||
if (CollapsedState.isHead(collapsedState)) {
|
||||
val nextHead = readableDatabase
|
||||
.select(ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$ID > ? AND $COLLAPSED_HEAD_ID = ?", messageId, messageId)
|
||||
.orderBy("$DATE_RECEIVED ASC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.readToSingleLongOrNull()
|
||||
|
||||
if (nextHead != null) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to collapsedState.id)
|
||||
.where("$ID = ?", nextHead)
|
||||
.run()
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_HEAD_ID to nextHead)
|
||||
.where("$COLLAPSED_HEAD_ID = ?", messageId)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun deleteMessage(messageId: Long, threadId: Long, notify: Boolean = true, updateThread: Boolean = true): Boolean {
|
||||
Log.d(TAG, "deleteMessage($messageId)")
|
||||
@@ -3770,6 +3898,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
mentions.deleteMentionsForMessage(messageId)
|
||||
disassociatePollFromPollTerminate(polls.getPollTerminateMessageId(messageId))
|
||||
disassociatePinnedMessage(messageId)
|
||||
reassignCollapsedHead(messageId)
|
||||
|
||||
writableDatabase
|
||||
.delete(TABLE_NAME)
|
||||
@@ -4531,6 +4660,65 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
.run()
|
||||
}
|
||||
|
||||
fun collapsePendingCollapsibleEvents(threadId: Long, sinceTimestamp: Long) {
|
||||
val where = if (sinceTimestamp > -1) {
|
||||
"$THREAD_ID = ? AND $COLLAPSED_STATE = ? AND $DATE_RECEIVED <= $sinceTimestamp"
|
||||
} else {
|
||||
"$THREAD_ID = ? AND $COLLAPSED_STATE = ?"
|
||||
}
|
||||
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.COLLAPSED.id)
|
||||
.where(where, threadId, CollapsedState.PENDING_COLLAPSED.id)
|
||||
.run()
|
||||
}
|
||||
|
||||
fun collapseAllPendingCollapsibleEvents() {
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.COLLAPSED.id)
|
||||
.where("$COLLAPSED_STATE = ?", CollapsedState.PENDING_COLLAPSED.id)
|
||||
.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* If the oldest message in a thread is [CollapsedState.COLLAPSED], [CollapsedState.PENDING_COLLAPSED], or [CollapsedState.EXPANDED],
|
||||
* that means its head reference has been deleted in a previous operation. In that case, we promote the
|
||||
* oldest message to be the HEAD and update any existing events that previously had the deleted head as a reference.
|
||||
*/
|
||||
fun fixPotentialDanglingCollapsibleEvent(threadId: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.select(ID, COLLAPSED_STATE, COLLAPSED_HEAD_ID)
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = ?", threadId)
|
||||
.orderBy("$DATE_RECEIVED ASC")
|
||||
.limit(1)
|
||||
.run()
|
||||
.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val id = cursor.requireLong(ID)
|
||||
val collapsedState = CollapsedState.deserialize(cursor.requireLong(COLLAPSED_STATE))
|
||||
val deletedHeadId = cursor.requireLong(COLLAPSED_HEAD_ID)
|
||||
if (collapsedState == CollapsedState.COLLAPSED || collapsedState == CollapsedState.EXPANDED || collapsedState == CollapsedState.PENDING_COLLAPSED) {
|
||||
val newState = if (collapsedState == CollapsedState.EXPANDED) CollapsedState.HEAD_EXPANDED.id else CollapsedState.HEAD_COLLAPSED.id
|
||||
val updated = db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to newState)
|
||||
.where("$ID = ?", id)
|
||||
.run()
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_HEAD_ID to id)
|
||||
.where("$COLLAPSED_HEAD_ID = ?", deletedHeadId)
|
||||
.run()
|
||||
|
||||
Log.i(TAG, "Found dangling collapsed set, reset head: $updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setNotifiedTimestamp(timestamp: Long, ids: List<Long>) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
@@ -4781,7 +4969,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $DATE_RECEIVED > $targetMessageDateReceived")
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $DATE_RECEIVED > $targetMessageDateReceived AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -4799,7 +4987,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $DATE_RECEIVED > $receivedTimestamp")
|
||||
.where("$THREAD_ID = $threadId AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $DATE_RECEIVED > $receivedTimestamp AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}")
|
||||
.run()
|
||||
.readToSingleInt(-1)
|
||||
}
|
||||
@@ -4843,9 +5031,9 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
*/
|
||||
fun getMessagePositionInConversation(threadId: Long, groupStoryId: Long, receivedTimestamp: Long): Int {
|
||||
val selection = if (groupStoryId > 0) {
|
||||
"$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
|
||||
"$THREAD_ID = $threadId AND $DATE_RECEIVED < $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID = $groupStoryId AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}"
|
||||
} else {
|
||||
"$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL"
|
||||
"$THREAD_ID = $threadId AND $DATE_RECEIVED > $receivedTimestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}"
|
||||
}
|
||||
|
||||
return readableDatabase
|
||||
@@ -4871,7 +5059,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.where("$DATE_RECEIVED < $date AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -4889,7 +5077,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
return readableDatabase
|
||||
.select("COUNT(*)")
|
||||
.from(TABLE_NAME)
|
||||
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED ${if (inclusive) ">=" else ">"} $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL")
|
||||
.where("$THREAD_ID = $threadId AND $DATE_RECEIVED ${if (inclusive) ">=" else ">"} $timestamp AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND $LATEST_REVISION_ID IS NULL AND $COLLAPSED_STATE != ${CollapsedState.COLLAPSED.id}")
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
@@ -5380,15 +5568,23 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
|
||||
/**
|
||||
* A cursor containing all of the messages in a given thread, in the proper order, respecting offset/limit.
|
||||
* This does *not* have attachments in it.
|
||||
* This does *not* have attachments in it. Use [filterCollapsed] to exclude collapsed events.
|
||||
*/
|
||||
fun getConversation(threadId: Long, offset: Long = 0, limit: Long = 0, dateReceiveOrderBy: String = "DESC"): Cursor {
|
||||
fun getConversation(threadId: Long, offset: Long = 0, limit: Long = 0, dateReceiveOrderBy: String = "DESC", filterCollapsed: Boolean = false): Cursor {
|
||||
val limitStr: String = if (limit > 0 || offset > 0) "$offset, $limit" else ""
|
||||
|
||||
var query = "$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL"
|
||||
val args = mutableListOf(threadId.toString(), 0.toString(), 0.toString(), (-1).toString())
|
||||
|
||||
if (filterCollapsed) {
|
||||
query += " AND $COLLAPSED_STATE != ?"
|
||||
args.add(CollapsedState.COLLAPSED.id.toString())
|
||||
}
|
||||
|
||||
return readableDatabase
|
||||
.select(*MMS_PROJECTION)
|
||||
.from("$TABLE_NAME INDEXED BY $INDEX_THREAD_STORY_SCHEDULED_DATE_LATEST_REVISION_ID")
|
||||
.where("$THREAD_ID = ? AND $STORY_TYPE = ? AND $PARENT_STORY_ID <= ? AND $SCHEDULED_DATE = ? AND $LATEST_REVISION_ID IS NULL", threadId, 0, 0, -1)
|
||||
.where(query, args.toTypedArray())
|
||||
.orderBy("$DATE_RECEIVED $dateReceiveOrderBy")
|
||||
.limit(limitStr)
|
||||
.run()
|
||||
@@ -5979,6 +6175,70 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of updates that belong in a collapsed update set where [messageId] is the head (first update) in that set
|
||||
* If an event is [PENDING_COLLAPSED], we do not want to consider it part of the count until it is seen.
|
||||
*/
|
||||
fun getCollapsedCount(messageId: Long): Int {
|
||||
return readableDatabase
|
||||
.count()
|
||||
.from(TABLE_NAME)
|
||||
.where("$COLLAPSED_HEAD_ID = ? AND $COLLAPSED_STATE != ?", messageId, CollapsedState.PENDING_COLLAPSED.id)
|
||||
.run()
|
||||
.readToSingleInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a collapsed head, set it and all of the updates in that set, to expanded
|
||||
*/
|
||||
fun expandEvents(messageId: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.HEAD_EXPANDED.id)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.EXPANDED.id)
|
||||
.where("$COLLAPSED_HEAD_ID = ? AND $COLLAPSED_STATE = ?", messageId, CollapsedState.COLLAPSED.id)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an expanded head, set it and all of the updates in that set, to collapsed
|
||||
*/
|
||||
fun collapseEvents(messageId: Long) {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.HEAD_COLLAPSED.id)
|
||||
.where("$ID = ?", messageId)
|
||||
.run()
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.COLLAPSED.id)
|
||||
.where("$COLLAPSED_HEAD_ID = ? AND $COLLAPSED_STATE = ?", messageId, CollapsedState.EXPANDED.id)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses any expanded events in a thread
|
||||
*/
|
||||
fun collapseAllEvents() {
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.HEAD_COLLAPSED.id)
|
||||
.where("$COLLAPSED_STATE = ?", CollapsedState.HEAD_EXPANDED.id)
|
||||
.run()
|
||||
|
||||
db.update(TABLE_NAME)
|
||||
.values(COLLAPSED_STATE to CollapsedState.COLLAPSED.id)
|
||||
.where("$COLLAPSED_STATE = ?", CollapsedState.EXPANDED.id)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove duplicate messages that were imported from a backup without the same sql constraint on the this table.
|
||||
*
|
||||
@@ -6451,6 +6711,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
val pinnedUntil = cursor.requireLong(PINNED_UNTIL)
|
||||
val deletedBy = cursor.requireLongOrNull(DELETED_BY)?.let { RecipientId.from(it) }
|
||||
val isStarred = cursor.requireBoolean(STARRED)
|
||||
val collapsedState = CollapsedState.deserialize(cursor.requireLong(COLLAPSED_STATE))
|
||||
val collapsedHeadId = cursor.requireLong(COLLAPSED_HEAD_ID)
|
||||
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
|
||||
val messageExtras = messageExtraBytes?.let { MessageExtras.ADAPTER.decode(it) }
|
||||
|
||||
@@ -6546,6 +6808,8 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
isRead,
|
||||
pinnedUntil,
|
||||
deletedBy,
|
||||
collapsedState,
|
||||
collapsedHeadId,
|
||||
messageExtras,
|
||||
isStarred
|
||||
)
|
||||
|
||||
@@ -450,6 +450,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
|
||||
if (deletes > 0) {
|
||||
Log.i(TAG, "Trimming deleted $deletes messages thread: $threadId")
|
||||
messages.fixPotentialDanglingCollapsibleEvent(threadId)
|
||||
setLastScrolled(threadId, 0)
|
||||
val threadDeleted = update(threadId = threadId, unarchive = false, syncThreadDelete = syncThreadTrimDeletes)
|
||||
notifyConversationListeners(threadId)
|
||||
@@ -499,6 +500,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
|
||||
messages.setAllReactionsSeen()
|
||||
messages.setAllVotesSeen()
|
||||
messages.collapseAllPendingCollapsibleEvents()
|
||||
notifyConversationListListeners()
|
||||
|
||||
return messageRecords
|
||||
@@ -561,6 +563,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
||||
|
||||
messages.setReactionsSeen(threadId, sinceTimestamp)
|
||||
messages.setVoteSeen(threadId, sinceTimestamp)
|
||||
messages.collapsePendingCollapsibleEvents(threadId, sinceTimestamp)
|
||||
|
||||
val unreadCount = messages.getUnreadCount(threadId)
|
||||
val unreadMentionsCount = messages.getUnreadMentionCount(threadId)
|
||||
|
||||
@@ -165,6 +165,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V309_GroupTerminate
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V310_AddStarredColumn
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V311_AddAttachmentMediaOverviewSizeIndex
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V312_RefactorNameCollisionTables
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V313_AddCollapsingUpdateColumns
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -337,10 +338,11 @@ object SignalDatabaseMigrations {
|
||||
309 to V309_GroupTerminatedColumnMigration,
|
||||
310 to V310_AddStarredColumn,
|
||||
311 to V311_AddAttachmentMediaOverviewSizeIndex,
|
||||
312 to V312_RefactorNameCollisionTables
|
||||
312 to V312_RefactorNameCollisionTables,
|
||||
313 to V313_AddCollapsingUpdateColumns
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 312
|
||||
const val DATABASE_VERSION = 313
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds the columns and indexes necessary for collapsing updates
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V313_AddCollapsingUpdateColumns : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN collapsed_state INTEGER DEFAULT 0")
|
||||
db.execSQL("ALTER TABLE message ADD COLUMN collapsed_head_id INTEGER DEFAULT 0")
|
||||
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS message_collapsed_state_index ON message (collapsed_state)")
|
||||
db.execSQL("CREATE INDEX message_collapsed_head_id_index ON message (collapsed_head_id)")
|
||||
|
||||
// Adjust existing index to disregard collapsed updates from the thread count
|
||||
db.execSQL("DROP INDEX IF EXISTS message_thread_count_index")
|
||||
db.execSQL("CREATE INDEX message_thread_count_index ON message (thread_id) WHERE story_type = 0 AND parent_story_id <= 0 AND scheduled_date = -1 AND latest_revision_id IS NULL AND collapsed_state != 3")
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CollapsedState;
|
||||
import org.thoughtcrime.securesms.fonts.SignalSymbols.Glyph;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -60,6 +61,8 @@ public class InMemoryMessageRecord extends MessageRecord {
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
CollapsedState.NONE,
|
||||
0,
|
||||
null,
|
||||
false);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.signal.archive.proto.GroupCreationUpdate;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
|
||||
import org.thoughtcrime.securesms.database.CollapsedState;
|
||||
import org.thoughtcrime.securesms.database.MessageTypes;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
@@ -115,6 +116,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final int revisionNumber;
|
||||
private final long pinnedUntil;
|
||||
private final RecipientId deletedBy;
|
||||
private final CollapsedState collapsedState;
|
||||
private final long collapsedHeadId;
|
||||
private final MessageExtras messageExtras;
|
||||
private final boolean starred;
|
||||
|
||||
@@ -139,6 +142,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
int revisionNumber,
|
||||
long pinnedUntil,
|
||||
@Nullable RecipientId deletedBy,
|
||||
CollapsedState collapsedState,
|
||||
long collapsedHeadId,
|
||||
@Nullable MessageExtras messageExtras,
|
||||
boolean starred)
|
||||
{
|
||||
@@ -162,6 +167,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
this.revisionNumber = revisionNumber;
|
||||
this.pinnedUntil = pinnedUntil;
|
||||
this.deletedBy = deletedBy;
|
||||
this.collapsedState = collapsedState;
|
||||
this.collapsedHeadId = collapsedHeadId;
|
||||
this.messageExtras = messageExtras;
|
||||
this.starred = starred;
|
||||
}
|
||||
@@ -818,6 +825,14 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
messageExtras.adminDeleteStatus.status == AdminDeleteStatus.Status.FAILED;
|
||||
}
|
||||
|
||||
public CollapsedState getCollapsedState() {
|
||||
return collapsedState;
|
||||
}
|
||||
|
||||
public long getCollapsedHeadId() {
|
||||
return collapsedHeadId;
|
||||
}
|
||||
|
||||
public boolean isInMemoryMessageRecord() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.CallTable;
|
||||
import org.thoughtcrime.securesms.database.CollapsedState;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.Status;
|
||||
import org.thoughtcrime.securesms.database.MessageTypes;
|
||||
@@ -121,13 +122,15 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
boolean isRead,
|
||||
long pinnedUntil,
|
||||
@Nullable RecipientId deletedBy,
|
||||
@NonNull CollapsedState collapsedState,
|
||||
long collapsedHeadId,
|
||||
@Nullable MessageExtras messageExtras,
|
||||
boolean starred)
|
||||
{
|
||||
super(id, body, fromRecipient, fromDeviceId, toRecipient,
|
||||
dateSent, dateReceived, dateServer, threadId, Status.STATUS_NONE, hasDeliveryReceipt,
|
||||
mailbox, mismatches, failures, subscriptionId, expiresIn, expireStarted, expireTimerVersion, hasReadReceipt,
|
||||
unidentified, reactions, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, pinnedUntil, deletedBy, messageExtras, starred);
|
||||
unidentified, reactions, notifiedTimestamp, viewed, receiptTimestamp, originalMessageId, revisionNumber, pinnedUntil, deletedBy, collapsedState, collapsedHeadId, messageExtras, starred);
|
||||
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
@@ -341,7 +344,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
incomingType, getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withReactions(@NonNull List<ReactionRecord> reactions) {
|
||||
@@ -349,7 +352,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), reactions, mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withoutQuote() {
|
||||
@@ -357,7 +360,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), null, getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withAttachments(@NonNull List<DatabaseAttachment> attachments) {
|
||||
@@ -379,7 +382,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), quote, contacts, linkPreviews, isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withPayment(@NonNull Payment payment) {
|
||||
@@ -387,7 +390,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), payment, getCall(), getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +399,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), call, getPoll(), getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
public @NonNull MmsMessageRecord withPoll(@Nullable PollRecord poll) {
|
||||
@@ -404,7 +407,7 @@ public class MmsMessageRecord extends MessageRecord {
|
||||
getType(), getIdentityKeyMismatches(), getNetworkFailures(), getSubscriptionId(), getExpiresIn(), getExpireStarted(), getExpireTimerVersion(), isViewOnce(),
|
||||
hasReadReceipt(), getQuote(), getSharedContacts(), getLinkPreviews(), isUnidentified(), getReactions(), mentionsSelf,
|
||||
getNotifiedTimestamp(), isViewed(), getReceiptTimestamp(), getMessageRanges(), getStoryType(), getParentStoryId(), getGiftBadge(), getPayment(), getCall(), poll, getScheduledDate(), getLatestRevisionId(),
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getMessageExtras(), isStarred());
|
||||
getOriginalMessageId(), getRevisionNumber(), isRead(), getPinnedUntil(), getDeletedBy(), getCollapsedState(), getCollapsedHeadId(), getMessageExtras(), isStarred());
|
||||
}
|
||||
|
||||
private static @NonNull List<Contact> updateContacts(@NonNull List<Contact> contacts, @NonNull Map<AttachmentId, DatabaseAttachment> attachmentIdMap) {
|
||||
|
||||
@@ -417,6 +417,14 @@ class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onExpandEvents(messageId: Long) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onCollapseEvents(messageId: Long) {
|
||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
class Dialog : WrapperDialogFragment() {
|
||||
override fun getWrappedFragment(): Fragment {
|
||||
return MessageDetailsFragment().apply {
|
||||
|
||||
@@ -421,4 +421,6 @@ private class StarredMessageClickListener(
|
||||
override fun onUpdateSignalClicked() = Unit
|
||||
override fun onViewPollClicked(messageId: Long) = Unit
|
||||
override fun onViewPinnedMessage(messageId: Long) = Unit
|
||||
override fun onCollapseEvents(messageId: Long) = Unit
|
||||
override fun onExpandEvents(messageId: Long) = Unit
|
||||
}
|
||||
|
||||
@@ -1315,5 +1315,16 @@ object RemoteConfig {
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
/**
|
||||
* Whether to collapse update events
|
||||
*/
|
||||
@JvmStatic
|
||||
@get:JvmName("collapseEvents")
|
||||
val collapseEvents: Boolean by remoteBoolean(
|
||||
key = "android.collapseEvents",
|
||||
defaultValue = false,
|
||||
hotSwappable = true
|
||||
)
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user