mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-14 23:17:46 +00:00
Merge remote-tracking branch 'upstream/dev' into conversation_camera_improvement
This commit is contained in:
@@ -19,6 +19,7 @@ import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
||||
import org.thoughtcrime.securesms.database.Database
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints
|
||||
@@ -167,14 +168,28 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
}
|
||||
|
||||
override fun deleteMessage(messageID: Long, isSms: Boolean) {
|
||||
if (isSms) {
|
||||
val db = DatabaseFactory.getSmsDatabase(context)
|
||||
db.deleteMessage(messageID)
|
||||
} else {
|
||||
val db = DatabaseFactory.getMmsDatabase(context)
|
||||
db.delete(messageID)
|
||||
}
|
||||
val messagingDatabase: MessagingDatabase = if (isSms) DatabaseFactory.getSmsDatabase(context)
|
||||
else DatabaseFactory.getMmsDatabase(context)
|
||||
messagingDatabase.deleteMessage(messageID)
|
||||
DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms)
|
||||
DatabaseFactory.getLokiMessageDatabase(context).deleteMessageServerHash(messageID)
|
||||
}
|
||||
|
||||
override fun updateMessageAsDeleted(timestamp: Long, author: String) {
|
||||
val database = DatabaseFactory.getMmsSmsDatabase(context)
|
||||
val address = Address.fromSerialized(author)
|
||||
val message = database.getMessageFor(timestamp, address) ?: return
|
||||
val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseFactory.getMmsDatabase(context)
|
||||
else DatabaseFactory.getSmsDatabase(context)
|
||||
messagingDatabase.markAsDeleted(message.id, message.isRead)
|
||||
if (message.isOutgoing) {
|
||||
messagingDatabase.deleteMessage(message.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getServerHashForMessage(messageID: Long): String? {
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
|
||||
return messageDB.getMessageServerHash(messageID)
|
||||
}
|
||||
|
||||
override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? {
|
||||
|
@@ -50,6 +50,7 @@ import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.mentions.Mention
|
||||
import org.session.libsession.messaging.mentions.MentionsManager
|
||||
import org.session.libsession.messaging.messages.control.DataExtractionNotification
|
||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
|
||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
||||
import org.session.libsession.messaging.messages.visible.OpenGroupInvitation
|
||||
@@ -59,8 +60,10 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.MediaTypes
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.concurrent.SimpleTask
|
||||
@@ -70,6 +73,7 @@ import org.session.libsignal.crypto.MnemonicCodec
|
||||
import org.session.libsignal.utilities.ListenableFuture
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder
|
||||
@@ -205,6 +209,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
const val PICK_GIF = 10
|
||||
const val PICK_FROM_LIBRARY = 12
|
||||
const val INVITE_CONTACTS = 124
|
||||
|
||||
//flag
|
||||
const val IS_UNSEND_REQUESTS_ENABLED = false
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -1110,7 +1117,61 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
stopAudioHandler.removeCallbacks(stopVoiceMessageRecordingTask)
|
||||
}
|
||||
|
||||
override fun deleteMessages(messages: Set<MessageRecord>) {
|
||||
private fun buildUnsendRequest(message: MessageRecord): UnsendRequest? {
|
||||
if (this.thread.isOpenGroupRecipient) return null
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
messageDataProvider.getServerHashForMessage(message.id) ?: return null
|
||||
val unsendRequest = UnsendRequest()
|
||||
if (message.isOutgoing) {
|
||||
unsendRequest.author = TextSecurePreferences.getLocalNumber(this)
|
||||
} else {
|
||||
unsendRequest.author = message.individualRecipient.address.contactIdentifier()
|
||||
}
|
||||
unsendRequest.timestamp = message.timestamp
|
||||
|
||||
return unsendRequest
|
||||
}
|
||||
|
||||
private fun deleteLocally(message: MessageRecord) {
|
||||
buildUnsendRequest(message)?.let { unsendRequest ->
|
||||
TextSecurePreferences.getLocalNumber(this@ConversationActivityV2)?.let {
|
||||
MessageSender.send(unsendRequest, Address.fromSerialized(it))
|
||||
}
|
||||
}
|
||||
MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||
}
|
||||
|
||||
private fun deleteForEveryone(message: MessageRecord) {
|
||||
buildUnsendRequest(message)?.let { unsendRequest ->
|
||||
MessageSender.send(unsendRequest, thread.address)
|
||||
}
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2)
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID)
|
||||
if (openGroup != null) {
|
||||
messageDB.getServerID(message.id, !message.isMms)?.let { messageServerID ->
|
||||
OpenGroupAPIV2.deleteMessage(messageServerID, openGroup.room, openGroup.server)
|
||||
.success {
|
||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||
}.failUi { error ->
|
||||
Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
messageDataProvider.deleteMessage(message.id, !message.isMms)
|
||||
messageDataProvider.getServerHashForMessage(message.id)?.let { serverHash ->
|
||||
var publicKey = thread.address.serialize()
|
||||
if (thread.isClosedGroupRecipient) { publicKey = GroupUtil.doubleDecodeGroupID(publicKey).toHexString() }
|
||||
SnodeAPI.deleteMessage(publicKey, listOf(serverHash))
|
||||
.failUi { error ->
|
||||
Toast.makeText(this@ConversationActivityV2, "Couldn't delete message due to error: $error", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove this after the unsend request is enabled
|
||||
fun deleteMessagesWithoutUnsendRequest(messages: Set<MessageRecord>) {
|
||||
val messageCount = messages.size
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val messageDB = DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2)
|
||||
@@ -1137,7 +1198,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
} else {
|
||||
for (message in messages) {
|
||||
if (message.isMms) {
|
||||
DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).delete(message.id)
|
||||
DatabaseFactory.getMmsDatabase(this@ConversationActivityV2).deleteMessage(message.id)
|
||||
} else {
|
||||
DatabaseFactory.getSmsDatabase(this@ConversationActivityV2).deleteMessage(message.id)
|
||||
}
|
||||
@@ -1152,6 +1213,72 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
builder.show()
|
||||
}
|
||||
|
||||
override fun deleteMessages(messages: Set<MessageRecord>) {
|
||||
if (!IS_UNSEND_REQUESTS_ENABLED) {
|
||||
deleteMessagesWithoutUnsendRequest(messages)
|
||||
return
|
||||
}
|
||||
val allSentByCurrentUser = messages.all { it.isOutgoing }
|
||||
val allHasHash = messages.all { DatabaseFactory.getLokiMessageDatabase(this@ConversationActivityV2).getMessageServerHash(it.id) != null }
|
||||
if (thread.isOpenGroupRecipient) {
|
||||
val messageCount = messages.size
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
||||
builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
||||
builder.setCancelable(true)
|
||||
builder.setPositiveButton(R.string.delete) { _, _ ->
|
||||
for (message in messages) {
|
||||
this.deleteForEveryone(message)
|
||||
}
|
||||
endActionMode()
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
endActionMode()
|
||||
}
|
||||
builder.show()
|
||||
} else if (allSentByCurrentUser && allHasHash) {
|
||||
val bottomSheet = DeleteOptionsBottomSheet()
|
||||
bottomSheet.recipient = thread
|
||||
bottomSheet.onDeleteForMeTapped = {
|
||||
for (message in messages) {
|
||||
this.deleteLocally(message)
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
endActionMode()
|
||||
}
|
||||
bottomSheet.onDeleteForEveryoneTapped = {
|
||||
for (message in messages) {
|
||||
this.deleteForEveryone(message)
|
||||
}
|
||||
bottomSheet.dismiss()
|
||||
endActionMode()
|
||||
}
|
||||
bottomSheet.onCancelTapped = {
|
||||
bottomSheet.dismiss()
|
||||
endActionMode()
|
||||
}
|
||||
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||
} else {
|
||||
val messageCount = messages.size
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(resources.getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messageCount, messageCount))
|
||||
builder.setMessage(resources.getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messageCount, messageCount))
|
||||
builder.setCancelable(true)
|
||||
builder.setPositiveButton(R.string.delete) { _, _ ->
|
||||
for (message in messages) {
|
||||
this.deleteLocally(message)
|
||||
}
|
||||
endActionMode()
|
||||
}
|
||||
builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
endActionMode()
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun banUser(messages: Set<MessageRecord>) {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
val sessionID = messages.first().individualRecipient.address.toString()
|
||||
|
@@ -73,9 +73,11 @@ class ConversationAdapter(context: Context, cursor: Cursor, private val onItemPr
|
||||
val position = viewHolder.adapterPosition
|
||||
view.indexInAdapter = position
|
||||
view.bind(message, getMessageBefore(position, cursor), getMessageAfter(position, cursor), glide, searchQuery)
|
||||
view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) }
|
||||
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
||||
if (!message.isDeleted) {
|
||||
view.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, view, event) }
|
||||
view.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) }
|
||||
view.onLongPress = { onItemLongPress(message, viewHolder.adapterPosition) }
|
||||
}
|
||||
view.contentViewDelegate = visibleMessageContentViewDelegate
|
||||
}
|
||||
is ControlMessageViewHolder -> viewHolder.view.bind(message)
|
||||
|
@@ -0,0 +1,52 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.*
|
||||
import kotlinx.android.synthetic.main.fragment_delete_message_bottom_sheet.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
|
||||
class DeleteOptionsBottomSheet: BottomSheetDialogFragment(), View.OnClickListener {
|
||||
|
||||
lateinit var recipient: Recipient
|
||||
|
||||
var onDeleteForMeTapped: (() -> Unit?)? = null
|
||||
var onDeleteForEveryoneTapped: (() -> Unit)? = null
|
||||
var onCancelTapped: (() -> Unit)? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_delete_message_bottom_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
when (v) {
|
||||
deleteForMeTextView -> onDeleteForMeTapped?.invoke()
|
||||
deleteForEveryoneTextView -> onDeleteForEveryoneTapped?.invoke()
|
||||
cancelTextView -> onCancelTapped?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (!this::recipient.isInitialized) { return dismiss() }
|
||||
if (!recipient.isGroupRecipient) {
|
||||
deleteForEveryoneTextView.text = resources.getString(R.string.delete_message_for_me_and_recipient, recipient.name)
|
||||
}
|
||||
deleteForMeTextView.setOnClickListener(this)
|
||||
deleteForEveryoneTextView.setOnClickListener(this)
|
||||
cancelTextView.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val window = dialog?.window ?: return
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
|
||||
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
|
||||
}
|
||||
}
|
@@ -21,13 +21,14 @@ import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity
|
||||
import org.thoughtcrime.securesms.components.CornerMask
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
import org.thoughtcrime.securesms.longmessage.LongMessageActivity
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.Slide
|
||||
import org.thoughtcrime.securesms.video.exo.AttachmentDataSource
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class AlbumThumbnailView : FrameLayout {
|
||||
@@ -80,6 +81,13 @@ class AlbumThumbnailView : FrameLayout {
|
||||
}
|
||||
return
|
||||
}
|
||||
val intersectedSpans = albumCellBodyText.getIntersectedModalSpans(eventRect)
|
||||
if (intersectedSpans.isNotEmpty()) {
|
||||
intersectedSpans.forEach { span ->
|
||||
span.onClick(albumCellBodyText)
|
||||
}
|
||||
return
|
||||
}
|
||||
// test each album child
|
||||
albumCellContainer.findViewById<ViewGroup>(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child ->
|
||||
child.getGlobalVisibleRect(testRect)
|
||||
@@ -130,7 +138,8 @@ class AlbumThumbnailView : FrameLayout {
|
||||
thumbnailView.setImageResource(glideRequests, slide, isPreview = false, mms = message)
|
||||
}
|
||||
albumCellBodyParent.isVisible = message.body.isNotEmpty()
|
||||
albumCellBodyText.text = message.body
|
||||
val body = VisibleMessageContentView.getBodySpans(context, message, null)
|
||||
albumCellBodyText.text = body
|
||||
post {
|
||||
// post to await layout of text
|
||||
albumCellBodyText.layout?.let { layout ->
|
||||
|
@@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapter
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||
@@ -34,8 +35,17 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
||||
val thread = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!!
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
fun userCanDeleteSelectedItems(): Boolean {
|
||||
if (openGroup == null) { return true }
|
||||
val allSentByCurrentUser = selectedItems.all { it.isOutgoing }
|
||||
|
||||
// Remove this after the unsend request is enabled
|
||||
if (!ConversationActivityV2.IS_UNSEND_REQUESTS_ENABLED) {
|
||||
if (openGroup == null) { return true }
|
||||
if (allSentByCurrentUser) { return true }
|
||||
return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
|
||||
}
|
||||
|
||||
val allReceivedByCurrentUser = selectedItems.all { !it.isOutgoing }
|
||||
if (openGroup == null) { return allSentByCurrentUser || allReceivedByCurrentUser }
|
||||
if (allSentByCurrentUser) { return true }
|
||||
return OpenGroupAPIV2.isUserModerator(userPublicKey, openGroup.room, openGroup.server)
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.ColorInt
|
||||
import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.view.*
|
||||
import kotlinx.android.synthetic.main.view_deleted_message.view.*
|
||||
import kotlinx.android.synthetic.main.view_document.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import java.util.*
|
||||
|
||||
class DeletedMessageView : LinearLayout {
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||
|
||||
private fun initialize() {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_deleted_message, this)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun bind(message: MessageRecord, @ColorInt textColor: Int) {
|
||||
assert(message.isDeleted)
|
||||
deleteTitleTextView.text = context.getString(R.string.deleted_message)
|
||||
deleteTitleTextView.setTextColor(textColor)
|
||||
deletedMessageViewIconImageView.imageTintList = ColorStateList.valueOf(textColor)
|
||||
}
|
||||
// endregion
|
||||
}
|
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spannable
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.URLSpan
|
||||
@@ -28,21 +29,21 @@ import okhttp3.HttpUrl
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
import org.thoughtcrime.securesms.util.SearchUtil.StyleFactory
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
||||
import java.net.IDN
|
||||
import org.thoughtcrime.securesms.util.getColorWithID
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -76,7 +77,11 @@ class VisibleMessageContentView : LinearLayout {
|
||||
mainContainer.removeAllViews()
|
||||
onContentClick = null
|
||||
onContentDoubleTap = null
|
||||
if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
||||
if (message.isDeleted) {
|
||||
val deletedMessageView = DeletedMessageView(context)
|
||||
deletedMessageView.bind(message, VisibleMessageContentView.getTextColor(context,message))
|
||||
mainContainer.addView(deletedMessageView)
|
||||
} else if (message is MmsMessageRecord && message.linkPreviews.isNotEmpty()) {
|
||||
val linkPreviewView = LinkPreviewView(context)
|
||||
linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster, searchQuery)
|
||||
mainContainer.addView(linkPreviewView)
|
||||
@@ -106,6 +111,10 @@ class VisibleMessageContentView : LinearLayout {
|
||||
quoteView.getGlobalVisibleRect(r)
|
||||
if (r.contains(event.rawX.roundToInt(), event.rawY.roundToInt())) {
|
||||
delegate?.scrollToMessageIfPossible(quote.id)
|
||||
} else {
|
||||
bodyTextView.getIntersectedModalSpans(event).forEach { span ->
|
||||
span.onClick(bodyTextView)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (message is MmsMessageRecord && message.slideDeck.audioSlide != null) {
|
||||
@@ -209,7 +218,18 @@ class VisibleMessageContentView : LinearLayout {
|
||||
val color = getTextColor(context, message)
|
||||
result.setTextColor(color)
|
||||
result.setLinkTextColor(color)
|
||||
val body = getBodySpans(context, message, searchQuery)
|
||||
result.text = body
|
||||
return result
|
||||
}
|
||||
|
||||
fun getBodySpans(context: Context, message: MessageRecord, searchQuery: String?): Spannable {
|
||||
var body = message.body.toSpannable()
|
||||
|
||||
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
|
||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery)
|
||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery)
|
||||
|
||||
Linkify.addLinks(body, Linkify.WEB_URLS)
|
||||
|
||||
// replace URLSpans with ModalURLSpans
|
||||
@@ -225,13 +245,7 @@ class VisibleMessageContentView : LinearLayout {
|
||||
body.removeSpan(urlSpan)
|
||||
body.setSpan(replacementSpan, start, end, flags)
|
||||
}
|
||||
|
||||
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context)
|
||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { BackgroundColorSpan(Color.WHITE) }, body, searchQuery)
|
||||
body = SearchUtil.getHighlightedSpan(Locale.getDefault(), StyleFactory { ForegroundColorSpan(Color.BLACK) }, body, searchQuery)
|
||||
|
||||
result.text = body
|
||||
return result
|
||||
return body
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
|
@@ -266,6 +266,7 @@ class VisibleMessageView : LinearLayout {
|
||||
|
||||
// region Interaction
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (onPress == null || onSwipeToReply == null || onLongPress == null) { return false }
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> onDown(event)
|
||||
MotionEvent.ACTION_MOVE -> onMove(event)
|
||||
|
@@ -12,12 +12,14 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
private val messageIDTable = "loki_message_friend_request_database"
|
||||
private val messageThreadMappingTable = "loki_message_thread_mapping_database"
|
||||
private val errorMessageTable = "loki_error_message_database"
|
||||
private val messageHashTable = "loki_message_hash_database"
|
||||
private val messageID = "message_id"
|
||||
private val serverID = "server_id"
|
||||
private val friendRequestStatus = "friend_request_status"
|
||||
private val threadID = "thread_id"
|
||||
private val errorMessage = "error_message"
|
||||
private val messageType = "message_type"
|
||||
private val serverHash = "server_hash"
|
||||
@JvmStatic
|
||||
val createMessageIDTableCommand = "CREATE TABLE $messageIDTable ($messageID INTEGER PRIMARY KEY, $serverID INTEGER DEFAULT 0, $friendRequestStatus INTEGER DEFAULT 0);"
|
||||
@JvmStatic
|
||||
@@ -28,6 +30,8 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
val updateMessageIDTableForType = "ALTER TABLE $messageIDTable ADD COLUMN $messageType INTEGER DEFAULT 0; ALTER TABLE $messageIDTable ADD CONSTRAINT PK_$messageIDTable PRIMARY KEY ($messageID, $serverID);"
|
||||
@JvmStatic
|
||||
val updateMessageMappingTable = "ALTER TABLE $messageThreadMappingTable ADD COLUMN $serverID INTEGER DEFAULT 0; ALTER TABLE $messageThreadMappingTable ADD CONSTRAINT PK_$messageThreadMappingTable PRIMARY KEY ($messageID, $serverID);"
|
||||
@JvmStatic
|
||||
val createMessageHashTableCommand = "CREATE TABLE IF NOT EXISTS $messageHashTable ($messageID INTEGER PRIMARY KEY, $serverHash STRING);"
|
||||
|
||||
const val SMS_TYPE = 0
|
||||
const val MMS_TYPE = 1
|
||||
@@ -150,4 +154,24 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageServerHash(messageID: Long): String? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) { cursor ->
|
||||
cursor.getString(serverHash)
|
||||
}
|
||||
}
|
||||
|
||||
fun setMessageServerHash(messageID: Long, serverHash: String) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues(2)
|
||||
contentValues.put(Companion.messageID, messageID)
|
||||
contentValues.put(Companion.serverHash, serverHash)
|
||||
database.insertOrUpdate(messageHashTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
||||
}
|
||||
|
||||
fun deleteMessageServerHash(messageID: Long) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
||||
}
|
||||
}
|
@@ -38,6 +38,10 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
|
||||
public abstract void markAsSent(long messageId, boolean secure);
|
||||
public abstract void markUnidentified(long messageId, boolean unidentified);
|
||||
|
||||
public abstract void markAsDeleted(long messageId, boolean read);
|
||||
|
||||
public abstract boolean deleteMessage(long messageId);
|
||||
|
||||
public void addMismatchedIdentity(long messageId, Address address, IdentityKey identityKey) {
|
||||
try {
|
||||
addToDocument(messageId, MISMATCHED_IDENTITIES,
|
||||
|
@@ -391,6 +391,23 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsDeleted(long messageId, boolean read) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(BODY, "");
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId));
|
||||
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); }
|
||||
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE, Optional.of(threadId));
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markExpireStarted(long messageId) {
|
||||
markExpireStarted(messageId, System.currentTimeMillis());
|
||||
@@ -906,7 +923,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
public boolean delete(long messageId) {
|
||||
@Override
|
||||
public boolean deleteMessage(long messageId) {
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
ThreadUtils.queue(() -> attachmentDatabase.deleteAttachmentsForMessage(messageId));
|
||||
@@ -1033,7 +1051,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
cursor = db.query(TABLE_NAME, new String[] {ID}, where, null, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
delete(cursor.getLong(0));
|
||||
deleteMessage(cursor.getLong(0));
|
||||
}
|
||||
|
||||
} finally {
|
||||
@@ -1059,7 +1077,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Log.i("MmsDatabase", "Trimming: " + cursor.getLong(0));
|
||||
delete(cursor.getLong(0));
|
||||
deleteMessage(cursor.getLong(0));
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
@@ -40,6 +40,7 @@ public interface MmsSmsColumns {
|
||||
protected static final long BASE_PENDING_SECURE_SMS_FALLBACK = 25;
|
||||
protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26;
|
||||
public static final long BASE_DRAFT_TYPE = 27;
|
||||
protected static final long BASE_DELETED_TYPE = 28;
|
||||
|
||||
protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE,
|
||||
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE,
|
||||
@@ -152,6 +153,8 @@ public interface MmsSmsColumns {
|
||||
return (type & BASE_TYPE_MASK) == BASE_INBOX_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isDeletedMessage(long type) { return (type & BASE_TYPE_MASK) == BASE_DELETED_TYPE; }
|
||||
|
||||
public static boolean isJoinedType(long type) {
|
||||
return (type & BASE_TYPE_MASK) == JOINED_TYPE;
|
||||
}
|
||||
|
@@ -129,7 +129,7 @@ public class MmsSmsDatabase extends Database {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
|
||||
return queryTables(PROJECTION, selection, order, "1");
|
||||
return queryTables(PROJECTION, selection, order, null);
|
||||
}
|
||||
|
||||
public long getLastMessageID(long threadId) {
|
||||
|
@@ -183,6 +183,18 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsDeleted(long messageId, boolean read) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(BODY, "");
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
if (!read) { DatabaseFactory.getThreadDatabase(context).decrementUnread(threadId, 1); }
|
||||
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markExpireStarted(long id) {
|
||||
markExpireStarted(id, System.currentTimeMillis());
|
||||
@@ -517,6 +529,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteMessage(long messageId) {
|
||||
Log.i("MessageDatabase", "Deleting: " + messageId);
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
|
@@ -148,6 +148,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) {
|
||||
JobQueue.shared.add(TrimThreadJob(threadID))
|
||||
}
|
||||
message.serverHash?.let { serverHash ->
|
||||
messageID?.let { id ->
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(id, serverHash)
|
||||
}
|
||||
}
|
||||
return messageID
|
||||
}
|
||||
|
||||
@@ -358,6 +363,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMessageServerHash(messageID: Long, serverHash: String) {
|
||||
DatabaseFactory.getLokiMessageDatabase(context).setMessageServerHash(messageID, serverHash)
|
||||
}
|
||||
|
||||
override fun getGroup(groupID: String): GroupRecord? {
|
||||
val group = DatabaseFactory.getGroupDatabase(context).getGroup(groupID)
|
||||
return if (group.isPresent) { group.get() } else null
|
||||
|
@@ -294,6 +294,14 @@ public class ThreadDatabase extends Database {
|
||||
String.valueOf(threadId)});
|
||||
}
|
||||
|
||||
public void decrementUnread(long threadId, int amount) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
|
||||
UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0",
|
||||
new String[] {String.valueOf(amount),
|
||||
String.valueOf(threadId)});
|
||||
}
|
||||
|
||||
public void setDistributionType(long threadId, int distributionType) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(TYPE, distributionType);
|
||||
@@ -536,9 +544,14 @@ public class ThreadDatabase extends Database {
|
||||
|
||||
try {
|
||||
reader = mmsSmsDatabase.readerFor(mmsSmsDatabase.getConversationSnippet(threadId));
|
||||
MessageRecord record;
|
||||
|
||||
if (reader != null && (record = reader.getNext()) != null) {
|
||||
MessageRecord record = null;
|
||||
if (reader != null) {
|
||||
record = reader.getNext();
|
||||
while (record != null && record.isDeleted()) {
|
||||
record = reader.getNext();
|
||||
}
|
||||
}
|
||||
if (record != null && !record.isDeleted()) {
|
||||
updateThread(threadId, count, getFormattedBodyFor(record), getAttachmentUriFor(record),
|
||||
record.getTimestamp(), record.getDeliveryStatus(), record.getDeliveryReceiptCount(),
|
||||
record.getType(), unarchive, record.getExpiresIn(), record.getReadReceiptCount());
|
||||
|
@@ -59,9 +59,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int lokiV25 = 46;
|
||||
private static final int lokiV26 = 47;
|
||||
private static final int lokiV27 = 48;
|
||||
private static final int lokiV28 = 49;
|
||||
|
||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV27;
|
||||
private static final int DATABASE_VERSION = lokiV28;
|
||||
private static final String DATABASE_NAME = "signal.db";
|
||||
|
||||
private final Context context;
|
||||
@@ -123,6 +124,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageIDTableCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreateSessionResetTableCommand());
|
||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
||||
db.execSQL(LokiUserDatabase.getCreateDisplayNameTableCommand());
|
||||
@@ -302,6 +304,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV28) {
|
||||
db.execSQL(LokiMessageDatabase.getCreateMessageHashTableCommand());
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@@ -117,6 +117,7 @@ public abstract class DisplayRecord {
|
||||
public boolean isMissedCall() {
|
||||
return SmsDatabase.Types.isMissedCall(type);
|
||||
}
|
||||
public boolean isDeleted() { return MmsSmsColumns.Types.isDeletedMessage(type); }
|
||||
|
||||
public boolean isControlMessage() {
|
||||
return isGroupUpdateMessage() || isExpirationTimerUpdate() || isDataExtractionNotification();
|
||||
|
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.longmessage;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
@@ -15,7 +16,7 @@ import org.session.libsession.utilities.Address;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsession.utilities.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities;
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageContentView;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
@@ -81,18 +82,10 @@ public class LongMessageActivity extends PassphraseRequiredActionBarActivity {
|
||||
String name = Util.getFirstNonEmpty(recipient.getName(), recipient.getProfileName(), recipient.getAddress().serialize());
|
||||
getSupportActionBar().setTitle(getString(R.string.LongMessageActivity_message_from_s, name));
|
||||
}
|
||||
|
||||
String trimmedBody = getTrimmedBody(message.get().getFullBody());
|
||||
String mentionBody = MentionUtilities.highlightMentions(trimmedBody, message.get().getMessageRecord().getThreadId(), this);
|
||||
|
||||
textBody.setText(mentionBody);
|
||||
Spannable bodySpans = VisibleMessageContentView.Companion.getBodySpans(this, message.get().getMessageRecord(), null);
|
||||
textBody.setText(bodySpans);
|
||||
textBody.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
});
|
||||
}
|
||||
|
||||
private String getTrimmedBody(@NonNull String text) {
|
||||
return text.length() <= MAX_DISPLAY_LENGTH ? text
|
||||
: text.substring(0, MAX_DISPLAY_LENGTH);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -49,9 +49,9 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
|
||||
// DMs
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val dmsPromise = SnodeAPI.getMessages(userPublicKey).map { envelopes ->
|
||||
envelopes.map { envelope ->
|
||||
envelopes.map { (envelope, serverHash) ->
|
||||
// FIXME: Using a job here seems like a bad idea...
|
||||
MessageReceiveJob(envelope.toByteArray()).executeAsync()
|
||||
MessageReceiveJob(envelope.toByteArray(), serverHash).executeAsync()
|
||||
}
|
||||
}
|
||||
promises.addAll(dmsPromise.get())
|
||||
|
@@ -229,7 +229,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
||||
}
|
||||
|
||||
if (expiredMessage != null) {
|
||||
if (expiredMessage.mms) mmsDatabase.delete(expiredMessage.id);
|
||||
if (expiredMessage.mms) mmsDatabase.deleteMessage(expiredMessage.id);
|
||||
else smsDatabase.deleteMessage(expiredMessage.id);
|
||||
}
|
||||
}
|
||||
|
@@ -66,7 +66,7 @@ public class AttachmentUtil {
|
||||
.size();
|
||||
|
||||
if (attachmentCount <= 1) {
|
||||
DatabaseFactory.getMmsDatabase(context).delete(mmsId);
|
||||
DatabaseFactory.getMmsDatabase(context).deleteMessage(mmsId);
|
||||
} else {
|
||||
DatabaseFactory.getAttachmentDatabase(context).deleteAttachment(attachmentId);
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@
|
||||
android:layout_width="@dimen/accent_line_thickness"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/accent"/>
|
||||
<TextView
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:maxLines="4"
|
||||
android:ellipsize="end"
|
||||
android:id="@+id/albumCellBodyText"
|
||||
|
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:behavior_hideable="true"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/deleteForMeTextView"
|
||||
style="@style/BottomSheetActionItem"
|
||||
android:text="@string/delete_message_for_me"
|
||||
android:textColor="@color/destructive"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/deleteForEveryoneTextView"
|
||||
style="@style/BottomSheetActionItem"
|
||||
android:text="@string/delete_message_for_everyone"
|
||||
android:textColor="@color/destructive"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cancelTextView"
|
||||
style="@style/BottomSheetActionItem"
|
||||
android:text="@string/cancel" />
|
||||
|
||||
</LinearLayout>
|
@@ -12,6 +12,8 @@
|
||||
|
||||
<TextView
|
||||
android:textSize="@dimen/text_size"
|
||||
android:focusable="true"
|
||||
android:textColorLink="@color/text"
|
||||
android:id="@+id/longmessage_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
32
app/src/main/res/layout/view_deleted_message.xml
Normal file
32
app/src/main/res/layout/view_deleted_message.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/small_spacing"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/deletedMessageViewIconImageView"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="@dimen/small_spacing"
|
||||
android:src="?menu_trash_icon"
|
||||
app:tint="@color/text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/deleteTitleTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="@dimen/small_spacing"
|
||||
android:textSize="@dimen/very_small_font_size"
|
||||
android:textColor="@color/text"
|
||||
tools:text="This message has been deleted"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</LinearLayout>
|
@@ -893,4 +893,9 @@
|
||||
<string name="dialog_send_seed_send_button_title">Send</string>
|
||||
<string name="notify_type_all">All</string>
|
||||
<string name="notify_type_mentions">Mentions</string>
|
||||
<string name="deleted_message">This message has been deleted</string>
|
||||
<string name="delete_message_for_me">Delete just for me</string>
|
||||
<string name="delete_message_for_everyone">Delete for everyone</string>
|
||||
<string name="delete_message_for_me_and_recipient">Delete for me and %s</string>
|
||||
|
||||
</resources>
|
||||
|
Reference in New Issue
Block a user