Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
983cfdd
Remove unused code
SessionHero01 Dec 11, 2025
3c38859
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Dec 11, 2025
116aa5e
WIP
SessionHero01 Dec 12, 2025
532ac54
Handling mark read automatically
SessionHero01 Dec 15, 2025
cbe2cb2
Optimize
SessionHero01 Dec 15, 2025
f96e604
Merge branch 'dev' into tidy-up-threads
SessionHero01 Dec 15, 2025
764deff
Remove debug code
SessionHero01 Dec 15, 2025
d4ab425
Merge remote-tracking branch 'origin/tidy-up-threads' into tidy-up-th…
SessionHero01 Dec 15, 2025
0d19fce
Fixed test issues
SessionHero01 Dec 15, 2025
e4de84d
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
simophin Dec 16, 2025
d98e29d
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
simophin Dec 16, 2025
82ad649
Merge issues
simophin Dec 16, 2025
ebce4f0
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Dec 16, 2025
49588fa
Fixed tests and added comments
SessionHero01 Dec 16, 2025
3ae46eb
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Dec 17, 2025
0bf3a06
Add missing place to notify thread updated
SessionHero01 Dec 17, 2025
f07c53b
Tidy up
SessionHero01 Dec 17, 2025
50d4bd0
Fixed missing thread notification
SessionHero01 Dec 17, 2025
f1122d0
Optimise thread query
SessionHero01 Jan 4, 2026
bfc05fb
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Jan 6, 2026
bcc68ee
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Jan 7, 2026
baf4062
Merge remote-tracking branch 'origin/dev' into tidy-up-threads
SessionHero01 Jan 8, 2026
96e6906
New implementation of getUnreadCount based on last read
SessionHero01 Jan 8, 2026
3d2ee30
Fixes deadlock
SessionHero01 Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,12 @@ interface StorageProtocol {
fun getOrCreateThreadIdFor(address: Address): Long
fun getThreadId(address: Address): Long?
fun getThreadIdForMms(mmsId: Long): Long
fun getLastUpdated(threadID: Long): Long
fun trimThreadBefore(threadID: Long, timestamp: Long)
fun getMessageCount(threadID: Long): Long
fun getTotalPinned(): Int
suspend fun getTotalSentProBadges(): Int
suspend fun getTotalSentLongMessages(): Int
fun setPinned(address: Address, isPinned: Boolean)
fun isRead(threadId: Long) : Boolean
fun setThreadCreationDate(threadId: Long, newDate: Long)
fun getLastLegacyRecipient(threadRecipient: String): String?
fun setLastLegacyRecipient(threadRecipient: String, senderRecipient: String?)
Expand All @@ -176,7 +174,9 @@ interface StorageProtocol {
attachments: List<Attachment>,
runThreadUpdate: Boolean
): MessageId?
fun markConversationAsRead(threadId: Long, lastSeenTime: Long, force: Boolean = false, updateNotification: Boolean = true)

fun updateConversationLastSeenIfNeeded(threadAddress: Address.Conversable, lastSeenTime: Long)
fun updateConversationLastSeenIfNeeded(threadId: Long, lastSeenTime: Long)

/**
* Marks the conversation as read up to and including the message with [messageId]. It will
Expand All @@ -186,13 +186,11 @@ interface StorageProtocol {
*/
fun markConversationAsReadUpToMessage(messageId: MessageId)
fun markConversationAsUnread(threadId: Long)
fun getLastSeen(threadId: Long): Long
fun getLastSeen(threadAddress: Address.Conversable): Long
fun ensureMessageHashesAreSender(hashes: Set<String>, sender: String, closedGroupId: String): Boolean
fun updateThread(threadId: Long, unarchive: Boolean)
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
fun insertMessageRequestResponseFromYou(threadId: Long)
fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long)
fun conversationHasOutgoing(userPublicKey: String): Boolean
fun deleteMessagesByHash(threadId: Long, hashes: List<String>)
fun deleteMessagesByUser(threadId: Long, userSessionId: String)
fun clearAllMessages(threadId: Long): List<String?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ data class ExpirationConfiguration(
val expiryMode: ExpiryMode = ExpiryMode.NONE,
val updatedTimestampMs: Long = 0
) {
val isEnabled = expiryMode.expirySeconds > 0

companion object {
val isNewConfigEnabled = true
}
val isEnabled get() = expiryMode.expirySeconds > 0
}

data class ExpirationDatabaseMetadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,14 @@ class ReceivedMessageProcessor @Inject constructor(
try {
return block(context)
} finally {
for (threadId in context.threadIDs.values) {
if (context.maxOutgoingMessageTimestamp > 0L &&
context.maxOutgoingMessageTimestamp > storage.getLastSeen(threadId)
) {
storage.markConversationAsRead(
threadId,
context.maxOutgoingMessageTimestamp,
force = true
)
}
for ((threadAddress, threadId) in context.threadIDs) {
storage.updateConversationLastSeenIfNeeded(
threadAddress = threadAddress,
context.maxOutgoingMessageTimestamp,
)

storage.updateThread(threadId, true)
notificationManager.updateNotification(this.context, threadId)
threadDatabase.notifyThreadUpdated(threadId)
}

// Handle pending community reactions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import android.provider.Settings
import androidx.annotation.ArrayRes
import androidx.annotation.StyleRes
import androidx.camera.core.CameraSelector
import androidx.core.app.NotificationCompat
import androidx.core.content.edit
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
Expand Down Expand Up @@ -262,8 +261,6 @@ interface TextSecurePreferences {
var migratedToDisablingKDF: Boolean
var migratedToMultiPartConfig: Boolean

var migratedDisappearingMessagesToMessageContent: Boolean

var selectedActivityAliasName: String?

var inAppReviewState: String?
Expand Down Expand Up @@ -746,10 +743,6 @@ class AppTextSecurePreferences @Inject constructor(
get() = getBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, false)
set(value) = setBooleanPreference(TextSecurePreferences.MIGRATED_TO_MULTIPART_CONFIG, value)

override var migratedDisappearingMessagesToMessageContent: Boolean
get() = getBooleanPreference("migrated_disappearing_messages_to_message_content", false)
set(value) = setBooleanPreference("migrated_disappearing_messages_to_message_content", value)

override fun getConfigurationMessageSynced(): Boolean {
return getBooleanPreference(TextSecurePreferences.CONFIGURATION_SYNCED, false)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import org.session.libsession.messaging.open_groups.GroupMemberRole
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.RemoteFile.Companion.toRemoteFile
import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.util.DateUtils.Companion.secondsToInstant
import java.time.Instant

/**
Expand Down Expand Up @@ -134,17 +136,24 @@ sealed interface RecipientData {
* A recipient that was saved in your contact config.
*/
data class Contact(
val name: String,
val nickname: String?,
override val avatar: RemoteFile.Encrypted?,
val approved: Boolean,
val approvedMe: Boolean,
val blocked: Boolean,
val expiryMode: ExpiryMode,
override val priority: Long,
private val configData: network.loki.messenger.libsession_util.util.Contact,
override val proData: ProData?,
override val profileUpdatedAt: Instant?,
) : RecipientData {
val name: String get() = configData.name
val nickname: String? get() = configData.nickname.takeIf { it.isNotBlank() }
val approved: Boolean get() = configData.approved
val approvedMe: Boolean get() = configData.approvedMe
val blocked: Boolean get() = configData.blocked
val createdAt: Instant get() = Instant.ofEpochSecond(configData.createdEpochSeconds)
override val priority: Long get() = configData.priority
override val profileUpdatedAt: Instant? get() = configData.profileUpdatedEpochSeconds
.secondsToInstant()

val expiryMode: ExpiryMode get() = configData.expiryMode

override val avatar: RemoteFile?
get() = configData.profilePicture.toRemoteFile()

val displayName: String
get() = nickname?.takeIf { it.isNotBlank() } ?: name

Expand Down Expand Up @@ -186,6 +195,7 @@ sealed interface RecipientData {
val kicked: Boolean get() = groupInfo.kicked
val destroyed: Boolean get() = groupInfo.destroyed
val shouldPoll: Boolean get() = groupInfo.shouldPoll
val joinedAt: Instant get() = Instant.ofEpochSecond(groupInfo.joinedAtSecs)

override val profileUpdatedAt: Instant?
get() = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.groups.handler.CleanupInvitationHandler
import org.thoughtcrime.securesms.groups.handler.DestroyedGroupSync
import org.thoughtcrime.securesms.groups.handler.RemoveGroupMemberHandler
import org.thoughtcrime.securesms.notifications.BackgroundPollManager
import org.thoughtcrime.securesms.notifications.MarkReadProcessor
import org.thoughtcrime.securesms.notifications.PushRegistrationHandler
import org.thoughtcrime.securesms.pro.ProStatusManager
import org.thoughtcrime.securesms.service.ExpiringMessageManager
Expand Down Expand Up @@ -41,6 +42,7 @@ class AuthAwareComponents(
proStatusManager: Lazy<ProStatusManager>,
pollerManager: Lazy<PollerManager>,
backgroundPollManager: Lazy<BackgroundPollManager>,
markReadProcessor: Lazy<MarkReadProcessor>,
): this(
components = listOf<Lazy<out AuthAwareComponent>>(
expiringMessageManager,
Expand All @@ -56,6 +58,7 @@ class AuthAwareComponents(
proStatusManager,
pollerManager,
backgroundPollManager,
markReadProcessor,
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import network.loki.messenger.R
import network.loki.messenger.libsession_util.ReadableGroupInfoConfig
import network.loki.messenger.libsession_util.util.Conversation
import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.avatars.AvatarCacheCleaner
import org.session.libsession.database.StorageProtocol
Expand Down Expand Up @@ -56,8 +51,8 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.dependencies.ManagerScope
import org.thoughtcrime.securesms.repository.ConversationRepository
import org.thoughtcrime.securesms.util.SessionMetaProtocol
import org.thoughtcrime.securesms.util.castAwayType
import java.util.EnumSet
import org.thoughtcrime.securesms.util.erase
import org.thoughtcrime.securesms.util.get
import java.util.concurrent.TimeUnit
import javax.inject.Inject

Expand Down Expand Up @@ -92,18 +87,10 @@ class ConfigToDatabaseSync @Inject constructor(
@param:ManagerScope private val scope: CoroutineScope,
) : AuthAwareComponent {
override suspend fun doWhileLoggedIn(loggedInState: LoggedInState) {
combine(
conversationRepository.conversationListAddressesFlow,
configFactory.userConfigsChanged(EnumSet.of(UserConfigType.CONVO_INFO_VOLATILE))
.castAwayType()
.onStart { emit(Unit) }
.map { _ -> configFactory.withUserConfigs { it.convoInfoVolatile.all() } },
::Pair
).distinctUntilChanged()
.collectLatest { (conversations, convoInfo) ->
conversationRepository.conversationListAddressesFlow
.collectLatest { conversations ->
try {
ensureConversations(conversations, loggedInState.accountId)
updateConvoVolatile(convoInfo)
} catch (e: Exception) {
Log.e(TAG, "Error updating conversations from config", e)
}
Expand All @@ -129,6 +116,15 @@ class ConfigToDatabaseSync @Inject constructor(
// If you can find out what it does, please remove it.
SessionMetaProtocol.clearReceivedMessages()

// Remove all convo info
configFactory.withMutableUserConfigs { configs ->
result.deletedThreads.keys.forEach { address ->
if (address is Address.Conversable) {
configs.convoInfoVolatile.erase(address)
}
}
}

// Some type of convo require additional cleanup, we'll go through them here
for ((address, threadId) in result.deletedThreads) {
storage.cancelPendingMessageSendJobs(threadId)
Expand Down Expand Up @@ -337,32 +333,4 @@ class ConfigToDatabaseSync @Inject constructor(
private val MmsMessageRecord.containsAttachment: Boolean
get() = this.slideDeck.slides.isNotEmpty() && !this.slideDeck.isVoiceNote


private fun updateConvoVolatile(convos: List<Conversation?>) {
for (conversation in convos.asSequence().filterNotNull()) {
val address: Address.Conversable = when (conversation) {
is Conversation.OneToOne -> Address.Standard(AccountId(conversation.accountId))
is Conversation.LegacyGroup -> Address.LegacyGroup(conversation.groupId)
is Conversation.Community -> Address.Community(serverUrl = conversation.baseCommunityInfo.baseUrl, room = conversation.baseCommunityInfo.room)
is Conversation.ClosedGroup -> Address.Group(AccountId(conversation.accountId)) // New groups will be managed bia libsession
is Conversation.BlindedOneToOne -> {
// Not supported yet
continue
}
}

val threadId = threadDatabase.getThreadIdIfExistsFor(address)

if (threadId != -1L) {
if (conversation.lastRead > storage.getLastSeen(threadId)) {
storage.markConversationAsRead(
threadId,
conversation.lastRead,
force = true
)
storage.updateThread(threadId, false)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.ui.UINavigator
@HiltViewModel(assistedFactory = DisappearingMessagesViewModel.Factory::class)
class DisappearingMessagesViewModel @AssistedInject constructor(
@Assisted private val address: Address,
@Assisted("isNewConfigEnabled") private val isNewConfigEnabled: Boolean,
@Assisted("showDebugOptions") private val showDebugOptions: Boolean,
@Assisted private val navigator: UINavigator<ConversationSettingsDestination>,
@param:ApplicationContext private val context: Context,
Expand All @@ -39,7 +38,7 @@ class DisappearingMessagesViewModel @AssistedInject constructor(

private val _state = MutableStateFlow(
State(
isNewConfigEnabled = isNewConfigEnabled,
isNewConfigEnabled = true,
showDebugOptions = showDebugOptions
)
)
Expand Down Expand Up @@ -95,7 +94,6 @@ class DisappearingMessagesViewModel @AssistedInject constructor(
interface Factory {
fun create(
address: Address,
@Assisted("isNewConfigEnabled") isNewConfigEnabled: Boolean,
@Assisted("showDebugOptions") showDebugOptions: Boolean,
navigator: UINavigator<ConversationSettingsDestination>
): DisappearingMessagesViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ import network.loki.messenger.databinding.ActivityConversationV2Binding
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.groups.GroupManagerV2
import org.session.libsession.messaging.messages.ExpirationConfiguration
import org.session.libsession.messaging.messages.applyExpiryMode
import org.session.libsession.messaging.messages.control.DataExtractionNotification
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
Expand Down Expand Up @@ -209,7 +208,6 @@ import org.thoughtcrime.securesms.util.adapter.runWhenLaidOut
import org.thoughtcrime.securesms.util.drawToBitmap
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import org.thoughtcrime.securesms.util.getConversationUnread
import org.thoughtcrime.securesms.util.isFullyScrolled
import org.thoughtcrime.securesms.util.isNearBottom
import org.thoughtcrime.securesms.util.push
Expand Down Expand Up @@ -378,7 +376,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
private val adapter by lazy {
val adapter = ConversationAdapter(
this,
storage.getLastSeen(viewModel.threadId),
storage.getLastSeen(viewModel.address),
false,
onItemPress = { message, position, view, event ->
handlePress(message, position, view, event)
Expand Down Expand Up @@ -638,9 +636,10 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
try {
when (it) {
is Long -> {
if (storage.getLastSeen(viewModel.threadId) < it) {
storage.markConversationAsRead(viewModel.threadId, it)
}
storage.updateConversationLastSeenIfNeeded(
viewModel.address,
it
)
}

is MessageId -> {
Expand Down Expand Up @@ -801,6 +800,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<ConversationLoader.Data> {
return conversationLoaderFactory.create(
threadID = viewModel.threadId,
threadAddress = viewModel.address,
reverse = false,
)
}
Expand Down Expand Up @@ -830,24 +830,6 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
} else {
if (firstLoad.getAndSet(false)) {
scrollToFirstUnreadMessageOrBottom()

// On the first load, check if there unread messages
if (unreadCount == 0 && adapter.itemCount > 0) {
lifecycleScope.launch(Dispatchers.Default) {
val isUnread = configFactory.withUserConfigs {
it.convoInfoVolatile.getConversationUnread(
viewModel.address,
)
}

if (isUnread) {
storage.markConversationAsRead(
viewModel.threadId,
clock.currentTimeMills()
)
}
}
}
} else {
// If there are new data updated, we'll try to stay scrolled at the bottom (if we were at the bottom).
// scrolled to bottom has a leniency of 50dp, so if we are within the 50dp but not fully at the bottom, scroll down
Expand Down Expand Up @@ -1053,8 +1035,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
private fun setUpOutdatedClientBanner() {
val legacyRecipient = viewModel.legacyBannerRecipient(this)

val shouldShowLegacy = ExpirationConfiguration.isNewConfigEnabled &&
legacyRecipient != null
val shouldShowLegacy = legacyRecipient != null

binding.conversationHeader.outdatedDisappearingBanner.isVisible = shouldShowLegacy
if (shouldShowLegacy) {
Expand Down Expand Up @@ -1248,7 +1229,7 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate,
return
}

val lastSeenTimestamp = threadDb.getLastSeenAndHasSent(viewModel.threadId).first()
val lastSeenTimestamp = storage.getLastSeen(viewModel.address)
val lastSeenItemPosition = adapter.findLastSeenItemPosition(lastSeenTimestamp) ?: return

binding.conversationRecyclerView.runWhenLaidOut {
Expand Down
Loading