From 89a13f3e1025e393b1a15afb33150b36ca8f8fc4 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Thu, 27 Nov 2025 11:48:27 +1100 Subject: [PATCH 1/7] Clean up doubled up dialog --- .../conversation/v2/ConversationActivityV2.kt | 8 ++--- .../conversation/v2/dialogs/BlockedDialog.kt | 36 ------------------- 2 files changed, 3 insertions(+), 41 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 5c5f85920d..a3b010a610 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -58,7 +58,6 @@ import androidx.recyclerview.widget.RecyclerView import com.annimon.stream.Stream import com.bumptech.glide.Glide import com.squareup.phrase.Phrase -import dagger.Lazy import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.lifecycle.withCreationCallback import kotlinx.coroutines.CancellationException @@ -119,7 +118,6 @@ import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.utilities.ListenableFuture import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.toHexString -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.FullComposeActivity.Companion.applyCommonPropertiesForCompose import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorderHandle @@ -135,7 +133,6 @@ import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companio import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_SAVE -import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate @@ -158,7 +155,6 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities -import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.MmsDatabase @@ -2050,9 +2046,11 @@ class ConversationActivityV2 : ScreenLockActionBarActivity(), InputBarDelegate, override fun sendMessage() { val recipient = viewModel.recipient + // It shouldn't be possible to send a message to a blocked user anymore. + // But as a safety net: // show the unblock dialog when trying to send a message to a blocked contact if (recipient.isStandardRecipient && recipient.blocked) { - BlockedDialog(recipient.address, recipient.displayName()).show(supportFragmentManager, "Blocked Dialog") + unblock() return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt deleted file mode 100644 index 451dcc3031..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/BlockedDialog.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.dialogs - -import android.app.Dialog -import android.graphics.Typeface -import android.os.Bundle -import android.text.Spannable -import android.text.SpannableStringBuilder -import android.text.style.StyleSpan -import androidx.fragment.app.DialogFragment -import network.loki.messenger.R -import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY -import org.thoughtcrime.securesms.createSessionDialog -import org.thoughtcrime.securesms.ui.getSubbedCharSequence - -/** Shown upon sending a message to a user that's blocked. */ -class BlockedDialog(private val recipient: Address, private val contactName: String) : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog { - val explanationCS = context.getSubbedCharSequence(R.string.blockUnblockName, NAME_KEY to contactName) - val spannable = SpannableStringBuilder(explanationCS) - val startIndex = explanationCS.indexOf(contactName) - spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + contactName.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - - title(resources.getString(R.string.blockUnblock)) - text(spannable) - dangerButton(R.string.blockUnblock, R.string.AccessibilityId_unblockConfirm) { unblock() } - cancelButton { dismiss() } - } - - private fun unblock() { - MessagingModuleConfiguration.shared.storage.setBlocked(listOf(recipient), false) - dismiss() - } -} From dcadd72e4ca4c8e7e0e87566e949d0fffecda60b Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Thu, 27 Nov 2025 14:39:50 +1100 Subject: [PATCH 2/7] Using formatted value for chars left --- .../securesms/conversation/v2/input_bar/InputBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 81a3ce84d4..ac74fb1352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -365,7 +365,7 @@ class InputBar @JvmOverloads constructor( fun setCharLimitState(state: InputbarViewModel.InputBarCharLimitState?) { // handle char limit if(state != null){ - binding.characterLimitText.text = state.count.toString() + binding.characterLimitText.text = state.countFormatted binding.characterLimitText.setTextColor(if(state.danger) dangerColor else textColor) binding.characterLimitContainer.setOnClickListener { delegate?.onCharLimitTapped() From 3e6e7258129245b365c3e732611c1322e2dc58a6 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Thu, 27 Nov 2025 14:46:09 +1100 Subject: [PATCH 3/7] First stage of Pro stats logic --- .../libsession/database/StorageProtocol.kt | 2 + .../securesms/database/MmsDatabase.kt | 34 ++++++++-- .../securesms/database/MmsSmsDatabase.java | 10 +++ .../securesms/database/SmsDatabase.java | 25 +++++++ .../securesms/database/Storage.kt | 10 +++ .../prosettings/ProSettingsViewModel.kt | 66 ++++++++++++++++--- 6 files changed, 131 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index a5b45a1580..a164007f1a 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -146,6 +146,8 @@ interface StorageProtocol { fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long fun getTotalPinned(): Int + fun getTotalSentProBadges(): Int + fun getTotalSentLongMessages(): Int fun setPinned(address: Address, isPinned: Boolean) fun isRead(threadId: Long) : Boolean fun setThreadCreationDate(threadId: Long, newDate: Long) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 4d042c6157..6f407ea7f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -112,6 +112,26 @@ class MmsDatabase @Inject constructor( .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } + fun getOutgoingFeatureCount(featureMask: Long): Int { + val db = readableDatabase + + // get list of outgoing message types + val outgoingTypes = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES.joinToString(",") + + // outgoing clause + val outgoingSelection = "($MESSAGE_BOX & ${MmsSmsColumns.Types.BASE_TYPE_MASK}) IN ($outgoingTypes)" + + // feature mask check + val where = "($PRO_FEATURES & $featureMask) != 0 AND $outgoingSelection" + + db.query(TABLE_NAME, arrayOf("COUNT(*)"), where, null, null, null, null).use { cursor -> + if (cursor.moveToFirst()) { + return cursor.getInt(0) + } + } + return 0 + } + fun isDeletedMessage(id: Long): Boolean = writableDatabase.query( TABLE_NAME, @@ -696,7 +716,7 @@ class MmsDatabase @Inject constructor( val deletedMessageIDs: MutableList val deletedMessagesThreadIDs = hashSetOf() - writableDatabase.rawQuery( + writableDatabase.rawQuery( "DELETE FROM $TABLE_NAME WHERE $where RETURNING $ID, $THREAD_ID", *whereArgs ).use { cursor -> @@ -1044,12 +1064,12 @@ class MmsDatabase @Inject constructor( val quoteText = retrievedQuote?.body val quoteMissing = retrievedQuote == null val quoteDeck = ( - (retrievedQuote as? MmsMessageRecord)?.slideDeck ?: - Stream.of(attachmentDatabase.getAttachment(cursor)) - .filter { obj: DatabaseAttachment? -> obj!!.isQuote } - .toList() - .let { SlideDeck(context, it) } - ) + (retrievedQuote as? MmsMessageRecord)?.slideDeck ?: + Stream.of(attachmentDatabase.getAttachment(cursor)) + .filter { obj: DatabaseAttachment? -> obj!!.isQuote } + .toList() + .let { SlideDeck(context, it) } + ) return Quote( quoteId, recipientRepository.getRecipientSync(quoteAuthor.toAddress()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index a881f936ae..7bfd12e60a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -55,6 +55,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext; import kotlin.Pair; import kotlin.Triple; +import network.loki.messenger.libsession_util.protocol.ProFeature; @Singleton public class MmsSmsDatabase extends Database { @@ -427,6 +428,15 @@ public long getConversationCount(long threadId) { return count; } + public int getOutgoingFeatureCount(ProFeature feature) { + long mask = 1L << feature.getBitIndex(); + + int smsCount = smsDatabase.get().getOutgoingFeatureCount(mask); + int mmsCount = mmsDatabase.get().getOutgoingFeatureCount(mask); + + return smsCount + mmsCount; + } + public void incrementReadReceiptCount(SyncMessageId syncMessageId, long timestamp) { smsDatabase.get().incrementReceiptCount(syncMessageId, false, true); mmsDatabase.get().incrementReceiptCount(syncMessageId, timestamp, false, true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 49e0117fd5..0d5772ef89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -282,6 +282,31 @@ public boolean isOutgoingMessage(long id) { return isOutgoing; } + public int getOutgoingFeatureCount(long featureMask) { + SQLiteDatabase db = getReadableDatabase(); + + // get list of outgoing message types + StringBuilder outgoingTypes = new StringBuilder(); + long[] types = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES; + for (int i = 0; i < types.length; i++) { + if (i > 0) outgoingTypes.append(","); + outgoingTypes.append(types[i]); + } + + // outgoing clause + String outgoingSelection = "(" + TYPE + " & " + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + outgoingTypes + ")"; + + // feature mask check + String where = "(" + PRO_FEATURES + " & " + featureMask + ") != 0 AND " + outgoingSelection; + + try (Cursor cursor = db.query(TABLE_NAME, new String[]{"COUNT(*)"}, where, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getInt(0); + } + } + return 0; + } + public boolean isDeletedMessage(long id) { SQLiteDatabase database = getWritableDatabase(); Cursor cursor = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 1d3ef8fa50..8c9069ef1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -8,6 +8,8 @@ import network.loki.messenger.libsession_util.PRIORITY_PINNED import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.ReadableUserGroupsConfig +import network.loki.messenger.libsession_util.protocol.ProMessageFeature +import network.loki.messenger.libsession_util.protocol.ProProfileFeature import network.loki.messenger.libsession_util.util.BitSet import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.Bytes @@ -946,6 +948,14 @@ open class Storage @Inject constructor( } } + override fun getTotalSentProBadges(): Int { + return mmsSmsDatabase.getOutgoingFeatureCount(ProProfileFeature.PRO_BADGE) + } + + override fun getTotalSentLongMessages(): Int { + return mmsSmsDatabase.getOutgoingFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) + } + override fun setPinned(address: Address, isPinned: Boolean) { val isLocalNumber = address.address == getUserPublicKey() configFactory.withMutableUserConfigs { configs -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt index 0afe1c3fdc..4a6451be95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/prosettings/ProSettingsViewModel.kt @@ -15,13 +15,18 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import network.loki.messenger.R +import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.NonTranslatableStringConstants import org.session.libsession.utilities.StringSubstitutionConstants.ACTION_TYPE_KEY @@ -69,6 +74,7 @@ class ProSettingsViewModel @AssistedInject constructor( private val prefs: TextSecurePreferences, private val proDetailsRepository: ProDetailsRepository, private val configFactory: Lazy, + private val storage: StorageProtocol, ) : ViewModel() { @AssistedFactory @@ -184,8 +190,11 @@ class ProSettingsViewModel @AssistedInject constructor( private fun generateState(proDataState: ProDataState){ val subType = proDataState.type + // calculate stats for pro users + if(subType is ProStatus.Active) refreshProStats() + _proSettingsUIState.update { - ProSettingsState( + it.copy( proDataState = proDataState, subscriptionExpiryLabel = when(subType){ is ProStatus.Active.AutoRenewing -> @@ -206,14 +215,6 @@ class ProSettingsViewModel @AssistedInject constructor( is ProStatus.Active -> subType.duration.expiryFromNow() else -> "" }, - proStats = State.Success( //todo PRO calculate properly - ProStats( - groupsUpdated = 0, - pinnedConversations = 12, - proBadges = 6400, - longMessages = 215, - ) - ) ) } } @@ -811,6 +812,53 @@ class ProSettingsViewModel @AssistedInject constructor( } } + private fun refreshProStats(){ + viewModelScope.launch { + // show a loader for the stats + _proSettingsUIState.update { + it.copy( + proStats = State.Loading + ) + } + + // calculate pro stats values + try { + val stats = withContext(Dispatchers.IO) { + val pinsDeferred = async { + storage.getTotalPinned() + } + + val badgesDeferred = async { + storage.getTotalSentProBadges() + } + + val longMsgDeferred = async { + storage.getTotalSentLongMessages() + } + + ProStats( + groupsUpdated = 0, + pinnedConversations = pinsDeferred.await(), + proBadges = badgesDeferred.await(), + longMessages = longMsgDeferred.await(), + ) + } + + // update ui with results + _proSettingsUIState.update { + it.copy(proStats = State.Success(stats)) + } + } catch (e: Exception) { + // currently the UI doesn't have an error display + // it will look like it's still loading + // but the logic is there in case we have a look for stats errors + _proSettingsUIState.update { + it.copy(proStats = State.Error(e)) + } + } + } + } + sealed interface Commands { data class ShowOpenUrlDialog(val url: String?) : Commands data object ShowTCPolicyDialog: Commands From dd6032478220a50b4e38480ecb9097b52c249a04 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Thu, 27 Nov 2025 15:39:50 +1100 Subject: [PATCH 4/7] SES-4929 - requested tags for QA --- .../settings/ConversationSettingsDialogs.kt | 1 + .../securesms/preferences/SettingsScreen.kt | 4 +++- .../securesms/ui/ProComponents.kt | 21 ++++++++++++++----- .../securesms/ui/components/Text.kt | 8 ++++++- .../src/main/res/values/strings.xml | 6 ++++++ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt index ccb1e3f534..d058fb29f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsDialogs.kt @@ -263,6 +263,7 @@ fun ConversationSettingsDialogs( iconRes = R.drawable.ic_pro_badge, iconSize = 40.sp to 18.sp, style = LocalType.current.large, + textQaTag = stringResource(R.string.qa_cta_body) ) }, content = { CTAImage(heroImage = R.drawable.cta_hero_group) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt index c6c96c25e2..06a27e8455 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsScreen.kt @@ -1010,7 +1010,9 @@ fun AnimatedProCTA( // main message Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .qaTag(R.string.qa_cta_body) + .align(Alignment.CenterHorizontally), text = stringResource(R.string.proAnimatedDisplayPicture), textAlign = TextAlign.Center, style = LocalType.current.base.copy( diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt index a5a48edf5f..527a5e6f23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/ProComponents.kt @@ -310,7 +310,10 @@ fun SessionProCTA( // features if (features.isNotEmpty()) { features.forEachIndexed { index, feature -> - ProCTAFeature(data = feature) + ProCTAFeature( + modifier = Modifier.qaTag(stringResource(R.string.qa_cta_feature) + index.toString()), + data = feature + ) if (index < features.size - 1) { Spacer(Modifier.height(LocalDimensions.current.xsSpacing)) } @@ -330,7 +333,9 @@ fun SessionProCTA( ) { positiveButtonText?.let { AccentFillButtonRect( - modifier = Modifier.then( + modifier = Modifier + .qaTag(R.string.qa_cta_button_positive) + .then( if (negativeButtonText != null) Modifier.weight(1f) else Modifier @@ -342,7 +347,9 @@ fun SessionProCTA( negativeButtonText?.let { TertiaryFillButtonRect( - modifier = Modifier.then( + modifier = Modifier + .qaTag(R.string.qa_cta_button_negative) + .then( if (positiveButtonText != null) Modifier.weight(1f) else Modifier @@ -443,7 +450,9 @@ fun SimpleSessionProCTA( badgeAtStart = badgeAtStart, textContent = { Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .qaTag(R.string.qa_cta_body) + .align(Alignment.CenterHorizontally), text = text, textAlign = TextAlign.Center, style = LocalType.current.base.copy( @@ -495,7 +504,9 @@ fun AnimatedSessionProCTA( modifier = modifier, textContent = { Text( - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = Modifier + .qaTag(R.string.qa_cta_body) + .align(Alignment.CenterHorizontally), text = text, textAlign = TextAlign.Center, style = LocalType.current.base.copy( diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Text.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Text.kt index 25cd0a4bd2..39f2a57182 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/Text.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/Text.kt @@ -258,6 +258,7 @@ fun AnnotatedTextWithIcon( modifier: Modifier = Modifier, style: TextStyle = LocalType.current.base, color: Color = Color.Unspecified, + textQaTag: String? = null, iconSize: Pair = 12.sp to 12.sp, iconPaddingValues: PaddingValues = PaddingValues(start = style.lineHeight.value.dp * 0.2f), onIconClick: (() -> Unit)? = null @@ -296,6 +297,7 @@ fun AnnotatedTextWithIcon( modifier: Modifier = Modifier, style: TextStyle = LocalType.current.base, color: Color = Color.Unspecified, + textQaTag: String? = null, iconSize: Pair = 12.sp to 12.sp, ) { var inlineContent: Map = mapOf() @@ -330,7 +332,11 @@ fun AnnotatedTextWithIcon( Text( text = annotated, - modifier = modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth() + .then( + if (textQaTag != null) Modifier.qaTag(textQaTag) + else Modifier + ), style = style, color = color, textAlign = TextAlign.Center, diff --git a/content-descriptions/src/main/res/values/strings.xml b/content-descriptions/src/main/res/values/strings.xml index a727f999e5..d0982ac176 100644 --- a/content-descriptions/src/main/res/values/strings.xml +++ b/content-descriptions/src/main/res/values/strings.xml @@ -291,6 +291,12 @@ action-item-icon qa-blocked-contacts-settings-item + + cta-body + cta-feature- + cta-button-positive + cta-button-negative + qa-collapsing-footer-action \ No newline at end of file From 581cc87029831c3253e55b4d917867ebb68760a4 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 28 Nov 2025 09:34:13 +1100 Subject: [PATCH 5/7] Renamed methods --- .../org/thoughtcrime/securesms/database/MmsDatabase.kt | 2 +- .../thoughtcrime/securesms/database/MmsSmsDatabase.java | 7 ++++--- .../org/thoughtcrime/securesms/database/SmsDatabase.java | 2 +- .../java/org/thoughtcrime/securesms/database/Storage.kt | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 6f407ea7f7..7297de90ce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -112,7 +112,7 @@ class MmsDatabase @Inject constructor( .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } - fun getOutgoingFeatureCount(featureMask: Long): Int { + fun getOutgoingProFeatureCount(featureMask: Long): Int { val db = readableDatabase // get list of outgoing message types diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 7bfd12e60a..f7ecd4dcc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -56,6 +56,7 @@ import kotlin.Pair; import kotlin.Triple; import network.loki.messenger.libsession_util.protocol.ProFeature; +import network.loki.messenger.libsession_util.protocol.ProMessageFeature; @Singleton public class MmsSmsDatabase extends Database { @@ -428,11 +429,11 @@ public long getConversationCount(long threadId) { return count; } - public int getOutgoingFeatureCount(ProFeature feature) { + public int getOutgoingProFeatureCount(ProFeature feature) { long mask = 1L << feature.getBitIndex(); - int smsCount = smsDatabase.get().getOutgoingFeatureCount(mask); - int mmsCount = mmsDatabase.get().getOutgoingFeatureCount(mask); + int smsCount = smsDatabase.get().getOutgoingProFeatureCount(mask); + int mmsCount = mmsDatabase.get().getOutgoingProFeatureCount(mask); return smsCount + mmsCount; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 0d5772ef89..d710bd2c70 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -282,7 +282,7 @@ public boolean isOutgoingMessage(long id) { return isOutgoing; } - public int getOutgoingFeatureCount(long featureMask) { + public int getOutgoingProFeatureCount(long featureMask) { SQLiteDatabase db = getReadableDatabase(); // get list of outgoing message types diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 8c9069ef1a..3e0d4ac736 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -949,11 +949,11 @@ open class Storage @Inject constructor( } override fun getTotalSentProBadges(): Int { - return mmsSmsDatabase.getOutgoingFeatureCount(ProProfileFeature.PRO_BADGE) + return mmsSmsDatabase.getOutgoingProFeatureCount(ProProfileFeature.PRO_BADGE) } override fun getTotalSentLongMessages(): Int { - return mmsSmsDatabase.getOutgoingFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) + return mmsSmsDatabase.getOutgoingProFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) } override fun setPinned(address: Address, isPinned: Boolean) { From db689c29fc4eb6a6ef19def3e7a8f65b182b6e2e Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 28 Nov 2025 14:06:38 +1100 Subject: [PATCH 6/7] Updated logic to match two feature rows in DB --- .../libsession/database/StorageProtocol.kt | 6 ++-- .../settings/ConversationSettingsViewModel.kt | 34 +++++++++++-------- .../securesms/database/MmsDatabase.kt | 12 +++---- .../securesms/database/SmsDatabase.java | 15 ++++---- .../securesms/database/Storage.kt | 16 +++++---- .../securesms/home/HomeViewModel.kt | 33 ++++++++++-------- 6 files changed, 66 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index a164007f1a..7506fe5d6d 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -145,9 +145,9 @@ interface StorageProtocol { fun getLastUpdated(threadID: Long): Long fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long - fun getTotalPinned(): Int - fun getTotalSentProBadges(): Int - fun getTotalSentLongMessages(): Int + suspend 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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 6ed520d3be..6fbcba4257 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -11,6 +11,7 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity.CLIPBOARD_SERVICE import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel import com.squareup.phrase.Phrase import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -711,20 +712,25 @@ class ConversationSettingsViewModel @AssistedInject constructor( } private fun pinConversation(){ - // check the pin limit before continuing - val totalPins = storage.getTotalPinned() - val maxPins = proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) - if(totalPins >= maxPins){ - // the user has reached the pin limit, show the CTA - _dialogState.update { - it.copy(pinCTA = PinProCTA( - overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.proDataState.value.type - )) - } - } else { - viewModelScope.launch { - storage.setPinned(address, true) + viewModelScope.launch { + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = + proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) + if (totalPins >= maxPins) { + // the user has reached the pin limit, show the CTA + _dialogState.update { + it.copy( + pinCTA = PinProCTA( + overTheLimit = totalPins > maxPins, + proSubscription = proStatusManager.proDataState.value.type + ) + ) + } + } else { + viewModelScope.launch { + storage.setPinned(address, true) + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 6efb3633f7..d1f7583a9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -124,17 +124,17 @@ class MmsDatabase @Inject constructor( val outgoingTypes = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES.joinToString(",") // outgoing clause - val outgoingSelection = "($MESSAGE_BOX & ${MmsSmsColumns.Types.BASE_TYPE_MASK}) IN ($outgoingTypes)" + val outgoingSelection = + "($MESSAGE_BOX & ${MmsSmsColumns.Types.BASE_TYPE_MASK}) IN ($outgoingTypes)" // feature mask check - val where = "($PRO_FEATURES & $featureMask) != 0 AND $outgoingSelection" + val where = + "(($PRO_MESSAGE_FEATURES & $featureMask) != 0 OR " + + " ($PRO_PROFILE_FEATURES & $featureMask) != 0) AND $outgoingSelection" db.query(TABLE_NAME, arrayOf("COUNT(*)"), where, null, null, null, null).use { cursor -> - if (cursor.moveToFirst()) { - return cursor.getInt(0) - } + return if (cursor.moveToFirst()) cursor.getInt(0) else 0 } - return 0 } fun isDeletedMessage(id: Long): Boolean = diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index c8fb35e6a8..654d0c0e2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -297,19 +297,22 @@ public boolean isOutgoingMessage(long id) { public int getOutgoingProFeatureCount(long featureMask) { SQLiteDatabase db = getReadableDatabase(); - // get list of outgoing message types - StringBuilder outgoingTypes = new StringBuilder(); long[] types = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES; + StringBuilder outgoingTypes = new StringBuilder(types.length * 3); for (int i = 0; i < types.length; i++) { if (i > 0) outgoingTypes.append(","); outgoingTypes.append(types[i]); } // outgoing clause - String outgoingSelection = "(" + TYPE + " & " + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + outgoingTypes + ")"; - - // feature mask check - String where = "(" + PRO_FEATURES + " & " + featureMask + ") != 0 AND " + outgoingSelection; + String outgoingSelection = + "(" + TYPE + " & " + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + outgoingTypes + ")"; + + // feature check + String where = + "((" + PRO_MESSAGE_FEATURES + " & " + featureMask + ") != 0" + + " OR (" + PRO_PROFILE_FEATURES + " & " + featureMask + ") != 0)" + + " AND " + outgoingSelection; try (Cursor cursor = db.query(TABLE_NAME, new String[]{"COUNT(*)"}, where, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index ab5bfaac27..ad319d7d39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,10 +4,14 @@ import android.content.Context import android.net.Uri import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.PRIORITY_PINNED import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ReadableUserGroupsConfig +import network.loki.messenger.libsession_util.protocol.ProMessageFeature +import network.loki.messenger.libsession_util.protocol.ProProfileFeature import network.loki.messenger.libsession_util.util.BlindKeyAPI import network.loki.messenger.libsession_util.util.Bytes import network.loki.messenger.libsession_util.util.Conversation @@ -903,8 +907,8 @@ open class Storage @Inject constructor( return mmsSmsDb.getConversationCount(threadID) } - override fun getTotalPinned(): Int { - return configFactory.withUserConfigs { + override suspend fun getTotalPinned(): Int = withContext(Dispatchers.IO) { + configFactory.withUserConfigs { var totalPins = 0 // check if the note to self is pinned @@ -945,12 +949,12 @@ open class Storage @Inject constructor( } } - override fun getTotalSentProBadges(): Int { - return mmsSmsDatabase.getOutgoingProFeatureCount(ProProfileFeature.PRO_BADGE) + override suspend fun getTotalSentProBadges(): Int = withContext(Dispatchers.IO) { + mmsSmsDatabase.getOutgoingProFeatureCount(ProProfileFeature.PRO_BADGE) } - override fun getTotalSentLongMessages(): Int { - return mmsSmsDatabase.getOutgoingProFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) + override suspend fun getTotalSentLongMessages(): Int = withContext(Dispatchers.IO) { + mmsSmsDatabase.getOutgoingProFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) } override fun setPinned(address: Address, isPinned: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index bf4849a7ec..e8dbd3af79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -268,22 +268,25 @@ class HomeViewModel @Inject constructor( } fun setPinned(address: Address, pinned: Boolean) { - // check the pin limit before continuing - val totalPins = storage.getTotalPinned() - val maxPins = proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) - if (pinned && totalPins >= maxPins) { - // the user has reached the pin limit, show the CTA - _dialogsState.update { - it.copy( - pinCTA = PinProCTA( - overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.proDataState.value.type + viewModelScope.launch { + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = + proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) + if (pinned && totalPins >= maxPins) { + // the user has reached the pin limit, show the CTA + _dialogsState.update { + it.copy( + pinCTA = PinProCTA( + overTheLimit = totalPins > maxPins, + proSubscription = proStatusManager.proDataState.value.type + ) ) - ) - } - } else { - viewModelScope.launch(Dispatchers.Default) { - storage.setPinned(address, pinned) + } + } else { + viewModelScope.launch(Dispatchers.Default) { + storage.setPinned(address, pinned) + } } } } From ded6ca8ea40816cf8207faf42c8054617fac1c46 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Fri, 28 Nov 2025 15:17:28 +1100 Subject: [PATCH 7/7] Splitting two column logic --- .../libsession/database/StorageProtocol.kt | 2 +- .../settings/ConversationSettingsViewModel.kt | 34 +++++++++---------- .../securesms/database/MmsDatabase.kt | 22 +++++++----- .../securesms/database/MmsSmsDatabase.java | 14 ++++---- .../securesms/database/SmsDatabase.java | 21 +++++++----- .../securesms/database/Storage.kt | 25 ++++++++++---- .../securesms/home/HomeViewModel.kt | 34 +++++++++---------- 7 files changed, 86 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt index 7506fe5d6d..1d99e66c52 100644 --- a/app/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/app/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -145,7 +145,7 @@ interface StorageProtocol { fun getLastUpdated(threadID: Long): Long fun trimThreadBefore(threadID: Long, timestamp: Long) fun getMessageCount(threadID: Long): Long - suspend fun getTotalPinned(): Int + fun getTotalPinned(): Int suspend fun getTotalSentProBadges(): Int suspend fun getTotalSentLongMessages(): Int fun setPinned(address: Address, isPinned: Boolean) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt index 6fbcba4257..b54cba5abc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/settings/ConversationSettingsViewModel.kt @@ -712,25 +712,23 @@ class ConversationSettingsViewModel @AssistedInject constructor( } private fun pinConversation(){ - viewModelScope.launch { - // check the pin limit before continuing - val totalPins = storage.getTotalPinned() - val maxPins = - proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) - if (totalPins >= maxPins) { - // the user has reached the pin limit, show the CTA - _dialogState.update { - it.copy( - pinCTA = PinProCTA( - overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.proDataState.value.type - ) + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = + proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) + if (totalPins >= maxPins) { + // the user has reached the pin limit, show the CTA + _dialogState.update { + it.copy( + pinCTA = PinProCTA( + overTheLimit = totalPins > maxPins, + proSubscription = proStatusManager.proDataState.value.type ) - } - } else { - viewModelScope.launch { - storage.setPinned(address, true) - } + ) + } + } else { + viewModelScope.launch { + storage.setPinned(address, true) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index d1f7583a9a..70a4e50947 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -117,24 +117,30 @@ class MmsDatabase @Inject constructor( .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } - fun getOutgoingProFeatureCount(featureMask: Long): Int { - val db = readableDatabase + fun getOutgoingMessageProFeatureCount(featureMask: Long): Int { + return getOutgoingProFeatureCountInternal(PRO_MESSAGE_FEATURES, featureMask) + } + + fun getOutgoingProfileProFeatureCount(featureMask: Long): Int { + return getOutgoingProFeatureCountInternal(PRO_PROFILE_FEATURES, featureMask) + } - // get list of outgoing message types + private fun getOutgoingProFeatureCountInternal(column: String, featureMask: Long): Int { + val db = readableDatabase val outgoingTypes = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES.joinToString(",") // outgoing clause val outgoingSelection = "($MESSAGE_BOX & ${MmsSmsColumns.Types.BASE_TYPE_MASK}) IN ($outgoingTypes)" - // feature mask check - val where = - "(($PRO_MESSAGE_FEATURES & $featureMask) != 0 OR " + - " ($PRO_PROFILE_FEATURES & $featureMask) != 0) AND $outgoingSelection" + val where = "($column & $featureMask) != 0 AND $outgoingSelection" db.query(TABLE_NAME, arrayOf("COUNT(*)"), where, null, null, null, null).use { cursor -> - return if (cursor.moveToFirst()) cursor.getInt(0) else 0 + if (cursor.moveToFirst()) { + return cursor.getInt(0) + } } + return 0 } fun isDeletedMessage(id: Long): Boolean = diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index f7ecd4dcc4..1aa7363678 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -429,15 +429,17 @@ public long getConversationCount(long threadId) { return count; } - public int getOutgoingProFeatureCount(ProFeature feature) { - long mask = 1L << feature.getBitIndex(); - - int smsCount = smsDatabase.get().getOutgoingProFeatureCount(mask); - int mmsCount = mmsDatabase.get().getOutgoingProFeatureCount(mask); + public int getOutgoingMessageProFeatureCount(long featureMask) { + return smsDatabase.get().getOutgoingMessageProFeatureCount(featureMask) + + mmsDatabase.get().getOutgoingMessageProFeatureCount(featureMask); + } - return smsCount + mmsCount; + public int getOutgoingProfileProFeatureCount(long featureMask) { + return smsDatabase.get().getOutgoingProfileProFeatureCount(featureMask) + + mmsDatabase.get().getOutgoingProfileProFeatureCount(featureMask); } + public void incrementReadReceiptCount(SyncMessageId syncMessageId, long timestamp) { smsDatabase.get().incrementReceiptCount(syncMessageId, false, true); mmsDatabase.get().incrementReceiptCount(syncMessageId, timestamp, false, true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 654d0c0e2b..7a6204dfe6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -294,31 +294,36 @@ public boolean isOutgoingMessage(long id) { return isOutgoing; } - public int getOutgoingProFeatureCount(long featureMask) { + public int getOutgoingMessageProFeatureCount(long featureMask) { + return getOutgoingProFeatureCountInternal(PRO_MESSAGE_FEATURES, featureMask); + } + + public int getOutgoingProfileProFeatureCount(long featureMask) { + return getOutgoingProFeatureCountInternal(PRO_PROFILE_FEATURES, featureMask); + } + + private int getOutgoingProFeatureCountInternal(@NonNull String columnName, long featureMask) { SQLiteDatabase db = getReadableDatabase(); + // outgoing clause + StringBuilder outgoingTypes = new StringBuilder(); long[] types = MmsSmsColumns.Types.OUTGOING_MESSAGE_TYPES; - StringBuilder outgoingTypes = new StringBuilder(types.length * 3); for (int i = 0; i < types.length; i++) { if (i > 0) outgoingTypes.append(","); outgoingTypes.append(types[i]); } - // outgoing clause String outgoingSelection = "(" + TYPE + " & " + MmsSmsColumns.Types.BASE_TYPE_MASK + ") IN (" + outgoingTypes + ")"; - // feature check - String where = - "((" + PRO_MESSAGE_FEATURES + " & " + featureMask + ") != 0" + - " OR (" + PRO_PROFILE_FEATURES + " & " + featureMask + ") != 0)" + - " AND " + outgoingSelection; + String where = "(" + columnName + " & " + featureMask + ") != 0 AND " + outgoingSelection; try (Cursor cursor = db.query(TABLE_NAME, new String[]{"COUNT(*)"}, where, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { return cursor.getInt(0); } } + return 0; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index ad319d7d39..c0c8576408 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -10,6 +10,7 @@ import network.loki.messenger.libsession_util.MutableConversationVolatileConfig import network.loki.messenger.libsession_util.PRIORITY_PINNED import network.loki.messenger.libsession_util.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ReadableUserGroupsConfig +import network.loki.messenger.libsession_util.protocol.ProFeature import network.loki.messenger.libsession_util.protocol.ProMessageFeature import network.loki.messenger.libsession_util.protocol.ProProfileFeature import network.loki.messenger.libsession_util.util.BlindKeyAPI @@ -907,8 +908,8 @@ open class Storage @Inject constructor( return mmsSmsDb.getConversationCount(threadID) } - override suspend fun getTotalPinned(): Int = withContext(Dispatchers.IO) { - configFactory.withUserConfigs { + override fun getTotalPinned(): Int { + return configFactory.withUserConfigs { var totalPins = 0 // check if the note to self is pinned @@ -949,12 +950,22 @@ open class Storage @Inject constructor( } } - override suspend fun getTotalSentProBadges(): Int = withContext(Dispatchers.IO) { - mmsSmsDatabase.getOutgoingProFeatureCount(ProProfileFeature.PRO_BADGE) - } + override suspend fun getTotalSentProBadges(): Int = + getTotalSentForFeature(ProProfileFeature.PRO_BADGE) + + override suspend fun getTotalSentLongMessages(): Int = + getTotalSentForFeature(ProMessageFeature.HIGHER_CHARACTER_LIMIT) + + suspend fun getTotalSentForFeature(feature: ProFeature): Int = withContext(Dispatchers.IO) { + val mask = 1L shl feature.bitIndex - override suspend fun getTotalSentLongMessages(): Int = withContext(Dispatchers.IO) { - mmsSmsDatabase.getOutgoingProFeatureCount(ProMessageFeature.HIGHER_CHARACTER_LIMIT) + when (feature) { + is ProMessageFeature -> + mmsSmsDatabase.getOutgoingMessageProFeatureCount(mask) + + is ProProfileFeature -> + mmsSmsDatabase.getOutgoingProfileProFeatureCount(mask) + } } override fun setPinned(address: Address, isPinned: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt index e8dbd3af79..3333e6ad61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeViewModel.kt @@ -268,25 +268,23 @@ class HomeViewModel @Inject constructor( } fun setPinned(address: Address, pinned: Boolean) { - viewModelScope.launch { - // check the pin limit before continuing - val totalPins = storage.getTotalPinned() - val maxPins = - proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) - if (pinned && totalPins >= maxPins) { - // the user has reached the pin limit, show the CTA - _dialogsState.update { - it.copy( - pinCTA = PinProCTA( - overTheLimit = totalPins > maxPins, - proSubscription = proStatusManager.proDataState.value.type - ) + // check the pin limit before continuing + val totalPins = storage.getTotalPinned() + val maxPins = + proStatusManager.getPinnedConversationLimit(recipientRepository.getSelf().isPro) + if (pinned && totalPins >= maxPins) { + // the user has reached the pin limit, show the CTA + _dialogsState.update { + it.copy( + pinCTA = PinProCTA( + overTheLimit = totalPins > maxPins, + proSubscription = proStatusManager.proDataState.value.type ) - } - } else { - viewModelScope.launch(Dispatchers.Default) { - storage.setPinned(address, pinned) - } + ) + } + } else { + viewModelScope.launch(Dispatchers.Default) { + storage.setPinned(address, pinned) } } }