mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add import/export of chat colors.
This commit is contained in:
@@ -86,6 +86,8 @@ fun RecipientTable.getContactsForBackup(selfId: Long): BackupContactIterator {
|
||||
RecipientTable.PROFILE_FAMILY_NAME,
|
||||
RecipientTable.PROFILE_JOINED_NAME,
|
||||
RecipientTable.MUTE_UNTIL,
|
||||
RecipientTable.CHAT_COLORS,
|
||||
RecipientTable.CUSTOM_CHAT_COLORS_ID,
|
||||
RecipientTable.EXTRAS
|
||||
)
|
||||
.from(RecipientTable.TABLE_NAME)
|
||||
|
||||
@@ -7,16 +7,23 @@ package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import android.database.Cursor
|
||||
import androidx.core.content.contentValuesOf
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.toInt
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Chat
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import java.io.Closeable
|
||||
|
||||
@@ -33,7 +40,9 @@ fun ThreadTable.getThreadsForBackup(): ChatIterator {
|
||||
${ThreadTable.ARCHIVED},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING}
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS},
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}
|
||||
FROM ${ThreadTable.TABLE_NAME}
|
||||
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
|
||||
"""
|
||||
@@ -49,6 +58,15 @@ fun ThreadTable.clearAllDataForBackupRestore() {
|
||||
}
|
||||
|
||||
fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? {
|
||||
val chatColor = chat.style?.parseChatColor()
|
||||
val chatColorWithId = if (chatColor != null && chatColor.id is ChatColors.Id.NotSet) {
|
||||
val savedColors = SignalDatabase.chatColors.getSavedChatColors()
|
||||
val match = savedColors.find { it.matchesWithoutId(chatColor) }
|
||||
match ?: SignalDatabase.chatColors.saveChatColors(chatColor)
|
||||
} else {
|
||||
chatColor
|
||||
}
|
||||
|
||||
val threadId = writableDatabase
|
||||
.insertInto(ThreadTable.TABLE_NAME)
|
||||
.values(
|
||||
@@ -65,7 +83,9 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? {
|
||||
contentValuesOf(
|
||||
RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.MentionSetting.DO_NOT_NOTIFY.id else RecipientTable.MentionSetting.ALWAYS_NOTIFY.id),
|
||||
RecipientTable.MUTE_UNTIL to chat.muteUntilMs,
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs
|
||||
RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs,
|
||||
RecipientTable.CHAT_COLORS to chatColorWithId?.serialize()?.encode(),
|
||||
RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColorWithId?.id ?: ChatColors.Id.NotSet).longValue
|
||||
),
|
||||
"${RecipientTable.ID} = ?",
|
||||
SqlUtil.buildArgs(recipientId.toLong())
|
||||
@@ -84,6 +104,32 @@ class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS)
|
||||
val customChatColorsId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID))
|
||||
val chatColors: ChatColors? = if (serializedChatColors != null) {
|
||||
try {
|
||||
ChatColors.forChatColor(customChatColorsId, ChatColor.ADAPTER.decode(serializedChatColors))
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val chatStyleBuilder = ChatStyle.Builder()
|
||||
if (chatColors != null) {
|
||||
val presetBubbleColor = chatColors.tryToMapToBackupPreset()
|
||||
if (presetBubbleColor != null) {
|
||||
chatStyleBuilder.bubbleColorPreset = presetBubbleColor
|
||||
} else if (chatColors.isGradient()) {
|
||||
chatStyleBuilder.bubbleGradient = ChatStyle.Gradient(angle = chatColors.getDegrees().toInt(), colors = chatColors.getColors().toList())
|
||||
} else if (customChatColorsId is ChatColors.Id.Auto) {
|
||||
chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor()
|
||||
} else {
|
||||
chatStyleBuilder.bubbleSolidColor = chatColors.asSingleColor()
|
||||
}
|
||||
}
|
||||
|
||||
return Chat(
|
||||
id = cursor.requireLong(ThreadTable.ID),
|
||||
recipientId = cursor.requireLong(ThreadTable.RECIPIENT_ID),
|
||||
@@ -92,7 +138,8 @@ class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
|
||||
expirationTimerMs = cursor.requireLong(RecipientTable.MESSAGE_EXPIRATION_TIME),
|
||||
muteUntilMs = cursor.requireLong(RecipientTable.MUTE_UNTIL),
|
||||
markedUnread = ThreadTable.ReadStatus.deserialize(cursor.requireInt(ThreadTable.READ)) == ThreadTable.ReadStatus.FORCED_UNREAD,
|
||||
dontNotifyForMentionsIfMuted = RecipientTable.MentionSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING)
|
||||
dontNotifyForMentionsIfMuted = RecipientTable.MentionSetting.DO_NOT_NOTIFY.id == cursor.requireInt(RecipientTable.MENTION_SETTING),
|
||||
style = chatStyleBuilder.build()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,3 +147,79 @@ class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChatStyle.parseChatColor(): ChatColors? {
|
||||
if (bubbleColorPreset != null) {
|
||||
return when (bubbleColorPreset) {
|
||||
ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON
|
||||
ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION
|
||||
ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP
|
||||
ChatStyle.BubbleColorPreset.SOLID_FOREST -> ChatColorsPalette.Bubbles.FOREST
|
||||
ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN -> ChatColorsPalette.Bubbles.WINTERGREEN
|
||||
ChatStyle.BubbleColorPreset.SOLID_TEAL -> ChatColorsPalette.Bubbles.TEAL
|
||||
ChatStyle.BubbleColorPreset.SOLID_BLUE -> ChatColorsPalette.Bubbles.BLUE
|
||||
ChatStyle.BubbleColorPreset.SOLID_INDIGO -> ChatColorsPalette.Bubbles.INDIGO
|
||||
ChatStyle.BubbleColorPreset.SOLID_VIOLET -> ChatColorsPalette.Bubbles.VIOLET
|
||||
ChatStyle.BubbleColorPreset.SOLID_PLUM -> ChatColorsPalette.Bubbles.PLUM
|
||||
ChatStyle.BubbleColorPreset.SOLID_TAUPE -> ChatColorsPalette.Bubbles.TAUPE
|
||||
ChatStyle.BubbleColorPreset.SOLID_STEEL -> ChatColorsPalette.Bubbles.STEEL
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_EMBER -> ChatColorsPalette.Bubbles.EMBER
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT -> ChatColorsPalette.Bubbles.MIDNIGHT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_INFRARED -> ChatColorsPalette.Bubbles.INFRARED
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_LAGOON -> ChatColorsPalette.Bubbles.LAGOON
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT -> ChatColorsPalette.Bubbles.FLUORESCENT
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_BASIL -> ChatColorsPalette.Bubbles.BASIL
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME -> ChatColorsPalette.Bubbles.SUBLIME
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_SEA -> ChatColorsPalette.Bubbles.SEA
|
||||
ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE -> ChatColorsPalette.Bubbles.TANGERINE
|
||||
ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE
|
||||
}
|
||||
}
|
||||
if (autoBubbleColor != null) {
|
||||
return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto)
|
||||
}
|
||||
if (bubbleSolidColor != null) {
|
||||
return ChatColors(id = ChatColors.Id.NotSet, singleColor = bubbleSolidColor, linearGradient = null)
|
||||
}
|
||||
if (bubbleGradient != null) {
|
||||
return ChatColors(
|
||||
id = ChatColors.Id.NotSet,
|
||||
singleColor = null,
|
||||
linearGradient = ChatColors.LinearGradient(
|
||||
degrees = bubbleGradient.angle.toFloat(),
|
||||
colors = bubbleGradient.colors.toIntArray(),
|
||||
positions = floatArrayOf(0f, 1f)
|
||||
)
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun ChatColors.tryToMapToBackupPreset(): ChatStyle.BubbleColorPreset? {
|
||||
when (this) {
|
||||
// Solids
|
||||
ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON
|
||||
ChatColorsPalette.Bubbles.VERMILION -> return ChatStyle.BubbleColorPreset.SOLID_VERMILION
|
||||
ChatColorsPalette.Bubbles.BURLAP -> return ChatStyle.BubbleColorPreset.SOLID_BURLAP
|
||||
ChatColorsPalette.Bubbles.FOREST -> return ChatStyle.BubbleColorPreset.SOLID_FOREST
|
||||
ChatColorsPalette.Bubbles.WINTERGREEN -> return ChatStyle.BubbleColorPreset.SOLID_WINTERGREEN
|
||||
ChatColorsPalette.Bubbles.TEAL -> return ChatStyle.BubbleColorPreset.SOLID_TEAL
|
||||
ChatColorsPalette.Bubbles.BLUE -> return ChatStyle.BubbleColorPreset.SOLID_BLUE
|
||||
ChatColorsPalette.Bubbles.INDIGO -> return ChatStyle.BubbleColorPreset.SOLID_INDIGO
|
||||
ChatColorsPalette.Bubbles.VIOLET -> return ChatStyle.BubbleColorPreset.SOLID_VIOLET
|
||||
ChatColorsPalette.Bubbles.PLUM -> return ChatStyle.BubbleColorPreset.SOLID_PLUM
|
||||
ChatColorsPalette.Bubbles.TAUPE -> return ChatStyle.BubbleColorPreset.SOLID_TAUPE
|
||||
ChatColorsPalette.Bubbles.STEEL -> return ChatStyle.BubbleColorPreset.SOLID_STEEL
|
||||
// Gradients
|
||||
ChatColorsPalette.Bubbles.EMBER -> return ChatStyle.BubbleColorPreset.GRADIENT_EMBER
|
||||
ChatColorsPalette.Bubbles.MIDNIGHT -> return ChatStyle.BubbleColorPreset.GRADIENT_MIDNIGHT
|
||||
ChatColorsPalette.Bubbles.INFRARED -> return ChatStyle.BubbleColorPreset.GRADIENT_INFRARED
|
||||
ChatColorsPalette.Bubbles.LAGOON -> return ChatStyle.BubbleColorPreset.GRADIENT_LAGOON
|
||||
ChatColorsPalette.Bubbles.FLUORESCENT -> return ChatStyle.BubbleColorPreset.GRADIENT_FLUORESCENT
|
||||
ChatColorsPalette.Bubbles.BASIL -> return ChatStyle.BubbleColorPreset.GRADIENT_BASIL
|
||||
ChatColorsPalette.Bubbles.SUBLIME -> return ChatStyle.BubbleColorPreset.GRADIENT_SUBLIME
|
||||
ChatColorsPalette.Bubbles.SEA -> return ChatStyle.BubbleColorPreset.GRADIENT_SEA
|
||||
ChatColorsPalette.Bubbles.TANGERINE -> return ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -125,6 +125,13 @@ class ChatColors(
|
||||
|
||||
fun withId(id: Id): ChatColors = ChatColors(id, linearGradient, singleColor)
|
||||
|
||||
fun matchesWithoutId(other: ChatColors): Boolean {
|
||||
if (linearGradient != other.linearGradient) return false
|
||||
if (singleColor != other.singleColor) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val otherChatColors: ChatColors = (other as? ChatColors) ?: return false
|
||||
|
||||
|
||||
@@ -162,6 +162,9 @@ object ChatColorsPalette {
|
||||
@JvmStatic
|
||||
val default = ULTRAMARINE
|
||||
|
||||
/*
|
||||
* If updating this list of colors, make sure to update the backup import/export colors as well.
|
||||
*/
|
||||
val solids = listOf(
|
||||
CRIMSON,
|
||||
VERMILION,
|
||||
|
||||
Reference in New Issue
Block a user