mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-27 06:29:54 +00:00
Add notification profile and chat folder backupv2 proto support.
This commit is contained in:
committed by
Greyson Parrelli
parent
c91123e8e8
commit
d1bfa6ee9e
@@ -156,6 +156,14 @@ object ArchiveUploadProgress {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Sticker)
|
||||
}
|
||||
|
||||
override fun onNotificationProfile() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.NotificationProfile)
|
||||
}
|
||||
|
||||
override fun onChatFolder() {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.ChatFolder)
|
||||
}
|
||||
|
||||
override fun onMessage(currentProgress: Long, approximateCount: Long) {
|
||||
updatePhase(ArchiveUploadProgressState.BackupPhase.Message, currentProgress, approximateCount)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ import org.thoughtcrime.securesms.backup.v2.importer.ChatItemArchiveImporter
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.AccountDataArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.AdHocCallArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.ChatArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.ChatFolderProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.ChatItemArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.NotificationProfileProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.RecipientArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.processor.StickerArchiveProcessor
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
|
||||
@@ -576,6 +578,28 @@ object BackupRepository {
|
||||
return@export
|
||||
}
|
||||
|
||||
progressEmitter?.onNotificationProfile()
|
||||
NotificationProfileProcessor.export(dbSnapshot, exportState) { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("notification-profile")
|
||||
frameCount++
|
||||
}
|
||||
if (cancellationSignal()) {
|
||||
Log.w(TAG, "[export] Cancelled! Stopping")
|
||||
return@export
|
||||
}
|
||||
|
||||
progressEmitter?.onChatFolder()
|
||||
ChatFolderProcessor.export(dbSnapshot, exportState) { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("chat-folder")
|
||||
frameCount++
|
||||
}
|
||||
if (cancellationSignal()) {
|
||||
Log.w(TAG, "[export] Cancelled! Stopping")
|
||||
return@export
|
||||
}
|
||||
|
||||
val approximateMessageCount = dbSnapshot.messageTable.getApproximateExportableMessageCount(exportState.threadIds)
|
||||
val frameCountStart = frameCount
|
||||
progressEmitter?.onMessage(0, approximateMessageCount)
|
||||
@@ -727,9 +751,6 @@ object BackupRepository {
|
||||
SignalDatabase.recipients.setProfileKey(selfId, selfData.profileKey)
|
||||
SignalDatabase.recipients.setProfileSharing(selfId, true)
|
||||
|
||||
// Add back default All Chats chat folder after clearing data
|
||||
SignalDatabase.chatFolders.insertAllChatFolder()
|
||||
|
||||
val importState = ImportState(messageBackupKey, mediaRootBackupKey)
|
||||
val chatItemInserter: ChatItemArchiveImporter = ChatItemArchiveProcessor.beginImport(importState)
|
||||
|
||||
@@ -768,6 +789,18 @@ object BackupRepository {
|
||||
frameCount++
|
||||
}
|
||||
|
||||
frame.notificationProfile != null -> {
|
||||
NotificationProfileProcessor.import(frame.notificationProfile, importState)
|
||||
eventTimer.emit("notification-profile")
|
||||
frameCount++
|
||||
}
|
||||
|
||||
frame.chatFolder != null -> {
|
||||
ChatFolderProcessor.import(frame.chatFolder, importState)
|
||||
eventTimer.emit("chat-folder")
|
||||
frameCount++
|
||||
}
|
||||
|
||||
frame.chatItem != null -> {
|
||||
chatItemInserter.import(frame.chatItem)
|
||||
eventTimer.emit("chatItem")
|
||||
@@ -791,6 +824,11 @@ object BackupRepository {
|
||||
eventTimer.emit("chatItem")
|
||||
}
|
||||
|
||||
if (!importState.importedChatFolders) {
|
||||
// Add back default All Chats chat folder after clearing data if missing
|
||||
SignalDatabase.chatFolders.insertAllChatFolder()
|
||||
}
|
||||
|
||||
stopwatch.split("frames")
|
||||
|
||||
Log.d(TAG, "[import] Rebuilding FTS index...")
|
||||
@@ -1449,6 +1487,8 @@ object BackupRepository {
|
||||
fun onThread()
|
||||
fun onCall()
|
||||
fun onSticker()
|
||||
fun onNotificationProfile()
|
||||
fun onChatFolder()
|
||||
fun onMessage(currentProgress: Long, approximateCount: Long)
|
||||
fun onAttachment(currentProgress: Long, totalCount: Long)
|
||||
}
|
||||
@@ -1477,10 +1517,20 @@ class ImportState(val messageBackupKey: MessageBackupKey, val mediaRootBackupKey
|
||||
val chatIdToLocalRecipientId: MutableMap<Long, RecipientId> = hashMapOf()
|
||||
val chatIdToBackupRecipientId: MutableMap<Long, Long> = hashMapOf()
|
||||
val remoteToLocalColorId: MutableMap<Long, Long> = hashMapOf()
|
||||
val recipientIdToLocalThreadId: MutableMap<RecipientId, Long> = hashMapOf()
|
||||
val recipientIdToIsGroup: MutableMap<RecipientId, Boolean> = hashMapOf()
|
||||
|
||||
private var chatFolderPosition: Int = 0
|
||||
val importedChatFolders: Boolean
|
||||
get() = chatFolderPosition > 0
|
||||
|
||||
fun requireLocalRecipientId(remoteId: Long): RecipientId {
|
||||
return remoteToLocalRecipientId[remoteId] ?: throw IllegalArgumentException("There is no local recipientId for remote recipientId $remoteId!")
|
||||
}
|
||||
|
||||
fun getNextChatFolderPosition(): Int {
|
||||
return chatFolderPosition++
|
||||
}
|
||||
}
|
||||
|
||||
class BackupMetadata(
|
||||
|
||||
@@ -12,6 +12,8 @@ class LocalBackupV2Event(val type: Type, val count: Long = 0, val estimatedTotal
|
||||
PROGRESS_THREAD,
|
||||
PROGRESS_CALL,
|
||||
PROGRESS_STICKER,
|
||||
NOTIFICATION_PROFILE,
|
||||
CHAT_FOLDER,
|
||||
PROGRESS_MESSAGE,
|
||||
PROGRESS_ATTACHMENT,
|
||||
PROGRESS_VERIFYING,
|
||||
|
||||
@@ -169,6 +169,14 @@ object LocalArchiver {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_STICKER))
|
||||
}
|
||||
|
||||
override fun onNotificationProfile() {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.NOTIFICATION_PROFILE))
|
||||
}
|
||||
|
||||
override fun onChatFolder() {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.CHAT_FOLDER))
|
||||
}
|
||||
|
||||
override fun onMessage(currentProgress: Long, approximateCount: Long) {
|
||||
if (currentProgress == 0L) {
|
||||
EventBus.getDefault().post(LocalBackupV2Event(LocalBackupV2Event.Type.PROGRESS_MESSAGE))
|
||||
|
||||
@@ -46,5 +46,6 @@ object ChatArchiveProcessor {
|
||||
importState.chatIdToLocalRecipientId[chat.id] = recipientId
|
||||
importState.chatIdToLocalThreadId[chat.id] = threadId
|
||||
importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId
|
||||
importState.recipientIdToLocalThreadId[recipientId] = threadId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.processor
|
||||
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportState
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord
|
||||
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderMembershipTable
|
||||
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderTable
|
||||
import org.thoughtcrime.securesms.database.ChatFolderTables.MembershipType
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder as ChatFolderProto
|
||||
|
||||
/**
|
||||
* Handles exporting and importing [ChatFolderRecord]s.
|
||||
*/
|
||||
object ChatFolderProcessor {
|
||||
|
||||
private val TAG = Log.tag(ChatFolderProcessor::class)
|
||||
|
||||
fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) {
|
||||
val folders = db
|
||||
.chatFoldersTable
|
||||
.getChatFolders()
|
||||
.sortedBy { it.position }
|
||||
|
||||
if (folders.isEmpty()) {
|
||||
Log.d(TAG, "No chat folders, nothing to export")
|
||||
return
|
||||
}
|
||||
|
||||
if (folders.size == 1 && folders[0].folderType == ChatFolderRecord.FolderType.ALL) {
|
||||
Log.d(TAG, "Only ALL chat folder present, skipping chat folder export")
|
||||
return
|
||||
}
|
||||
|
||||
if (folders.none { it.folderType == ChatFolderRecord.FolderType.ALL }) {
|
||||
Log.w(TAG, "Missing ALL chat folder, exporting as first position")
|
||||
emitter.emit(ChatFolderRecord.getAllChatsFolderForBackup().toBackupFrame(emptyList(), emptyList()))
|
||||
}
|
||||
|
||||
folders.forEach { folder ->
|
||||
val includedRecipientIds = folder
|
||||
.includedChats
|
||||
.map { db.threadTable.getRecipientIdForThreadId(it)!!.toLong() }
|
||||
.filter { exportState.recipientIds.contains(it) }
|
||||
|
||||
val excludedRecipientIds = folder
|
||||
.excludedChats
|
||||
.map { db.threadTable.getRecipientIdForThreadId(it)!!.toLong() }
|
||||
.filter { exportState.recipientIds.contains(it) }
|
||||
|
||||
val frame = folder.toBackupFrame(includedRecipientIds, excludedRecipientIds)
|
||||
emitter.emit(frame)
|
||||
}
|
||||
}
|
||||
|
||||
fun import(chatFolder: ChatFolderProto, importState: ImportState) {
|
||||
val chatFolderId = SignalDatabase
|
||||
.writableDatabase
|
||||
.insertInto(ChatFolderTable.TABLE_NAME)
|
||||
.values(
|
||||
ChatFolderTable.NAME to chatFolder.name,
|
||||
ChatFolderTable.POSITION to importState.getNextChatFolderPosition(),
|
||||
ChatFolderTable.SHOW_UNREAD to chatFolder.showOnlyUnread,
|
||||
ChatFolderTable.SHOW_MUTED to chatFolder.showMutedChats,
|
||||
ChatFolderTable.SHOW_INDIVIDUAL to chatFolder.includeAllIndividualChats,
|
||||
ChatFolderTable.SHOW_GROUPS to chatFolder.includeAllGroupChats,
|
||||
ChatFolderTable.FOLDER_TYPE to chatFolder.folderType.toLocal().value
|
||||
)
|
||||
.run()
|
||||
|
||||
if (chatFolderId < 0) {
|
||||
Log.w(TAG, "Chat folder already exists")
|
||||
return
|
||||
}
|
||||
|
||||
val includedChatsQueries = chatFolder.includedRecipientIds.toMembershipInsertQueries(chatFolderId, importState, MembershipType.INCLUDED)
|
||||
includedChatsQueries.forEach {
|
||||
SignalDatabase.writableDatabase.execSQL(it.where, it.whereArgs)
|
||||
}
|
||||
|
||||
val excludedChatsQueries = chatFolder.excludedRecipientIds.toMembershipInsertQueries(chatFolderId, importState, MembershipType.EXCLUDED)
|
||||
excludedChatsQueries.forEach {
|
||||
SignalDatabase.writableDatabase.execSQL(it.where, it.whereArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChatFolderRecord.toBackupFrame(includedRecipientIds: List<Long>, excludedRecipientIds: List<Long>): Frame {
|
||||
val chatFolder = ChatFolderProto(
|
||||
name = this.name,
|
||||
showOnlyUnread = this.showUnread,
|
||||
showMutedChats = this.showMutedChats,
|
||||
includeAllIndividualChats = this.showIndividualChats,
|
||||
includeAllGroupChats = this.showGroupChats,
|
||||
folderType = when (this.folderType) {
|
||||
ChatFolderRecord.FolderType.ALL -> ChatFolderProto.FolderType.ALL
|
||||
ChatFolderRecord.FolderType.CUSTOM -> ChatFolderProto.FolderType.CUSTOM
|
||||
else -> throw IllegalStateException("Only ALL or CUSTOM should be in the db")
|
||||
},
|
||||
includedRecipientIds = includedRecipientIds,
|
||||
excludedRecipientIds = excludedRecipientIds
|
||||
)
|
||||
|
||||
return Frame(chatFolder = chatFolder)
|
||||
}
|
||||
|
||||
private fun ChatFolderProto.FolderType.toLocal(): ChatFolderRecord.FolderType {
|
||||
return when (this) {
|
||||
ChatFolder.FolderType.UNKNOWN -> throw IllegalStateException()
|
||||
ChatFolder.FolderType.ALL -> ChatFolderRecord.FolderType.ALL
|
||||
ChatFolder.FolderType.CUSTOM -> ChatFolderRecord.FolderType.CUSTOM
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Long>.toMembershipInsertQueries(chatFolderId: Long, importState: ImportState, membershipType: MembershipType): List<SqlUtil.Query> {
|
||||
val values = this
|
||||
.mapNotNull { importState.remoteToLocalRecipientId[it] }
|
||||
.map { recipientId -> importState.recipientIdToLocalThreadId[recipientId] ?: SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, importState.recipientIdToIsGroup[recipientId] == true) }
|
||||
.map { threadId ->
|
||||
contentValuesOf(
|
||||
ChatFolderMembershipTable.CHAT_FOLDER_ID to chatFolderId,
|
||||
ChatFolderMembershipTable.THREAD_ID to threadId,
|
||||
ChatFolderMembershipTable.MEMBERSHIP_TYPE to membershipType.value
|
||||
)
|
||||
}
|
||||
|
||||
return SqlUtil.buildBulkInsert(
|
||||
ChatFolderMembershipTable.TABLE_NAME,
|
||||
arrayOf(ChatFolderMembershipTable.CHAT_FOLDER_ID, ChatFolderMembershipTable.THREAD_ID, ChatFolderMembershipTable.MEMBERSHIP_TYPE),
|
||||
values
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.processor
|
||||
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.toInt
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportState
|
||||
import org.thoughtcrime.securesms.backup.v2.ImportState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileAllowedMembersTable
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileScheduleTable
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.serialize
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import java.lang.IllegalStateException
|
||||
import java.time.DayOfWeek
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.NotificationProfile as NotificationProfileProto
|
||||
|
||||
/**
|
||||
* Handles exporting and importing [NotificationProfile] models.
|
||||
*/
|
||||
object NotificationProfileProcessor {
|
||||
|
||||
private val TAG = Log.tag(NotificationProfileProcessor::class)
|
||||
|
||||
fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) {
|
||||
db.notificationProfileTables
|
||||
.getProfiles()
|
||||
.forEach { profile ->
|
||||
val frame = profile.toBackupFrame(includeRecipient = { id -> exportState.recipientIds.contains(id.toLong()) })
|
||||
emitter.emit(frame)
|
||||
}
|
||||
}
|
||||
|
||||
fun import(profile: NotificationProfileProto, importState: ImportState) {
|
||||
val profileId = SignalDatabase
|
||||
.writableDatabase
|
||||
.insertInto(NotificationProfileTable.TABLE_NAME)
|
||||
.values(
|
||||
NotificationProfileTable.NAME to profile.name,
|
||||
NotificationProfileTable.EMOJI to (profile.emoji ?: ""),
|
||||
NotificationProfileTable.COLOR to (AvatarColor.fromColor(profile.color) ?: AvatarColor.random()).serialize(),
|
||||
NotificationProfileTable.CREATED_AT to profile.createdAtMs,
|
||||
NotificationProfileTable.ALLOW_ALL_CALLS to profile.allowAllCalls.toInt(),
|
||||
NotificationProfileTable.ALLOW_ALL_MENTIONS to profile.allowAllMentions.toInt()
|
||||
)
|
||||
.run()
|
||||
|
||||
if (profileId < 0) {
|
||||
Log.w(TAG, "Notification profile name already exists")
|
||||
return
|
||||
}
|
||||
|
||||
SignalDatabase
|
||||
.writableDatabase
|
||||
.insertInto(NotificationProfileScheduleTable.TABLE_NAME)
|
||||
.values(
|
||||
NotificationProfileScheduleTable.NOTIFICATION_PROFILE_ID to profileId,
|
||||
NotificationProfileScheduleTable.ENABLED to profile.scheduleEnabled.toInt(),
|
||||
NotificationProfileScheduleTable.START to profile.scheduleStartTime,
|
||||
NotificationProfileScheduleTable.END to profile.scheduleEndTime,
|
||||
NotificationProfileScheduleTable.DAYS_ENABLED to profile.scheduleDaysEnabled.map { it.toLocal() }.toSet().serialize()
|
||||
)
|
||||
.run()
|
||||
|
||||
profile
|
||||
.allowedMembers
|
||||
.mapNotNull { importState.remoteToLocalRecipientId[it] }
|
||||
.forEach { recipientId ->
|
||||
SignalDatabase
|
||||
.writableDatabase
|
||||
.insertInto(NotificationProfileAllowedMembersTable.TABLE_NAME)
|
||||
.values(
|
||||
NotificationProfileAllowedMembersTable.NOTIFICATION_PROFILE_ID to profileId,
|
||||
NotificationProfileAllowedMembersTable.RECIPIENT_ID to recipientId.serialize()
|
||||
)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun NotificationProfile.toBackupFrame(includeRecipient: (RecipientId) -> Boolean): Frame {
|
||||
val profile = NotificationProfileProto(
|
||||
name = this.name,
|
||||
emoji = this.emoji.takeIf { it.isNotBlank() },
|
||||
color = this.color.colorInt(),
|
||||
createdAtMs = this.createdAt,
|
||||
allowAllCalls = this.allowAllCalls,
|
||||
allowAllMentions = this.allowAllMentions,
|
||||
allowedMembers = this.allowedMembers.filter { includeRecipient(it) }.map { it.toLong() },
|
||||
scheduleEnabled = this.schedule.enabled,
|
||||
scheduleStartTime = this.schedule.start,
|
||||
scheduleEndTime = this.schedule.end,
|
||||
scheduleDaysEnabled = this.schedule.daysEnabled.map { it.toBackupProto() }
|
||||
)
|
||||
|
||||
return Frame(notificationProfile = profile)
|
||||
}
|
||||
|
||||
private fun DayOfWeek.toBackupProto(): NotificationProfileProto.DayOfWeek {
|
||||
return when (this) {
|
||||
DayOfWeek.MONDAY -> NotificationProfileProto.DayOfWeek.MONDAY
|
||||
DayOfWeek.TUESDAY -> NotificationProfileProto.DayOfWeek.TUESDAY
|
||||
DayOfWeek.WEDNESDAY -> NotificationProfileProto.DayOfWeek.WEDNESDAY
|
||||
DayOfWeek.THURSDAY -> NotificationProfileProto.DayOfWeek.THURSDAY
|
||||
DayOfWeek.FRIDAY -> NotificationProfileProto.DayOfWeek.FRIDAY
|
||||
DayOfWeek.SATURDAY -> NotificationProfileProto.DayOfWeek.SATURDAY
|
||||
DayOfWeek.SUNDAY -> NotificationProfileProto.DayOfWeek.SUNDAY
|
||||
}
|
||||
}
|
||||
|
||||
private fun NotificationProfileProto.DayOfWeek.toLocal(): DayOfWeek {
|
||||
return when (this) {
|
||||
NotificationProfileProto.DayOfWeek.UNKNOWN -> throw IllegalStateException()
|
||||
NotificationProfileProto.DayOfWeek.MONDAY -> DayOfWeek.MONDAY
|
||||
NotificationProfileProto.DayOfWeek.TUESDAY -> DayOfWeek.TUESDAY
|
||||
NotificationProfileProto.DayOfWeek.WEDNESDAY -> DayOfWeek.WEDNESDAY
|
||||
NotificationProfileProto.DayOfWeek.THURSDAY -> DayOfWeek.THURSDAY
|
||||
NotificationProfileProto.DayOfWeek.FRIDAY -> DayOfWeek.FRIDAY
|
||||
NotificationProfileProto.DayOfWeek.SATURDAY -> DayOfWeek.SATURDAY
|
||||
NotificationProfileProto.DayOfWeek.SUNDAY -> DayOfWeek.SUNDAY
|
||||
}
|
||||
}
|
||||
@@ -49,4 +49,15 @@ data class ChatFolderRecord(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getAllChatsFolderForBackup(): ChatFolderRecord {
|
||||
return ChatFolderRecord(
|
||||
folderType = ChatFolderRecord.FolderType.ALL,
|
||||
showIndividualChats = true,
|
||||
showGroupChats = true,
|
||||
showMutedChats = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileDatabase
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileTables
|
||||
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
|
||||
|
||||
class EditNotificationProfileViewModel(private val profileId: Long, private val repository: NotificationProfilesRepository) : ViewModel() {
|
||||
@@ -34,8 +34,8 @@ class EditNotificationProfileViewModel(private val profileId: Long, private val
|
||||
|
||||
return save.map { r ->
|
||||
when (r) {
|
||||
is NotificationProfileDatabase.NotificationProfileChangeResult.Success -> SaveNotificationProfileResult.Success(r.notificationProfile, createMode)
|
||||
NotificationProfileDatabase.NotificationProfileChangeResult.DuplicateName -> SaveNotificationProfileResult.DuplicateNameFailure
|
||||
is NotificationProfileTables.NotificationProfileChangeResult.Success -> SaveNotificationProfileResult.Success(r.notificationProfile, createMode)
|
||||
NotificationProfileTables.NotificationProfileChangeResult.DuplicateName -> SaveNotificationProfileResult.DuplicateNameFailure
|
||||
}
|
||||
}.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileDatabase
|
||||
import org.thoughtcrime.securesms.database.NotificationProfileTables
|
||||
import org.thoughtcrime.securesms.database.RxDatabaseObserver
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.util.toMillis
|
||||
* One stop shop for all your Notification Profile data needs.
|
||||
*/
|
||||
class NotificationProfilesRepository {
|
||||
private val database: NotificationProfileDatabase = SignalDatabase.notificationProfiles
|
||||
private val database: NotificationProfileTables = SignalDatabase.notificationProfiles
|
||||
|
||||
fun getProfiles(): Flowable<List<NotificationProfile>> {
|
||||
return RxDatabaseObserver
|
||||
@@ -54,17 +54,17 @@ class NotificationProfilesRepository {
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun createProfile(name: String, selectedEmoji: String): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
|
||||
fun createProfile(name: String, selectedEmoji: String): Single<NotificationProfileTables.NotificationProfileChangeResult> {
|
||||
return Single.fromCallable { database.createProfile(name = name, emoji = selectedEmoji, color = AvatarColor.random(), createdAt = System.currentTimeMillis()) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun updateProfile(profileId: Long, name: String, selectedEmoji: String): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
|
||||
fun updateProfile(profileId: Long, name: String, selectedEmoji: String): Single<NotificationProfileTables.NotificationProfileChangeResult> {
|
||||
return Single.fromCallable { database.updateProfile(profileId = profileId, name = name, emoji = selectedEmoji) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun updateProfile(profile: NotificationProfile): Single<NotificationProfileDatabase.NotificationProfileChangeResult> {
|
||||
fun updateProfile(profile: NotificationProfile): Single<NotificationProfileTables.NotificationProfileChangeResult> {
|
||||
return Single.fromCallable { database.updateProfile(profile) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class NotificationProfilesRepository {
|
||||
.take(1)
|
||||
.singleOrError()
|
||||
.flatMap { updateProfile(it.copy(allowAllMentions = !it.allowAllMentions)) }
|
||||
.map { (it as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile }
|
||||
.map { (it as NotificationProfileTables.NotificationProfileChangeResult.Success).notificationProfile }
|
||||
}
|
||||
|
||||
fun toggleAllowAllCalls(profileId: Long): Single<NotificationProfile> {
|
||||
@@ -107,7 +107,7 @@ class NotificationProfilesRepository {
|
||||
.take(1)
|
||||
.singleOrError()
|
||||
.flatMap { updateProfile(it.copy(allowAllCalls = !it.allowAllCalls)) }
|
||||
.map { (it as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile }
|
||||
.map { (it as NotificationProfileTables.NotificationProfileChangeResult.Success).notificationProfile }
|
||||
}
|
||||
|
||||
fun manuallyToggleProfile(profile: NotificationProfile, now: Long = System.currentTimeMillis()): Completable {
|
||||
|
||||
@@ -4,9 +4,11 @@ import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A serializable set of color constants that can be used for avatars.
|
||||
@@ -27,8 +29,11 @@ public enum AvatarColor {
|
||||
UNKNOWN("UNKNOWN", 0x00000000),
|
||||
ON_SURFACE_VARIANT("ON_SURFACE_VARIANT", 0x00000000);
|
||||
|
||||
/** Fast map of name to enum, while also giving us a location to map old colors to new ones. */
|
||||
/**
|
||||
* Fast map of name to enum, while also giving us a location to map old colors to new ones.
|
||||
*/
|
||||
private static final Map<String, AvatarColor> NAME_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (AvatarColor color : AvatarColor.values()) {
|
||||
NAME_MAP.put(color.serialize(), color);
|
||||
@@ -97,7 +102,9 @@ public enum AvatarColor {
|
||||
NAME_MAP.put("grey", A210);
|
||||
}
|
||||
|
||||
/** Colors that can be assigned via {@link #random()}. */
|
||||
/**
|
||||
* Colors that can be assigned via {@link #random()}.
|
||||
*/
|
||||
static final AvatarColor[] RANDOM_OPTIONS = new AvatarColor[] {
|
||||
A100,
|
||||
A110,
|
||||
@@ -137,4 +144,11 @@ public enum AvatarColor {
|
||||
public static @NonNull AvatarColor deserialize(@Nullable String name) {
|
||||
return Objects.requireNonNull(NAME_MAP.getOrDefault(name, A210));
|
||||
}
|
||||
|
||||
public static @Nullable AvatarColor fromColor(@ColorInt int color) {
|
||||
return Arrays.stream(values())
|
||||
.filter(c -> c.color == color)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.time.DayOfWeek
|
||||
/**
|
||||
* Database for maintaining Notification Profiles, Notification Profile Schedules, and Notification Profile allowed memebers.
|
||||
*/
|
||||
class NotificationProfileDatabase(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
|
||||
class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
@@ -32,7 +32,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
|
||||
val CREATE_INDEXES: Array<String> = arrayOf(NotificationProfileScheduleTable.CREATE_INDEX, NotificationProfileAllowedMembersTable.CREATE_INDEX)
|
||||
}
|
||||
|
||||
private object NotificationProfileTable {
|
||||
object NotificationProfileTable {
|
||||
const val TABLE_NAME = "notification_profile"
|
||||
|
||||
const val ID = "_id"
|
||||
@@ -56,7 +56,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
|
||||
"""
|
||||
}
|
||||
|
||||
private object NotificationProfileScheduleTable {
|
||||
object NotificationProfileScheduleTable {
|
||||
const val TABLE_NAME = "notification_profile_schedule"
|
||||
|
||||
const val ID = "_id"
|
||||
@@ -82,7 +82,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
|
||||
const val CREATE_INDEX = "CREATE INDEX notification_profile_schedule_profile_index ON $TABLE_NAME ($NOTIFICATION_PROFILE_ID)"
|
||||
}
|
||||
|
||||
private object NotificationProfileAllowedMembersTable {
|
||||
object NotificationProfileAllowedMembersTable {
|
||||
const val TABLE_NAME = "notification_profile_allowed_members"
|
||||
|
||||
const val ID = "_id"
|
||||
@@ -367,7 +367,7 @@ class NotificationProfileDatabase(context: Context, databaseHelper: SignalDataba
|
||||
}
|
||||
}
|
||||
|
||||
private fun Iterable<DayOfWeek>.serialize(): String {
|
||||
fun Iterable<DayOfWeek>.serialize(): String {
|
||||
return joinToString(separator = ",", transform = { it.serialize() })
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
val messageSendLogTables: MessageSendLogTables = MessageSendLogTables(context, this)
|
||||
val avatarPickerDatabase: AvatarPickerDatabase = AvatarPickerDatabase(context, this)
|
||||
val reactionTable: ReactionTable = ReactionTable(context, this)
|
||||
val notificationProfileDatabase: NotificationProfileDatabase = NotificationProfileDatabase(context, this)
|
||||
val notificationProfileTables: NotificationProfileTables = NotificationProfileTables(context, this)
|
||||
val donationReceiptTable: DonationReceiptTable = DonationReceiptTable(context, this)
|
||||
val distributionListTables: DistributionListTables = DistributionListTables(context, this)
|
||||
val storySendTable: StorySendTable = StorySendTable(context, this)
|
||||
@@ -120,7 +120,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
executeStatements(db, SearchTable.CREATE_TABLE)
|
||||
executeStatements(db, RemappedRecordTables.CREATE_TABLE)
|
||||
executeStatements(db, MessageSendLogTables.CREATE_TABLE)
|
||||
executeStatements(db, NotificationProfileDatabase.CREATE_TABLE)
|
||||
executeStatements(db, NotificationProfileTables.CREATE_TABLE)
|
||||
executeStatements(db, DistributionListTables.CREATE_TABLE)
|
||||
executeStatements(db, ChatFolderTables.CREATE_TABLE)
|
||||
db.execSQL(BackupMediaSnapshotTable.CREATE_TABLE)
|
||||
@@ -137,7 +137,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
executeStatements(db, MentionTable.CREATE_INDEXES)
|
||||
executeStatements(db, PaymentTable.CREATE_INDEXES)
|
||||
executeStatements(db, MessageSendLogTables.CREATE_INDEXES)
|
||||
executeStatements(db, NotificationProfileDatabase.CREATE_INDEXES)
|
||||
executeStatements(db, NotificationProfileTables.CREATE_INDEXES)
|
||||
executeStatements(db, DonationReceiptTable.CREATE_INDEXS)
|
||||
executeStatements(db, StorySendTable.CREATE_INDEXS)
|
||||
executeStatements(db, DistributionListTables.CREATE_INDEXES)
|
||||
@@ -456,8 +456,8 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("notificationProfiles")
|
||||
val notificationProfiles: NotificationProfileDatabase
|
||||
get() = instance!!.notificationProfileDatabase
|
||||
val notificationProfiles: NotificationProfileTables
|
||||
get() = instance!!.notificationProfileTables
|
||||
|
||||
@get:JvmStatic
|
||||
@get:JvmName("payments")
|
||||
|
||||
Reference in New Issue
Block a user