From f6d50ac85822d49cd087692322d625f139622e31 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Tue, 15 Oct 2024 10:53:19 +1100 Subject: [PATCH 1/5] Feature/strings nice to haves (#1686) * Initial commit with high level structure for new message deletion logic * Adding admin logic * New dialog styles * Matching existing dialog closer to new designs * Using the theme attribute danger instead of a hardcoded colour * Using classes for the dialogs Also cleaned up older references to align with newer look * Adding cancel handling Cleaning unused code * Handling local deletion with batch message deletion * Reusing the 'delete locally' * Delete on device should "marl the message as deleted", not remove it from the db directly * Displaying "marked as deleted" messages Split the `BASE_DELETED_TYPE` into two types: BASE_DELETED_OUTGOING_TYPE and BASE_DELETED_INCOMING_TYPE so we can differentiate them visually. * Proper handling of merged code * Removed temp bg color * Making sure the deleted message view is visible * Renaming functions for clarity * Adding the ability to customise the text for the deleted control messages * Removing code that was added back from merging dev back in * Using the updated strings * Toast confirmation on 'delete locally' * Recreating xml dialogs in Compose and moved logic in VM * Removing hardcoded strings * Updated message deletion logic Still need to finalise "note to self" and "legacy groups" * Deletion logic rework Moving away from promises * More deletion logic Hndling unsend request retrieval as per figma docs * Making sure multi-select works as expectec * Multi message handling Sharing admin logic * Deleting reactions when deleting a message * Deleting reactions when deleting a message * Grabbing server hash from notification data * Fixed unit tests * Handling deletion od "marked as deleted" messages * Handling Control Messages longpress and deletion * Back up handling of no map data for huawei notifications Also rethemed the send buttona dn home plus button to have better ax contrast by standardising the colour displayed on the accent color to be the same as the one on the sent messages * Removed test line * Reworking the deletion dialogs We removed the 'delete locally' dialog, instead we show the 'delete for everyone' with the second option disabled * Outgoing messages can all be marked as 'delete for everyone' Cleaned up invisible copy button on black bgs * Adding a confirmation dialog when clearing emoji * Message request text update * Restyling menu items to not show in uppercase * Proper hint for seach * Do not show seconds when they're 0 * Making the change to "hidden recovery" reactive so it can be dynamically updated in the settings page. This can be simplified once we make SharedPreferences widely accessible as Flows --------- Co-authored-by: ThomasArtProcessors <71994342+ThomasArtProcessors@users.noreply.github.com> --- app/build.gradle | 6 +-- .../conversation/v2/ConversationActivityV2.kt | 11 ++-- .../conversation/v2/ConversationV2Dialogs.kt | 37 +++++++++++-- .../conversation/v2/ConversationViewModel.kt | 54 +++++++++++++++++++ .../securesms/preferences/SettingsActivity.kt | 24 +++++++-- .../preferences/SettingsViewModel.kt | 11 ++++ .../RecoveryPasswordActivity.kt | 11 +++- .../RecoveryPasswordViewModel.kt | 4 -- .../res/layout/activity_conversation_v2.xml | 4 +- .../res/layout/view_global_search_input.xml | 2 +- app/src/main/res/values/styles.xml | 6 +++ app/src/main/res/values/themes.xml | 2 + .../v2/ConversationViewModelTest.kt | 3 +- .../libsession/utilities/LocalisedTimeUtil.kt | 3 +- 14 files changed, 148 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 95e37a849c..da0d2f3fa5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -268,9 +268,9 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" - implementation 'androidx.activity:activity-ktx:1.5.1' - implementation 'androidx.activity:activity-compose:1.5.1' - implementation 'androidx.fragment:fragment-ktx:1.5.3' + implementation 'androidx.activity:activity-ktx:1.9.2' + implementation 'androidx.activity:activity-compose:1.9.2' + implementation 'androidx.fragment:fragment-ktx:1.8.4' implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.work:work-runtime-ktx:2.7.1" 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 ad2db82cfb..0132a876cf 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 @@ -1536,14 +1536,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe sendEmojiRemoval(emoji, message) } + /** + * Called when the user is attempting to clear all instance of a specific emoji. + */ override fun onClearAll(emoji: String, messageId: MessageId) { - reactionDb.deleteEmojiReactions(emoji, messageId) - viewModel.openGroup?.let { openGroup -> - lokiMessageDb.getServerID(messageId.id, !messageId.mms)?.let { serverId -> - OpenGroupApi.deleteAllReactions(openGroup.room, openGroup.server, serverId, emoji) - } - } - threadDb.notifyThreadUpdated(viewModel.threadId) + viewModel.onEmojiClear(emoji, messageId) } override fun onMicrophoneButtonMove(event: MotionEvent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt index 26775f01c2..b9756a13be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt @@ -14,12 +14,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.squareup.phrase.Phrase import network.loki.messenger.R -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.HideDeleteAllDevicesDialog -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.HideDeleteEveryoneDialog -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.MarkAsDeletedForEveryone -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.MarkAsDeletedLocally -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.ShowOpenUrlDialog +import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY +import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.* import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.DialogButtonModel import org.thoughtcrime.securesms.ui.GetString @@ -202,6 +201,34 @@ fun ConversationV2Dialogs( ) } + + // Clear emoji + if(dialogsState.clearAllEmoji != null){ + AlertDialog( + onDismissRequest = { + // hide dialog + sendCommand(HideClearEmoji) + }, + text = stringResource(R.string.emojiReactsClearAll).let { txt -> + Phrase.from(txt).put(EMOJI_KEY, dialogsState.clearAllEmoji.emoji).format().toString() + }, + buttons = listOf( + DialogButtonModel( + text = GetString(stringResource(id = R.string.clear)), + color = LocalColors.current.danger, + onClick = { + // delete emoji + sendCommand( + ClearEmoji(dialogsState.clearAllEmoji.emoji, dialogsState.clearAllEmoji.messageId) + ) + } + ), + DialogButtonModel( + GetString(stringResource(R.string.cancel)) + ) + ) + ) + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 316aeec86c..29a9b9d79d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -37,7 +37,10 @@ import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.audio.AudioSlidePlayer import org.thoughtcrime.securesms.database.LokiMessageDatabase +import org.thoughtcrime.securesms.database.ReactionDatabase import org.thoughtcrime.securesms.database.Storage +import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.groups.OpenGroupManager @@ -52,6 +55,8 @@ class ConversationViewModel( private val repository: ConversationRepository, private val storage: Storage, private val messageDataProvider: MessageDataProvider, + private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, private val lokiMessageDb: LokiMessageDatabase, private val textSecurePreferences: TextSecurePreferences ) : ViewModel() { @@ -720,6 +725,12 @@ class ConversationViewModel( } } + is Commands.HideClearEmoji -> { + _dialogsState.update { + it.copy(clearAllEmoji = null) + } + } + is Commands.HideDeleteAllDevicesDialog -> { _dialogsState.update { it.copy(deleteAllDevices = null) @@ -737,6 +748,35 @@ class ConversationViewModel( is Commands.MarkAsDeletedForEveryone -> { markAsDeletedForEveryone(command.data) } + + + is Commands.ClearEmoji -> { + clearEmoji(command.emoji, command.messageId) + } + } + } + + private fun clearEmoji(emoji: String, messageId: MessageId){ + viewModelScope.launch(Dispatchers.Default) { + reactionDb.deleteEmojiReactions(emoji, messageId) + openGroup?.let { openGroup -> + lokiMessageDb.getServerID(messageId.id, !messageId.mms)?.let { serverId -> + OpenGroupApi.deleteAllReactions( + openGroup.room, + openGroup.server, + serverId, + emoji + ) + } + } + threadDb.notifyThreadUpdated(threadId) + } + } + + fun onEmojiClear(emoji: String, messageId: MessageId) { + // show a confirmation dialog + _dialogsState.update { + it.copy(clearAllEmoji = ClearAllEmoji(emoji, messageId)) } } @@ -753,6 +793,8 @@ class ConversationViewModel( private val repository: ConversationRepository, private val storage: Storage, private val messageDataProvider: MessageDataProvider, + private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, private val lokiMessageDb: LokiMessageDatabase, private val textSecurePreferences: TextSecurePreferences ) : ViewModelProvider.Factory { @@ -765,6 +807,8 @@ class ConversationViewModel( repository = repository, storage = storage, messageDataProvider = messageDataProvider, + threadDb = threadDb, + reactionDb = reactionDb, lokiMessageDb = lokiMessageDb, textSecurePreferences = textSecurePreferences ) as T @@ -773,6 +817,7 @@ class ConversationViewModel( data class DialogsState( val openLinkDialogUrl: String? = null, + val clearAllEmoji: ClearAllEmoji? = null, val deleteEveryone: DeleteForEveryoneDialogData? = null, val deleteAllDevices: DeleteForEveryoneDialogData? = null, ) @@ -785,10 +830,19 @@ class ConversationViewModel( val warning: String? = null ) + data class ClearAllEmoji( + val emoji: String, + val messageId: MessageId + ) + sealed class Commands { data class ShowOpenUrlDialog(val url: String?) : Commands() + + data class ClearEmoji(val emoji:String, val messageId: MessageId) : Commands() + data object HideDeleteEveryoneDialog : Commands() data object HideDeleteAllDevicesDialog : Commands() + data object HideClearEmoji : Commands() data class MarkAsDeletedLocally(val messages: Set): Commands() data class MarkAsDeletedForEveryone(val data: DeleteForEveryoneDialogData): Commands() diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index bbf075ca7c..eaebcb57f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -134,6 +134,16 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { cropImage(inputFile, outputFile) } + private val hideRecoveryLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult + + if(result.data?.getBooleanExtra(RecoveryPasswordActivity.RESULT_RECOVERY_HIDDEN, false) == true){ + viewModel.permanentlyHidePassword() + } + } + private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage) private var showAvatarDialog: Boolean by mutableStateOf(false) @@ -183,7 +193,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } binding.composeView.setThemedContent { - Buttons() + val recoveryHidden by viewModel.recoveryHidden.collectAsState() + Buttons(recoveryHidden = recoveryHidden) } lifecycleScope.launch { @@ -390,7 +401,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun Buttons() { + fun Buttons( + recoveryHidden: Boolean + ) { Column( modifier = Modifier .padding(horizontal = LocalDimensions.current.spacing) @@ -452,12 +465,15 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { Divider() // Only show the recovery password option if the user has not chosen to permanently hide it - if (!prefs.getHidePassword()) { + if (!recoveryHidden) { LargeItemButton( R.string.sessionRecoveryPassword, R.drawable.ic_shield_outline, Modifier.contentDescription(R.string.AccessibilityId_sessionRecoveryPasswordMenuItem) - ) { push() } + ) { + hideRecoveryLauncher.launch(Intent(baseContext, RecoveryPasswordActivity::class.java)) + overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) + } Divider() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index bedc913109..5b6fa78d44 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import network.loki.messenger.R @@ -65,6 +66,10 @@ class SettingsViewModel @Inject constructor( val showLoader: StateFlow get() = _showLoader + private val _recoveryHidden: MutableStateFlow = MutableStateFlow(prefs.getHidePassword()) + val recoveryHidden: StateFlow + get() = _recoveryHidden + /** * Refreshes the avatar on the main settings page */ @@ -230,6 +235,12 @@ class SettingsViewModel @Inject constructor( } } + fun permanentlyHidePassword() { + //todo we can simplify this once we expose all our sharedPrefs as flows + prefs.setHidePassword(true) + _recoveryHidden.update { true } + } + sealed class AvatarDialogState() { object NoAvatar : AvatarDialogState() data class UserAvatar(val address: Address) : AvatarDialogState() diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt index a6d38c13a0..cc9630ef57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordActivity.kt @@ -1,16 +1,21 @@ package org.thoughtcrime.securesms.recoverypassword +import android.content.Intent import android.os.Bundle import androidx.activity.viewModels import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import network.loki.messenger.R import org.thoughtcrime.securesms.BaseActionBarActivity -import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.ui.setComposeContent + class RecoveryPasswordActivity : BaseActionBarActivity() { + companion object { + const val RESULT_RECOVERY_HIDDEN = "recovery_hidden" + } + private val viewModel: RecoveryPasswordViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -25,7 +30,9 @@ class RecoveryPasswordActivity : BaseActionBarActivity() { mnemonic = mnemonic, seed = seed, confirmHideRecovery = { - viewModel.permanentlyHidePassword() + val returnIntent = Intent() + returnIntent.putExtra(RESULT_RECOVERY_HIDDEN, true) + setResult(RESULT_OK, returnIntent) finish() }, copyMnemonic = viewModel::copyMnemonic diff --git a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt index 0ad207cd23..b159accf23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recoverypassword/RecoveryPasswordViewModel.kt @@ -34,10 +34,6 @@ class RecoveryPasswordViewModel @Inject constructor( .map { MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }.encode(it, MnemonicCodec.Language.Configuration.english) } .stateIn(viewModelScope, SharingStarted.Eagerly, "") - fun permanentlyHidePassword() { - prefs.setHidePassword(true) - } - fun copyMnemonic() { prefs.setHasViewedSeed(true) ClipData.newPlainText("Seed", mnemonic.value) diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 77702dc4eb..dd62a33007 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -304,7 +304,7 @@ android:paddingHorizontal="@dimen/massive_spacing" android:paddingVertical="@dimen/small_spacing" android:textSize="@dimen/text_size" - android:text="@string/block"/> + android:text="@string/deleteAfterGroupPR1BlockUser"/> + android:text="@string/delete" /> diff --git a/app/src/main/res/layout/view_global_search_input.xml b/app/src/main/res/layout/view_global_search_input.xml index 2d5c03ec5f..cd164772bd 100644 --- a/app/src/main/res/layout/view_global_search_input.xml +++ b/app/src/main/res/layout/view_global_search_input.xml @@ -27,7 +27,7 @@ app:tint="?searchIconColor" android:contentDescription="@string/search" /> @dimen/very_large_font_size + + diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index 3a59cff9ab..a12af5f5c1 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -36,7 +36,8 @@ class ConversationViewModelTest: BaseViewModelTest() { private lateinit var messageRecord: MessageRecord private val viewModel: ConversationViewModel by lazy { - ConversationViewModel(threadId, edKeyPair, application, repository, storage, mock(), mock(), mock()) + ConversationViewModel(threadId, edKeyPair, application, repository, storage, + mock(), mock(), mock(), mock(), mock()) } @Before diff --git a/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt b/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt index 4733e67563..adb082f8ee 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/LocalisedTimeUtil.kt @@ -46,7 +46,8 @@ object LocalisedTimeUtil { "${this.inWholeHours}h ${minutesRemaining}m" } else if (this.inWholeMinutes > 0) { val secondsRemaining = this.minus(1.minutes.times(this.inWholeMinutes.toInt())).inWholeSeconds - "${this.inWholeMinutes}m ${secondsRemaining}s" + if(secondsRemaining > 0) "${this.inWholeMinutes}m ${secondsRemaining}s" + else "${this.inWholeMinutes}m" } else { "0m ${this.inWholeSeconds}s" } From 74939da01f0546e1643dab977a07c8cd4bda800d Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 21 Oct 2024 07:46:41 +1100 Subject: [PATCH 2/5] Fix/message deletion issues (#1696) * SES-2810 - Removing the screenshot privacy toggle * SES-2813 - clickable only when there is a 'follow settings' * SES-2815 - proper icon and spacing for deleted messages * Simplified deletion dialog to be reused for note to self and the rest as only the labels change * SES-2819 - Do not show a reaction on a deleted message * Fixing up deletion details Message view hides reactions completely if the message is marked as deleted All messages can now show the 'Delete' long press option Community messages should be removed completely not marked as deleted * Revert "SES-2819 - Do not show a reaction on a deleted message" This reverts commit 711e31a43a889187ec3be189ad4aa78f18c217d7. * Avoiding adding reactions if the message is marked as deleted --- .../securesms/BaseActionBarActivity.java | 6 +- .../v2/ConversationReactionOverlay.kt | 7 +- .../conversation/v2/ConversationV2Dialogs.kt | 95 +++---------------- .../conversation/v2/ConversationViewModel.kt | 39 ++++---- .../v2/menus/ConversationMenuItemHelper.kt | 7 -- .../v2/messages/ControlMessageView.kt | 6 +- .../v2/messages/VisibleMessageView.kt | 2 +- .../securesms/database/Storage.kt | 9 ++ .../main/res/layout/view_deleted_message.xml | 13 +-- app/src/main/res/xml/preferences_privacy.xml | 6 -- .../utilities/TextSecurePreferences.kt | 11 --- 11 files changed, 57 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java index c3321504ea..4e385cfe2b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java @@ -127,11 +127,7 @@ public abstract class BaseActionBarActivity extends AppCompatActivity { if (!isResume) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } else { - if (TextSecurePreferences.isScreenSecurityEnabled(this)) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); - } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); - } + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt index a76dead344..3aa19af994 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.kt @@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.components.emoji.EmojiImageView import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel import org.thoughtcrime.securesms.components.menu.ActionItem import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuItemHelper.userCanBanSelectedUsers -import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuItemHelper.userCanDeleteSelectedItems import org.thoughtcrime.securesms.database.MmsSmsDatabase import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord @@ -559,10 +558,8 @@ class ConversationReactionOverlay : FrameLayout { items += ActionItem(R.attr.menu_copy_icon, R.string.accountIDCopy, { handleActionItemClicked(Action.COPY_ACCOUNT_ID) }) } // Delete message - if (userCanDeleteSelectedItems(context, message, openGroup, userPublicKey, blindedPublicKey)) { - items += ActionItem(R.attr.menu_trash_icon, R.string.delete, { handleActionItemClicked(Action.DELETE) }, - R.string.AccessibilityId_deleteMessage, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger)) - } + items += ActionItem(R.attr.menu_trash_icon, R.string.delete, { handleActionItemClicked(Action.DELETE) }, + R.string.AccessibilityId_deleteMessage, message.subtitle, ThemeUtil.getThemedColor(context, R.attr.danger)) // Ban user if (userCanBanSelectedUsers(context, message, openGroup, userPublicKey, blindedPublicKey) && !isDeleteOnly) { items += ActionItem(R.attr.menu_block_icon, R.string.banUser, { handleActionItemClicked(Action.BAN_USER) }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt index b9756a13be..a958f9d84d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationV2Dialogs.kt @@ -19,6 +19,8 @@ import network.loki.messenger.R import org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.* +import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.DeleteForEveryoneDialogData +import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.DialogButtonModel import org.thoughtcrime.securesms.ui.GetString @@ -48,9 +50,10 @@ fun ConversationV2Dialogs( ) } - // delete message(s) for everyone + // delete message(s) if(dialogsState.deleteEveryone != null){ - var deleteForEveryone by remember { mutableStateOf(dialogsState.deleteEveryone.defaultToEveryone)} + val data = dialogsState.deleteEveryone + var deleteForEveryone by remember { mutableStateOf(data.defaultToEveryone)} AlertDialog( onDismissRequest = { @@ -59,17 +62,17 @@ fun ConversationV2Dialogs( }, title = pluralStringResource( R.plurals.deleteMessage, - dialogsState.deleteEveryone.messages.size, - dialogsState.deleteEveryone.messages.size + data.messages.size, + data.messages.size ), text = pluralStringResource( R.plurals.deleteMessageConfirm, - dialogsState.deleteEveryone.messages.size, - dialogsState.deleteEveryone.messages.size + data.messages.size, + data.messages.size ), content = { // add warning text, if any - dialogsState.deleteEveryone.warning?.let { + data.warning?.let { Text( text = it, textAlign = TextAlign.Center, @@ -103,9 +106,9 @@ fun ConversationV2Dialogs( ), option = RadioOption( value = Unit, - title = GetString(stringResource(R.string.deleteMessageEveryone)), + title = GetString(data.deleteForEveryoneLabel), selected = deleteForEveryone, - enabled = dialogsState.deleteEveryone.everyoneEnabled + enabled = data.everyoneEnabled ) ) { deleteForEveryone = true @@ -119,9 +122,9 @@ fun ConversationV2Dialogs( // delete messages based on chosen option sendCommand( if(deleteForEveryone) MarkAsDeletedForEveryone( - dialogsState.deleteEveryone.copy(defaultToEveryone = deleteForEveryone) + data.copy(defaultToEveryone = deleteForEveryone) ) - else MarkAsDeletedLocally(dialogsState.deleteEveryone.messages) + else MarkAsDeletedLocally(data.messages) ) } ), @@ -132,76 +135,6 @@ fun ConversationV2Dialogs( ) } - // delete message(s) for all my devices - if(dialogsState.deleteAllDevices != null){ - var deleteAllDevices by remember { mutableStateOf(dialogsState.deleteAllDevices.defaultToEveryone) } - - AlertDialog( - onDismissRequest = { - // hide dialog - sendCommand(HideDeleteAllDevicesDialog) - }, - title = pluralStringResource( - R.plurals.deleteMessage, - dialogsState.deleteAllDevices.messages.size, - dialogsState.deleteAllDevices.messages.size - ), - text = pluralStringResource( - R.plurals.deleteMessageConfirm, - dialogsState.deleteAllDevices.messages.size, - dialogsState.deleteAllDevices.messages.size - ), - content = { - TitledRadioButton( - contentPadding = PaddingValues( - horizontal = LocalDimensions.current.xxsSpacing, - vertical = 0.dp - ), - option = RadioOption( - value = Unit, - title = GetString(stringResource(R.string.deleteMessageDeviceOnly)), - selected = !deleteAllDevices - ) - ) { - deleteAllDevices = false - } - - TitledRadioButton( - contentPadding = PaddingValues( - horizontal = LocalDimensions.current.xxsSpacing, - vertical = 0.dp - ), - option = RadioOption( - value = Unit, - title = GetString(stringResource(R.string.deleteMessageDevicesAll)), - selected = deleteAllDevices - ) - ) { - deleteAllDevices = true - } - }, - buttons = listOf( - DialogButtonModel( - text = GetString(stringResource(id = R.string.delete)), - color = LocalColors.current.danger, - onClick = { - // delete messages based on chosen option - sendCommand( - if(deleteAllDevices) MarkAsDeletedForEveryone( - dialogsState.deleteAllDevices.copy(defaultToEveryone = deleteAllDevices) - ) - else MarkAsDeletedLocally(dialogsState.deleteAllDevices.messages) - ) - } - ), - DialogButtonModel( - GetString(stringResource(R.string.cancel)) - ) - ) - ) - } - - // Clear emoji if(dialogsState.clearAllEmoji != null){ AlertDialog( diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 29a9b9d79d..5ecddd2147 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -245,12 +245,12 @@ class ConversationViewModel( // hashes are required if wanting to delete messages from the 'storage server' // They are not required for communities OR if all messages are outgoing - // also we can only delete deleted messages (marked as deleted) locally + // also we can only delete deleted messages and control messages (marked as deleted) locally val canDeleteForEveryone = messages.all{ !it.isDeleted && !it.isControlMessage } && ( messages.all { it.isOutgoing } || conversationType == MessageType.COMMUNITY || - messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null - }) + messages.all { lokiMessageDb.getMessageServerHash(it.id, it.isMms) != null } + ) // There are three types of dialogs for deletion: // 1- Delete on device only OR all devices - Used for Note to self @@ -260,11 +260,16 @@ class ConversationViewModel( // the conversation is a note to self conversationType == MessageType.NOTE_TO_SELF -> { _dialogsState.update { - it.copy(deleteAllDevices = DeleteForEveryoneDialogData( + it.copy(deleteEveryone = DeleteForEveryoneDialogData( messages = messages, defaultToEveryone = false, - everyoneEnabled = true, - messageType = conversationType + everyoneEnabled = canDeleteForEveryone, + messageType = conversationType, + deleteForEveryoneLabel = application.getString(R.string.deleteMessageDevicesAll), + warning = if(canDeleteForEveryone) null else + application.resources.getQuantityString( + R.plurals.deleteMessageNoteToSelfWarning, messages.count(), messages.count() + ) ) ) } @@ -278,6 +283,7 @@ class ConversationViewModel( messages = messages, defaultToEveryone = isAdmin.value, everyoneEnabled = true, + deleteForEveryoneLabel = application.getString(R.string.deleteMessageEveryone), messageType = conversationType ) ) @@ -293,6 +299,7 @@ class ConversationViewModel( defaultToEveryone = false, everyoneEnabled = false, // disable 'delete for everyone' - can only delete locally in this case messageType = conversationType, + deleteForEveryoneLabel = application.getString(R.string.deleteMessageEveryone), warning = application.resources.getQuantityString( R.plurals.deleteMessageWarning, messages.count(), messages.count() ) @@ -548,7 +555,7 @@ class ConversationViewModel( ).show() } - _dialogsState.update { it.copy(deleteAllDevices = data) } + _dialogsState.update { it.copy(deleteEveryone = data) } } // hide loading indicator @@ -565,11 +572,8 @@ class ConversationViewModel( try { repository.deleteCommunityMessagesRemotely(threadId, data.messages) - // When this is done we simply need to remove the message locally - repository.markAsDeletedLocally( - messages = data.messages, - displayedMessage = application.getString(R.string.deleteMessageDeletedGlobally) - ) + // When this is done we simply need to remove the message locally (leave nothing behind) + repository.deleteMessages(messages = data.messages, threadId = threadId) // show confirmation toast withContext(Dispatchers.Main) { @@ -731,12 +735,6 @@ class ConversationViewModel( } } - is Commands.HideDeleteAllDevicesDialog -> { - _dialogsState.update { - it.copy(deleteAllDevices = null) - } - } - is Commands.MarkAsDeletedLocally -> { // hide dialog first _dialogsState.update { @@ -818,8 +816,7 @@ class ConversationViewModel( data class DialogsState( val openLinkDialogUrl: String? = null, val clearAllEmoji: ClearAllEmoji? = null, - val deleteEveryone: DeleteForEveryoneDialogData? = null, - val deleteAllDevices: DeleteForEveryoneDialogData? = null, + val deleteEveryone: DeleteForEveryoneDialogData? = null ) data class DeleteForEveryoneDialogData( @@ -827,6 +824,7 @@ class ConversationViewModel( val messageType: MessageType, val defaultToEveryone: Boolean, val everyoneEnabled: Boolean, + val deleteForEveryoneLabel: String, val warning: String? = null ) @@ -841,7 +839,6 @@ class ConversationViewModel( data class ClearEmoji(val emoji:String, val messageId: MessageId) : Commands() data object HideDeleteEveryoneDialog : Commands() - data object HideDeleteAllDevicesDialog : Commands() data object HideClearEmoji : Commands() data class MarkAsDeletedLocally(val messages: Set): Commands() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuItemHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuItemHelper.kt index ffc36fddf6..3356453596 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuItemHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuItemHelper.kt @@ -7,13 +7,6 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager object ConversationMenuItemHelper { - @JvmStatic - fun userCanDeleteSelectedItems(context: Context, message: MessageRecord, openGroup: OpenGroup?, userPublicKey: String, blindedPublicKey: String?): Boolean { - if (openGroup == null) return message.isOutgoing || !message.isOutgoing - if (message.isOutgoing) return true - return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey) - } - @JvmStatic fun userCanBanSelectedUsers(context: Context, message: MessageRecord, openGroup: OpenGroup?, userPublicKey: String, blindedPublicKey: String?): Boolean { if (openGroup == null) return false diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index 89591777be..08a06407c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -87,7 +87,11 @@ class ControlMessageView : LinearLayout { && message.expiryMode != (MessagingModuleConfiguration.shared.storage.getExpirationConfiguration(message.threadId)?.expiryMode ?: ExpiryMode.NONE) && threadRecipient?.isGroupRecipient != true - binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } + if (followSetting.isVisible) { + binding.controlContentView.setOnClickListener { disappearingMessages.showFollowSettingDialog(context, message) } + } else { + binding.controlContentView.setOnClickListener(null) + } } } message.isMediaSavedNotification -> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index f4ace02e1a..98e864e472 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -234,7 +234,7 @@ class VisibleMessageView : FrameLayout { showStatusMessage(message) // Emoji Reactions - if (message.reactions.isNotEmpty()) { + if (!message.isDeleted && message.reactions.isNotEmpty()) { val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { emojiReactionsBinding.value.root.let { root -> 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 a1903cc891..d8460159fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1686,12 +1686,21 @@ open class Storage( val timestamp = reaction.timestamp val localId = reaction.localId val isMms = reaction.isMms + val messageId = if (localId != null && localId > 0 && isMms != null) { + // bail early is the message is marked as deleted + val messagingDatabase: MessagingDatabase = if (isMms == true) DatabaseComponent.get(context).mmsDatabase() + else DatabaseComponent.get(context).smsDatabase() + if(messagingDatabase.getMessageRecord(localId)?.isDeleted == true) return + MessageId(localId, isMms) } else if (timestamp != null && timestamp > 0) { val messageRecord = DatabaseComponent.get(context).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: return + if (messageRecord.isDeleted) return + MessageId(messageRecord.id, messageRecord.isMms) } else return + DatabaseComponent.get(context).reactionDatabase().addReaction( messageId, ReactionRecord( diff --git a/app/src/main/res/layout/view_deleted_message.xml b/app/src/main/res/layout/view_deleted_message.xml index 719783a0d9..f52f7b7a1b 100644 --- a/app/src/main/res/layout/view_deleted_message.xml +++ b/app/src/main/res/layout/view_deleted_message.xml @@ -6,14 +6,15 @@ android:layout_height="wrap_content" xmlns:tools="http://schemas.android.com/tools" android:orientation="horizontal" - android:padding="@dimen/small_spacing"> + android:gravity="center_vertical" + android:paddingVertical="@dimen/small_spacing"> - - Date: Mon, 21 Oct 2024 09:45:16 +1100 Subject: [PATCH 3/5] Fix/conversation deletion issues (#1695) * SES-2464 - properly heading to home root when deleting a thread * SES-2810 - Catering for missing config contacts vs local contacts Making sure we deleted conversations when a contact is missing from the config while existing locally --- .../conversation/v2/ConversationActivityV2.kt | 11 ++++++----- .../org/thoughtcrime/securesms/database/Storage.kt | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) 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 0132a876cf..9cdb76cf25 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 @@ -57,7 +57,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch @@ -109,14 +108,14 @@ import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey import org.thoughtcrime.securesms.conversation.ConversationActionBarDelegate import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessagesActivity -import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.* import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener +import org.thoughtcrime.securesms.conversation.v2.ConversationViewModel.Commands.* import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP +import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_COPY import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE 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_COPY 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 @@ -157,6 +156,8 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.giph.ui.GiphyActivity import org.thoughtcrime.securesms.groups.OpenGroupManager +import org.thoughtcrime.securesms.home.HomeActivity +import org.thoughtcrime.securesms.home.startHomeActivity import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel @@ -850,8 +851,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.messageRequestBar.visibility = View.GONE } if (!uiState.conversationExists && !isFinishing) { - // Conversation should be deleted now, just go back - finish() + // Conversation should be deleted now, go to homepage with a cleared stack + baseContext.startHomeActivity(isFromOnboarding = false, isNewAccount = false) } // show or hide the text input 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 d8460159fe..4fd9f97ede 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1265,6 +1265,16 @@ open class Storage( } setRecipientHash(recipient, contact.hashCode().toString()) } + + // if we have contacts locally but that are missing from the config, remove their corresponding thread + val removedContacts = getAllContacts().filter { localContact -> + moreContacts.firstOrNull { + it.id == localContact.accountID + } == null + } + removedContacts.forEach { + getThreadId(fromSerialized(it.accountID))?.let(::deleteConversation) + } } override fun addContacts(contacts: List) { From 4b01fcec5e5564a4ced132a498e0838ca8041011 Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Mon, 21 Oct 2024 15:45:02 +1100 Subject: [PATCH 4/5] Fix/message deletion issues (#1697) * SES-2810 - Removing the screenshot privacy toggle * SES-2813 - clickable only when there is a 'follow settings' * SES-2815 - proper icon and spacing for deleted messages * Simplified deletion dialog to be reused for note to self and the rest as only the labels change * SES-2819 - Do not show a reaction on a deleted message * Fixing up deletion details Message view hides reactions completely if the message is marked as deleted All messages can now show the 'Delete' long press option Community messages should be removed completely not marked as deleted * Revert "SES-2819 - Do not show a reaction on a deleted message" This reverts commit 711e31a43a889187ec3be189ad4aa78f18c217d7. * Avoiding adding reactions if the message is marked as deleted * Removing uneeded icon * Deletion handled by VM so menu item is always visible * SES-2811 - Do not attempt to send a failed message marked as deleted * SES-2818 - Making sure we set the lastMessage in a thread properly, without using 'marked as deleted' messages * SES-2464 - changed the behaviour to finish the convo activity but instead refresh the sarch on resume * removing log --- .../attachments/DatabaseAttachmentProvider.kt | 6 +++++ .../conversation/v2/ConversationActivityV2.kt | 4 +-- .../menus/ConversationActionModeCallback.kt | 14 +---------- .../securesms/database/MmsDatabase.kt | 17 +++++++++++++ .../securesms/database/MmsSmsDatabase.java | 15 +++++++++++- .../securesms/database/SmsDatabase.java | 23 ++++++++++++++++++ .../securesms/home/HomeActivity.kt | 5 ++++ .../res/drawable-hdpi/ic_trash_filled_32.png | Bin 1492 -> 0 bytes .../res/drawable-mdpi/ic_trash_filled_32.png | Bin 835 -> 0 bytes .../res/drawable-xhdpi/ic_trash_filled_32.png | Bin 1782 -> 0 bytes .../drawable-xxhdpi/ic_trash_filled_32.png | Bin 3146 -> 0 bytes .../drawable-xxxhdpi/ic_trash_filled_32.png | Bin 4373 -> 0 bytes app/src/main/res/layout/image_editor_hud.xml | 3 ++- .../database/MessageDataProvider.kt | 1 + .../messaging/jobs/MessageSendJob.kt | 12 ++++++++- .../sending_receiving/MessageSender.kt | 8 +++++- 16 files changed, 89 insertions(+), 19 deletions(-) delete mode 100644 app/src/main/res/drawable-hdpi/ic_trash_filled_32.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_trash_filled_32.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_trash_filled_32.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_trash_filled_32.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_trash_filled_32.png diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index d74174fecb..06e344a239 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -145,6 +145,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp) } + override fun isDeletedMessage(timestamp: Long): Boolean { + val smsDatabase = DatabaseComponent.get(context).smsDatabase() + val mmsDatabase = DatabaseComponent.get(context).mmsDatabase() + return smsDatabase.isDeletedMessage(timestamp) || mmsDatabase.isDeletedMessage(timestamp) + } + override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { val database = DatabaseComponent.get(context).attachmentDatabase() val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return 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 9cdb76cf25..485fcaca5f 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 @@ -851,8 +851,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding.messageRequestBar.visibility = View.GONE } if (!uiState.conversationExists && !isFinishing) { - // Conversation should be deleted now, go to homepage with a cleared stack - baseContext.startHomeActivity(isFromOnboarding = false, isNewAccount = false) + // Conversation should be deleted now + finish() } // show or hide the text input diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index abadc06335..ea265bdbbf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -42,18 +42,6 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p val blindedPublicKey = openGroup?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, edKeyPair)?.publicKey?.asBytes } ?.let { AccountId(IdPrefix.BLINDED, it) }?.hexString - // Embedded function - fun userCanDeleteSelectedItems(): Boolean { - // admin can delete all combinations - if(adapter.isAdmin) return true - - val allSentByCurrentUser = selectedItems.all { it.isOutgoing } - val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing } - if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser } - if (allSentByCurrentUser) { return true } - return OpenGroupManager.isUserModerator(context, openGroup.groupId, userPublicKey, blindedPublicKey) - } - // Embedded function fun userCanBanSelectedUsers(): Boolean { if (openGroup == null) { return false } @@ -67,7 +55,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p // Delete message - menu.findItem(R.id.menu_context_delete_message).isVisible = userCanDeleteSelectedItems() + menu.findItem(R.id.menu_context_delete_message).isVisible = true // can always delete since delete logic will be handled by the VM // Ban user menu.findItem(R.id.menu_context_ban_user).isVisible = userCanBanSelectedUsers() // Ban and delete all 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 63460744f6..60c4de6883 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -106,6 +106,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } + fun isDeletedMessage(timestamp: Long): Boolean = + databaseHelper.writableDatabase.query( + TABLE_NAME, + arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), + DATE_SENT + " = ?", + arrayOf(timestamp.toString()), + null, + null, + null, + null + ).use { cursor -> + cursor.asSequence() + .map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) } + .map(cursor::getLong) + .any { MmsSmsColumns.Types.isDeletedMessage(it) } + } + fun incrementReceiptCount( messageId: SyncMessageId, timestamp: Long, 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 b737be855e..510272cfb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -17,6 +17,9 @@ package org.thoughtcrime.securesms.database; import static org.thoughtcrime.securesms.database.MmsDatabase.MESSAGE_BOX; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_DELETED_INCOMING_TYPE; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_DELETED_OUTGOING_TYPE; +import static org.thoughtcrime.securesms.database.MmsSmsColumns.Types.BASE_TYPE_MASK; import android.content.Context; import android.database.Cursor; @@ -96,6 +99,14 @@ public class MmsSmsDatabase extends Database { } } + public @Nullable MessageRecord getNonDeletedMessageForTimestamp(long timestamp) { + String selection = MmsSmsColumns.NORMALIZED_DATE_SENT + " = " + timestamp; + try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) { + MmsSmsDatabase.Reader reader = readerFor(cursor); + return reader.getNext(); + } + } + public @Nullable MessageRecord getMessageFor(long timestamp, String serializedAuthor) { return getMessageFor(timestamp, serializedAuthor, true); } @@ -323,7 +334,9 @@ public class MmsSmsDatabase extends Database { public long getLastMessageTimestamp(long threadId) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; - String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; + // make sure the last message isn't marked as deleted + String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + + "(ifnull("+SmsDatabase.TYPE+", "+MmsDatabase.MESSAGE_BOX+") & "+BASE_TYPE_MASK+") NOT IN ("+BASE_DELETED_OUTGOING_TYPE+", "+BASE_DELETED_INCOMING_TYPE+")"; // this ugly line checks whether the type is deleted (incoming or outgoing) for either the sms table or the mms table try (Cursor cursor = queryTables(PROJECTION, selection, order, "1")) { if (cursor.moveToFirst()) { 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 5088b76d29..ed7945e8a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -243,6 +243,7 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(READ, 1); contentValues.put(BODY, displayedMessage); contentValues.put(HAS_MENTION, 0); + contentValues.put(STATUS, Status.STATUS_NONE); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, @@ -299,6 +300,28 @@ public class SmsDatabase extends MessagingDatabase { return isOutgoing; } + public boolean isDeletedMessage(long timestamp) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + Cursor cursor = null; + boolean isDeleted = false; + + try { + cursor = database.query(TABLE_NAME, new String[] { ID, THREAD_ID, ADDRESS, TYPE }, + DATE_SENT + " = ?", new String[] { String.valueOf(timestamp) }, + null, null, null, null); + + while (cursor.moveToNext()) { + if (Types.isDeletedMessage(cursor.getLong(cursor.getColumnIndexOrThrow(TYPE)))) { + isDeleted = true; + } + } + } finally { + if (cursor != null) cursor.close(); + } + + return isDeleted; + } + public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); Cursor cursor = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 5a19263cac..cfbd03bb57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -372,6 +372,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), binding.seedReminderView.isVisible = false } + // refresh search on resume, in case we a conversation was deleted + if (binding.globalSearchRecycler.isVisible){ + globalSearchViewModel.refresh() + } + updateLegacyConfigView() // Sync config changes if there are any diff --git a/app/src/main/res/drawable-hdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-hdpi/ic_trash_filled_32.png deleted file mode 100644 index 586af53d086740cd4ead752188a6570a3db0c1b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1492 zcmV;_1uOcAP)Px)j!8s8RA>e5SxZbLRTS<2&qgOW&J4%`re`EEf!W{!a6w1KS@GGrFfk!)W@n6H z!`cO50fxj+T$t#>G*J>*8i5c#MrjwcnFI_18`^1Ln4ad-Up%L#UUg4()pS>PK}_tM z9O~7p`|i8HcdP4SvDB#MfvP-Ut!j0CG35x@lmQv=_KJGBWb+;o;#Qxm+%Np+kIjaBvVA9Uc7uYwiH`0F4!ChoN~Syi4I!R1HSY z16*%uX}On3B=&UlE}A|+KR;@3Z+{1CZv(FYG(s(3+;e|*G%@vz_&|$<@)SZvc~uMH zJyYk}%F4>eot>S-7+-t|ibkXVxLmH3&1Pe2NR-KB95+BiLqiX&R;z7wbv4x0)isWX zkASBDQ%R6G6HK{6DDniOo&i+Y3DCpLZC+`J;OCW|@eB+M+zEw3-#Q!)&-(iMT1Q96 zSD23jX8<=Rk)QH>^Yil`_4M?-pUq~|So>gZZtfxG$uG_Vh7xft4?5-WtZ-atByRHx zNjC+5sH}HkVc~vbV`K32^wgb9CY{J(8}h+QU~GjF_CO%e-qh68%C$>NOTWhB@k6)U zowD2QS$JA_(!#arblQPAe{XMZ5bV~yy}iG-wzf8$PG=epQ1}@+-g`Wr6r%gn@bK_& z;OC)d0Z?ROtV{IPwcXv_iJ}C);t4qMQ|NLFV6hMxfZ;Qur}ZeugL!;>9ESH@KA*3l zuCDG?J!UiKQO%;MRO*P4#QrhJW;&JSd7%f-04=@_+y&l7?uM?)`ID2AUt`bvz&+q~ z;AMbWQABzXptU?OeBnq3klrX-eMuA8i@AOb909CQ=YS{(80JaO0P$v|A*=#tIB5I{ za3v(~CGCq1sXX`AOd&l3s3;c@v|OnW7#Gpn+FCdki#=*!m zqhK)D<@fuU2A1vZ?Y|?D$Yw)BL;YJI&Z$YRUUVY&=C!E)i#4p}RnzqJ^kQFM-}jrF zn-L)lWivB4IQRqSPfkuAi}{D3{{H^&W@cu77IW2HUlf6|9jWUq$F3ur=sq_f2RaE6T=z)Dsf( z>VDE52<;CHlQJjO*->JZa< zdLpyDyu4nt`VuDK{q)q-)b9|@HV!}PtBL?sqz9M^tw@pBWrtvAXNSsn05<_fUtJh5 zm8Vh>ASXRQZzRwX|AG^jML1{4v9YnwA)iexo;gOKYLYs0kisYg!r|~Yh(gxvNFch8 zk^7%uk2itW0JikYaWL3Q69qAx{Fl(}@$vBwH#RnYMDsVwoUOzldK=a2$^QQSpZFia zWOsM>dyw4<+yGe3n7S`aA3QJ3Kqe3c(Fm4Z@lTJJFy=<$-$O>d#p1veVe5tI!agi} zMI19_MH$6VpGsO-u#~(6JwvctOoKS4@GlUo?VspN8-%DoPx%{YgYYR9Fe^Ry&VUQ4n6Z2*^uBvs!?$u%Ix+(uTqqBqqef%HCr65mts65)(Ru zrIn=(W)motP7)zRY)A+Tk|3@xSU}!>-(Aklh2_dZD2y`6ch5O9b7tnwdCktw|4zCU z6QGB_8tVBI$VOAJPb?+1fn~~cYy|96n9z#(!g(9KCeQ&G%*Ke0==^dnmwPrG4m*=M z0&%dqJYWZrl`k`Dde}w+T z>-FAoI-LipRO$fZAwc`IWdKA#GfN{<2)RsDIt!cwm}*X@j$}5Q9dP`@Zny9He7;A; zVzCB(7a&P)w|loxC_L@;dTo!#vxoKz_yd3r84Q=@JHTa9mS;WyMtUCEZJ^yK4F-cl zqtSQ+MroUY$wY^0k~b6n?s>glclrJP2WsIueK_u=)9HO=?i1!Kf39VVOTgWI3Aj18 z&(<2YTCJb36#%XP7C`I;bms;bj{&BKM~M?RMYKiy0_a1bnP{e5DhMcw>?9J2luoC6 zD43B*BpwcjAB!<$d&y+-wHUJnkIPDXO#jRYz<4$tWc-OtCQ}dcL!o1}T8|tK#}6?^ z`_*Exh?AjWYj+oAKvP?iB>oAHluQPLVFaFfV-59sJ#|h^D+O?Bt1BpO1K4I@n}NSN z11GM{(P+egl@}CMkOZE(s4W5b9VtGCX0)gAGPx*ut`KgRCodHnoUexRTRhhwn(vJMH?D4w3UUiE`+ojj4KySG+h`MuH2c_7~`T6 zHZ^HNVu(A43;bvkH!fU}5HxWC3lbwXU1>;*N|jm~S}NZk12g#l4Re~u`yeTvB zo#b%uyZ4@R&-tHo?|sa>6%{eX2*e2dpAo1mvh${&(y#6}{f-)8$X(xUjqqYOGQ%mAFl4~&K1J}?&xi=rrkyi~Yo)L*!_*Copn1&kEe%MB zDPh_HB2o~FkpUKY9DPh&FMd43hg32r;P`=$b2y2^8{&2GYNiAXbKXidl^)?7kv=Sb zR{X-m#KdPyOH04+?Chk(xhS=`xHv7DFKcUSJ0yQf{EWCkj9&DYED=>APt=>mr>-Zj4Arr}(7!s2IKI_sbqN7l)Y4)-#in!bHN4S?i=b#W!WbI^7HBGiJ&~6*1mF}?_kzJ%7KDa7o6lj9DZDu$J6KUFvd8WNl+;sKo)i=7j zy53PeO_wiU{(5wDbXE^I&xVE*P6N|GC@ZSx=H_P3pFjUvWo2bmLqo&S*4EaAp`oGM z8gBsG=4x}BZEUjG>Ru)Wj=J`eUleTweNN}iaV8_mq?*Z7!WdYms2{m`^XBJ`jg9OB z6~Bs3pFaJSsziTH%nt9)0OZk$6Ng7eM&3Sm?%eCDS14;9w70ikynFZVydII~l)x!t zRnuUi6L1;hoQv_kSzr#R-$OX_&>%(TAJ2#qZCF`ZnH2bM$~;>#fI_x;2q$w9Adv}W zLkgDJjNMs%%-Wgpm^AZ6uT&Ay-eES|9usIPDIP>9VcoEs&F>jH!_VLA+vH^eqf&jp#W%~tL!%kZ=l!-VS-;#JCQ*vCg z_6g#?06+*PjjXO}dSm3sD;cV)s=`SeUd}Lef@Vu#UjRrxjUJIV1t4nYIQ=C-nG7J^ zvCZsr9TJ!+|M2+byB_a#Vww#eS!}B=>Y;5RKk@-k`LHxd3czk^PXSIJI ze(%baE8n{N)gCD89$UzBJ{eGmetR3U4fLU|nwlC?loypmeSJMUwwnTF-2)4GE;a!6 zgN7&5^iL0IO8-V(T^)Oin*wFs0}FXBHULDK&B+5mX6L3ap=NYyjAp zvYo@}%}VRtP8p{!xn!MV_g(iyX5X61R4@jhOam90*>MI$24$HliZh^00~eXuaRx*N zWtl39GoVZZ7n#{{21EvBnJS7ipiBc7nb~m$LwU?J<^qi0{37VOi`As&xDn20wRA125?jg_G z^o-csK=eU*YhhvGkG)+7_oPcze`Ax_GQqMnr%_=5Fz1;hF*P;yQ%>!VXu!2=*M8Ad zcJYdD4cM9&g#iGCC}x7q;o;#il?P61Yikd>ySv9^2%pmIGF09Ggmr+On|=X zx4u8%O&~QeFmOq>aFeuE{DheGA2zTO$&XOX1YCw}>Fn%0ql?Xd9ux>;V`JaQjB9D?LIb<`FKq1dwEN!6e0=;$~*Jw5%yqXc1fb@l$>;2@DdBR(bOWa1)cQKK*Y zX(9_a!#TUTyiDr7*w@$h?&Rd;kWPocbjEM!J%LNo$wGlamuNw^MQ5e|$Nv8Qk9D7* zOLM*~ZWH4??+m<5VOyx^hZ-#95Hhdc?esI=WK_*f`vCy01_C8Z0*9SUTa;_jK9w5?>7!V4P|{{^fT5h1T)1>Wz(ocG zkv2BRd4~22GO|vG%t>?DKnWdn6dM&{oavx6Agpd>RQjDx8Ys>-=8GvtAVwfYAT$F1 Y0fFwsuOsDrQ~&?~07*qoM6N<$f;+cT3;+NC diff --git a/app/src/main/res/drawable-xxhdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-xxhdpi/ic_trash_filled_32.png deleted file mode 100644 index 6a4ccab1b61c95ba2cd9e20ac4bf9b16d841902a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3146 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 O08KSr#5WbI7ykpOp+9T@ diff --git a/app/src/main/res/layout/image_editor_hud.xml b/app/src/main/res/layout/image_editor_hud.xml index d78afa6686..6a9538d603 100644 --- a/app/src/main/res/layout/image_editor_hud.xml +++ b/app/src/main/res/layout/image_editor_hud.xml @@ -31,7 +31,8 @@ android:layout_height="wrap_content" android:background="?attr/selectableItemBackgroundBorderless" android:padding="8dp" - android:src="@drawable/ic_trash_filled_32" /> + app:tint="@color/white" + android:src="@drawable/ic_delete" /> ? diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt index 52d56184cc..2010a58e61 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt @@ -37,6 +37,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { val message = message as? VisibleMessage val storage = MessagingModuleConfiguration.shared.storage + // do not attempt to send if the message is marked as deleted + message?.sentTimestamp?.let{ + if(messageDataProvider.isDeletedMessage(it)){ + return@execute + } + } + val sentTimestamp = this.message.sentTimestamp val sender = storage.getUserPublicKey() if (sentTimestamp != null && sender != null) { @@ -107,7 +114,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { Log.w(TAG, "Failed to send $message::class.simpleName.") val message = message as? VisibleMessage if (message != null) { - if (!MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) { + if ( + MessagingModuleConfiguration.shared.messageDataProvider.isDeletedMessage(message.sentTimestamp!!) || + !MessagingModuleConfiguration.shared.messageDataProvider.isOutgoingMessage(message.sentTimestamp!!) + ) { return // The message has been deleted } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 301648f97b..73073532b0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -469,9 +469,15 @@ object MessageSender { fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) { val storage = MessagingModuleConfiguration.shared.storage + val timestamp = message.sentTimestamp!! + + // no need to handle if message is marked as deleted + if(MessagingModuleConfiguration.shared.messageDataProvider.isDeletedMessage(message.sentTimestamp!!)){ + return + } + val userPublicKey = storage.getUserPublicKey()!! - val timestamp = message.sentTimestamp!! val author = message.sender ?: userPublicKey if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error) From 4917548faf194a444d588b1d63051e369b3b4bec Mon Sep 17 00:00:00 2001 From: ThomasSession Date: Tue, 22 Oct 2024 15:38:46 +1100 Subject: [PATCH 5/5] Fix/empty conversations (#1698) * Not deleting a thread when it's empty - showing an empty snippet when thread is empty or only made up of 'marked as deleted' messages * SES-2814 - show empty state when conversations are empty --- .../conversation/v2/ConversationActivityV2.kt | 6 ++-- .../securesms/database/MmsDatabase.kt | 14 ++++---- .../securesms/database/SmsDatabase.java | 16 ++++----- .../securesms/database/Storage.kt | 2 +- .../securesms/database/ThreadDatabase.java | 36 ++++++++----------- .../database/model/ThreadRecord.java | 6 +++- .../res/layout/activity_conversation_v2.xml | 6 ++-- 7 files changed, 42 insertions(+), 44 deletions(-) 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 485fcaca5f..4c027b8d6c 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 @@ -1123,8 +1123,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val openGroup = viewModel.openGroup // Get the correct placeholder text for this type of empty conversation - val isNoteToSelf = recipient.isLocalNumber val txtCS: CharSequence = when { + // note to self recipient.isLocalNumber -> getString(R.string.noteToSelfEmpty) // If this is a community which we cannot write to @@ -1141,8 +1141,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe .format() } - recipient.isGroupRecipient -> { - // If this is a group or community that we CAN send messages to + // 10n1 and groups + recipient.is1on1 || recipient.isGroupRecipient -> { Phrase.from(applicationContext, R.string.groupNoMessages) .put(GROUP_NAME_KEY, recipient.toShortString()) .format() 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 60c4de6883..244f5db117 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -175,7 +175,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) get(context).groupReceiptDatabase() .update(ourAddress, id, status, timestamp) - get(context).threadDatabase().update(threadId, false, true) + get(context).threadDatabase().update(threadId, false) notifyConversationListeners(threadId) } } @@ -274,7 +274,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa " WHERE " + ID + " = ?", arrayOf(id.toString() + "") ) if (threadId.isPresent) { - get(context).threadDatabase().update(threadId.get(), false, true) + get(context).threadDatabase().update(threadId.get(), false) } } @@ -646,7 +646,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) { if (runThreadUpdate) { - get(context).threadDatabase().update(threadId, true, true) + get(context).threadDatabase().update(threadId, true) } } notifyConversationListeners(threadId) @@ -791,7 +791,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } setHasSent(threadId, true) if (runThreadUpdate) { - update(threadId, true, true) + update(threadId, true) } } return messageId @@ -930,7 +930,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa groupReceiptDatabase.deleteRowsForMessage(messageId) val database = databaseHelper.writableDatabase database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString())) - val threadDeleted = get(context).threadDatabase().update(threadId, false, true) + val threadDeleted = get(context).threadDatabase().update(threadId, false) notifyConversationListeners(threadId) notifyStickerListeners() notifyStickerPackListeners() @@ -948,7 +948,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa argValues ) - val threadDeleted = get(context).threadDatabase().update(threadId, false, true) + val threadDeleted = get(context).threadDatabase().update(threadId, false) notifyConversationListeners(threadId) notifyStickerListeners() notifyStickerPackListeners() @@ -1145,7 +1145,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } val threadDb = get(context).threadDatabase() for (threadId in threadIds) { - val threadDeleted = threadDb.update(threadId, false, true) + val threadDeleted = threadDb.update(threadId, false) notifyConversationListeners(threadId) } notifyStickerListeners() 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 ed7945e8a3..6deca0c939 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -158,7 +158,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(id); - DatabaseComponent.get(context).threadDatabase().update(threadId, false, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, false); notifyConversationListeners(threadId); } @@ -261,7 +261,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(id); - DatabaseComponent.get(context).threadDatabase().update(threadId, false, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, false); notifyConversationListeners(threadId); } @@ -346,7 +346,7 @@ public class SmsDatabase extends MessagingDatabase { ID + " = ?", new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))}); - DatabaseComponent.get(context).threadDatabase().update(threadId, false, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, false); notifyConversationListeners(threadId); foundMessage = true; } @@ -429,7 +429,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(messageId); - DatabaseComponent.get(context).threadDatabase().update(threadId, true, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, true); notifyConversationListeners(threadId); notifyConversationListListeners(); @@ -504,7 +504,7 @@ public class SmsDatabase extends MessagingDatabase { long messageId = db.insert(TABLE_NAME, null, values); if (runThreadUpdate) { - DatabaseComponent.get(context).threadDatabase().update(threadId, true, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, true); } if (message.getSubscriptionId() != -1) { @@ -596,7 +596,7 @@ public class SmsDatabase extends MessagingDatabase { } if (runThreadUpdate) { - DatabaseComponent.get(context).threadDatabase().update(threadId, true, true); + DatabaseComponent.get(context).threadDatabase().update(threadId, true); } long lastSeen = DatabaseComponent.get(context).threadDatabase().getLastSeenAndHasSent(threadId).first(); if (lastSeen < message.getSentTimestampMillis()) { @@ -656,7 +656,7 @@ public class SmsDatabase extends MessagingDatabase { long threadId = getThreadIdForMessage(messageId); db.delete(TABLE_NAME, ID_WHERE, new String[] {messageId+""}); notifyConversationListeners(threadId); - boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, false); + boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); return threadDeleted; } @@ -676,7 +676,7 @@ public class SmsDatabase extends MessagingDatabase { ID + " IN (" + StringUtils.join(argsArray, ',') + ")", argValues ); - boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false, true); + boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); notifyConversationListeners(threadId); return threadDeleted; } 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 4fd9f97ede..fc27d8ebd8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -287,7 +287,7 @@ open class Storage( override fun updateThread(threadId: Long, unarchive: Boolean) { val threadDb = DatabaseComponent.get(context).threadDatabase() - threadDb.update(threadId, unarchive, false) + threadDb.update(threadId, unarchive) } override fun persist(message: VisibleMessage, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index f48686aded..5cc8bd06f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -188,6 +188,16 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } + public void clearSnippet(long threadId){ + ContentValues contentValues = new ContentValues(1); + + contentValues.put(SNIPPET, ""); + + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.update(TABLE_NAME, contentValues, ID + " = ?", new String[] {threadId + ""}); + notifyConversationListListeners(); + } + public void updateSnippet(long threadId, String snippet, @Nullable Uri attachment, long date, long type, boolean unarchive) { ContentValues contentValues = new ContentValues(4); @@ -281,7 +291,7 @@ public class ThreadDatabase extends Database { DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, lastTweetDate); - update(threadId, false, true); + update(threadId, false); notifyConversationListeners(threadId); } } finally { @@ -294,7 +304,7 @@ public class ThreadDatabase extends Database { Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp); DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); - update(threadId, false, true); + update(threadId, false); notifyConversationListeners(threadId); } @@ -722,18 +732,10 @@ public class ThreadDatabase extends Database { notifyConversationListListeners(); } - public boolean update(long threadId, boolean unarchive, boolean shouldDeleteOnEmpty) { + public boolean update(long threadId, boolean unarchive) { MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); long count = mmsSmsDatabase.getConversationCount(threadId); - boolean shouldDeleteEmptyThread = shouldDeleteOnEmpty && possibleToDeleteThreadOnEmpty(threadId); - - if (count == 0 && shouldDeleteEmptyThread) { - deleteThread(threadId); - notifyConversationListListeners(); - return true; - } - try (MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId))) { MessageRecord record = null; if (reader != null) { @@ -748,11 +750,8 @@ public class ThreadDatabase extends Database { record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); return false; } else { - if (shouldDeleteEmptyThread) { - deleteThread(threadId); - return true; - } - // todo: add empty snippet that clears existing data + // for empty threads or if there is only deleted messages, show an empty snippet + clearSnippet(threadId); return false; } } finally { @@ -800,11 +799,6 @@ public class ThreadDatabase extends Database { return setLastSeen(threadId, lastSeenTime); } - private boolean possibleToDeleteThreadOnEmpty(long threadId) { - Recipient threadRecipient = getRecipientForThreadId(threadId); - return threadRecipient != null && !threadRecipient.isCommunityRecipient(); - } - private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) { if (messageRecord.isMms()) { MmsMessageRecord record = (MmsMessageRecord) messageRecord; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index c022598d2c..019eea64ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -102,7 +102,11 @@ public class ThreadRecord extends DisplayRecord { @Override public CharSequence getDisplayBody(@NonNull Context context) { - if (isGroupUpdateMessage()) { + // no need to display anything if there are no messages + if(lastMessage == null){ + return ""; + } + else if (isGroupUpdateMessage()) { return context.getString(R.string.groupUpdated); } else if (isOpenGroupInvitation()) { return context.getString(R.string.communityInvitation); diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index dd62a33007..cdc325160d 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -271,14 +271,14 @@