diff --git a/app/build.gradle b/app/build.gradle index 796595afaf..d1431fed36 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -273,9 +273,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/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/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 3475c2daee..f71d14262b 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 @@ -119,14 +119,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 @@ -168,6 +168,8 @@ import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.giph.ui.GiphyActivity import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.home.search.getSearchName +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 @@ -913,6 +915,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // show or hide loading indicator binding.loader.isVisible = state.showLoader + + if (state.isMessageRequestAccepted == true) { + binding.messageRequestBar.visibility = View.GONE + } } } } @@ -1169,8 +1175,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 @@ -1187,8 +1193,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() @@ -1591,14 +1597,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/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 26775f01c2..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 @@ -14,12 +14,13 @@ 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.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 @@ -49,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 = { @@ -60,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, @@ -104,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 @@ -120,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) ) } ), @@ -133,65 +135,24 @@ fun ConversationV2Dialogs( ) } - // delete message(s) for all my devices - if(dialogsState.deleteAllDevices != null){ - var deleteAllDevices by remember { mutableStateOf(dialogsState.deleteAllDevices.defaultToEveryone) } - + // Clear emoji + if(dialogsState.clearAllEmoji != null){ AlertDialog( onDismissRequest = { // hide dialog - sendCommand(HideDeleteAllDevicesDialog) + sendCommand(HideClearEmoji) }, - 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 - } + 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.delete)), + text = GetString(stringResource(id = R.string.clear)), color = LocalColors.current.danger, onClick = { - // delete messages based on chosen option + // delete emoji sendCommand( - if(deleteAllDevices) MarkAsDeletedForEveryone( - dialogsState.deleteAllDevices.copy(defaultToEveryone = deleteAllDevices) - ) - else MarkAsDeletedLocally(dialogsState.deleteAllDevices.messages) + ClearEmoji(dialogsState.clearAllEmoji.emoji, dialogsState.clearAllEmoji.messageId) ) } ), @@ -201,7 +162,6 @@ fun ConversationV2Dialogs( ) ) } - } } 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 e0feabe0be..5f24566017 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 @@ -43,7 +43,10 @@ import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.ThreadDatabase 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.dependencies.ConfigFactory @@ -61,6 +64,7 @@ class ConversationViewModel( private val messageDataProvider: MessageDataProvider, private val groupDb: GroupDatabase, private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, private val lokiMessageDb: LokiMessageDatabase, private val textSecurePreferences: TextSecurePreferences, private val configFactory: ConfigFactory, @@ -339,12 +343,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 @@ -354,11 +358,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() + ) ) ) } @@ -372,6 +381,7 @@ class ConversationViewModel( messages = messages, defaultToEveryone = isAdmin.value, everyoneEnabled = true, + deleteForEveryoneLabel = application.getString(R.string.deleteMessageEveryone), messageType = conversationType ) ) @@ -387,6 +397,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() ) @@ -642,7 +653,7 @@ class ConversationViewModel( ).show() } - _dialogsState.update { it.copy(deleteAllDevices = data) } + _dialogsState.update { it.copy(deleteEveryone = data) } } // hide loading indicator @@ -659,11 +670,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) { @@ -855,9 +863,9 @@ class ConversationViewModel( } } - is Commands.HideDeleteAllDevicesDialog -> { + is Commands.HideClearEmoji -> { _dialogsState.update { - it.copy(deleteAllDevices = null) + it.copy(clearAllEmoji = null) } } @@ -872,6 +880,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)) } } @@ -921,6 +958,7 @@ class ConversationViewModel( private val messageDataProvider: MessageDataProvider, private val groupDb: GroupDatabase, private val threadDb: ThreadDatabase, + private val reactionDb: ReactionDatabase, @ApplicationContext private val context: Context, private val lokiMessageDb: LokiMessageDatabase, @@ -939,6 +977,7 @@ class ConversationViewModel( messageDataProvider = messageDataProvider, groupDb = groupDb, threadDb = threadDb, + reactionDb = reactionDb, lokiMessageDb = lokiMessageDb, textSecurePreferences = textSecurePreferences, configFactory = configFactory, @@ -949,8 +988,8 @@ class ConversationViewModel( data class DialogsState( val openLinkDialogUrl: String? = null, - val deleteEveryone: DeleteForEveryoneDialogData? = null, - val deleteAllDevices: DeleteForEveryoneDialogData? = null, + val clearAllEmoji: ClearAllEmoji? = null, + val deleteEveryone: DeleteForEveryoneDialogData? = null ) data class DeleteForEveryoneDialogData( @@ -958,13 +997,22 @@ class ConversationViewModel( val messageType: MessageType, val defaultToEveryone: Boolean, val everyoneEnabled: Boolean, + val deleteForEveryoneLabel: String, 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/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index 8697eeecff..690dccf02d 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/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 190aa8b2dd..9a322d44ee 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 @@ -89,7 +89,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 35d193967e..4a6acbcfdf 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/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 03231151b7..0e5f858dd4 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, @@ -913,7 +930,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val groupReceiptDatabase = get(context).groupReceiptDatabase() groupReceiptDatabase.deleteRowsForMessage(messageId) val database = databaseHelper.writableDatabase - database.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString())) + database!!.delete(TABLE_NAME, ID_WHERE, arrayOf(messageId.toString())) val threadDeleted = get(context).threadDatabase().update(threadId, false) notifyConversationListeners(threadId) notifyStickerListeners() 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 f22f50ff57..7ee5f19ae8 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; @@ -98,6 +101,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); } @@ -342,7 +353,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 728ee1e3f7..a308bf30dd 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; + } + @Override public String getTypeColumn() { return TYPE; 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 da97321097..3c4e302a34 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1288,6 +1288,16 @@ open class Storage @Inject constructor( } 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) { @@ -1766,10 +1776,17 @@ open class Storage @Inject constructor( 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 = mmsSmsDatabase.getMessageForTimestamp(timestamp) ?: return + if (messageRecord.isDeleted) return MessageId(messageRecord.id, messageRecord.isMms) } else return reactionDatabase.addReaction( 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 bb0436f3df..4ba8ed4ae4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -193,6 +193,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); @@ -298,7 +308,7 @@ public class ThreadDatabase extends Database { public void trimThreadBefore(long threadId, long timestamp) { Log.i("ThreadDatabase", "Trimming thread: " + threadId + " before :"+timestamp); DatabaseComponent.get(context).smsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); - DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp, false); + DatabaseComponent.get(context).mmsDatabase().deleteMessagesInThreadBeforeDate(threadId, timestamp); update(threadId, false); notifyConversationListeners(threadId); } @@ -707,10 +717,7 @@ public class ThreadDatabase extends Database { MmsSmsDatabase mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); long count = mmsSmsDatabase.getConversationCount(threadId); - MmsSmsDatabase.Reader reader = null; - - try { - reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId)); + try (MmsSmsDatabase.Reader reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId))) { MessageRecord record = null; if (reader != null) { record = reader.getNext(); @@ -724,7 +731,8 @@ public class ThreadDatabase extends Database { record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount()); return false; } else { - updateThread(threadId, 0, "", null, System.currentTimeMillis(), 0, 0, 0, false, 0, 0); + // for empty threads or if there is only deleted messages, show an empty snippet + clearSnippet(threadId); return false; } } finally { @@ -772,10 +780,6 @@ public class ThreadDatabase extends Database { return setLastSeen(threadId, lastSeenTime); } - private boolean deleteThreadOnEmpty(long threadId) { - return false; - } - 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 7601a7ef89..7346cffc65 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 @@ -107,10 +107,12 @@ public class ThreadRecord extends DisplayRecord { @Override public CharSequence getDisplayBody(@NonNull Context context) { - if (isGroupUpdateMessage()) { - return lastMessage != null - ? lastMessage.getDisplayBody(context).toString() - : context.getString(R.string.groupUpdated); + // 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); } else if (MmsSmsColumns.Types.isLegacyType(type)) { 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 4226c61289..8acb514c19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -365,6 +365,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() } 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 7aff840b0a..cf539a4bb3 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 { @@ -383,7 +394,9 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } @Composable - fun Buttons() { + fun Buttons( + recoveryHidden: Boolean + ) { Column( modifier = Modifier .padding(horizontal = LocalDimensions.current.spacing) @@ -445,12 +458,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 91326608e2..548369c016 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 @@ -63,6 +64,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 */ @@ -228,6 +233,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/drawable-hdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-hdpi/ic_trash_filled_32.png deleted file mode 100644 index 586af53d08..0000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_trash_filled_32.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-mdpi/ic_trash_filled_32.png deleted file mode 100644 index 0b1351e1c9..0000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_trash_filled_32.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-xhdpi/ic_trash_filled_32.png deleted file mode 100644 index 3d9e02a1f1..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_trash_filled_32.png and /dev/null differ 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 6a4ccab1b6..0000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_trash_filled_32.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_trash_filled_32.png b/app/src/main/res/drawable-xxxhdpi/ic_trash_filled_32.png deleted file mode 100644 index 7ddfecdadb..0000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_trash_filled_32.png and /dev/null differ diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index c0847f3dad..dc71a9e71e 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -305,14 +305,14 @@ @@ -339,7 +339,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/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" /> + android:gravity="center_vertical" + android:paddingVertical="@dimen/small_spacing"> @dimen/very_large_font_size + + diff --git a/app/src/main/res/xml/preferences_privacy.xml b/app/src/main/res/xml/preferences_privacy.xml index b4082bd125..64bcc29902 100644 --- a/app/src/main/res/xml/preferences_privacy.xml +++ b/app/src/main/res/xml/preferences_privacy.xml @@ -11,12 +11,6 @@ android:title="@string/lockApp" android:summary="@string/lockAppDescription" /> - - ? 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 d28d38c771..81b14099c2 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 @@ -39,6 +39,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) { @@ -97,7 +104,10 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { Log.w(TAG, "Failed to send $message::class.simpleName.", error) 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 48a64e0cf1..53b835592a 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 @@ -511,9 +511,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) 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" } diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 0328575bda..725417e3f8 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -114,7 +114,6 @@ interface TextSecurePreferences { fun isEnterSendsEnabled(): Boolean fun isPasswordDisabled(): Boolean fun setPasswordDisabled(disabled: Boolean) - fun isScreenSecurityEnabled(): Boolean fun getLastVersionCode(): Int fun setLastVersionCode(versionCode: Int) fun isPassphraseTimeoutEnabled(): Boolean @@ -219,7 +218,6 @@ interface TextSecurePreferences { const val LED_BLINK_PREF_CUSTOM = "pref_led_blink_custom" const val PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval" const val PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase" - const val SCREEN_SECURITY_PREF = "pref_screen_security" const val ENTER_SENDS_PREF = "pref_enter_sends" const val THREAD_TRIM_ENABLED = "pref_trim_threads" internal const val LOCAL_NUMBER_PREF = "pref_local_number" @@ -666,11 +664,6 @@ interface TextSecurePreferences { setBooleanPreference(context, DISABLE_PASSPHRASE_PREF, disabled) } - @JvmStatic - fun isScreenSecurityEnabled(context: Context): Boolean { - return getBooleanPreference(context, SCREEN_SECURITY_PREF, context.resources.getBoolean(R.bool.screen_security_default)) - } - fun getLastVersionCode(context: Context): Int { return getIntegerPreference(context, LAST_VERSION_CODE_PREF, 0) } @@ -1298,10 +1291,6 @@ class AppTextSecurePreferences @Inject constructor( setBooleanPreference(TextSecurePreferences.DISABLE_PASSPHRASE_PREF, disabled) } - override fun isScreenSecurityEnabled(): Boolean { - return getBooleanPreference(TextSecurePreferences.SCREEN_SECURITY_PREF, true) - } - override fun getLastVersionCode(): Int { return getIntegerPreference(TextSecurePreferences.LAST_VERSION_CODE_PREF, 0) }