Added a setting to control community message request polling
Added logic to broadcast the community message request acceptance to SOGS so we can communicate it to message request senders Fixed an issue where database setting changes wouldn't trigger a live update on a settings screen Fixed an issue where some setting toggles wouldn't animate the state change Fixed a rarw force-unwrap crash
This commit is contained in:
parent
4d098914b2
commit
d863004e6d
|
@ -1 +1 @@
|
|||
Subproject commit d8f07fa92c12c5c2409774e03e03395d7847d1c2
|
||||
Subproject commit e3ccf29db08aaf0b9bb6bbe72ae5967cd183a78d
|
|
@ -531,6 +531,8 @@
|
|||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; };
|
||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */; };
|
||||
FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; };
|
||||
FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */; };
|
||||
FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */; };
|
||||
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; };
|
||||
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; };
|
||||
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; };
|
||||
|
@ -1689,6 +1691,8 @@
|
|||
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistableRecordUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Setting+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _015_BlockCommunityMessageRequests.swift; sourceTree = "<group>"; };
|
||||
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; };
|
||||
FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
|
||||
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = "<group>"; };
|
||||
|
@ -3618,6 +3622,7 @@
|
|||
FD368A6729DE8F9B000DBF1E /* _012_AddFTSIfNeeded.swift */,
|
||||
FD8ECF7C2934293A00C0D1BB /* _013_SessionUtilChanges.swift */,
|
||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */,
|
||||
FD1D732D2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift */,
|
||||
);
|
||||
path = Migrations;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3763,6 +3768,7 @@
|
|||
FD2B4B022949886900AB4848 /* Database */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD1D73292A85AA2000E3F410 /* Setting+Utilities.swift */,
|
||||
FD2B4B032949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift */,
|
||||
);
|
||||
path = Database;
|
||||
|
@ -5914,12 +5920,14 @@
|
|||
C352A35B2557824E00338F3E /* AttachmentUploadJob.swift in Sources */,
|
||||
FD2959922A4417A900888A17 /* PreparedSendData.swift in Sources */,
|
||||
FD5C7305284F0FF30029977D /* MessageReceiver+VisibleMessages.swift in Sources */,
|
||||
FD1D732E2A86114600E3F410 /* _015_BlockCommunityMessageRequests.swift in Sources */,
|
||||
FD432437299DEA38008A0213 /* TypeConversion+Utilities.swift in Sources */,
|
||||
FD2B4B042949887A00AB4848 /* QueryInterfaceRequest+Utilities.swift in Sources */,
|
||||
FD09797027FA6FF300936362 /* Profile.swift in Sources */,
|
||||
FD245C56285065EA00B966DD /* SNProto.swift in Sources */,
|
||||
FD09798B27FD1CFE00936362 /* Capability.swift in Sources */,
|
||||
C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */,
|
||||
FD1D732A2A85AA2000E3F410 /* Setting+Utilities.swift in Sources */,
|
||||
FD17D79C27F40B2E00122BE0 /* SMKLegacy.swift in Sources */,
|
||||
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
|
||||
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */,
|
||||
|
|
|
@ -208,17 +208,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
|||
}()
|
||||
|
||||
private lazy var emptyStateLabel: UILabel = {
|
||||
let text: String = String(
|
||||
format: {
|
||||
switch (viewModel.threadData.threadIsNoteToSelf, viewModel.threadData.canWrite) {
|
||||
case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized()
|
||||
case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized()
|
||||
default: return "CONVERSATION_EMPTY_STATE".localized()
|
||||
}
|
||||
}(),
|
||||
viewModel.threadData.displayName
|
||||
)
|
||||
|
||||
let text: String = emptyStateText(for: viewModel.threadData)
|
||||
let result: UILabel = UILabel()
|
||||
result.accessibilityLabel = "Empty state label"
|
||||
result.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -698,6 +688,24 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
|||
self.viewModel.onInteractionChange = nil
|
||||
}
|
||||
|
||||
private func emptyStateText(for threadData: SessionThreadViewModel) -> String {
|
||||
return String(
|
||||
format: {
|
||||
switch (threadData.threadIsNoteToSelf, threadData.canWrite) {
|
||||
case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized()
|
||||
case (_, false):
|
||||
return (threadData.profile?.blocksCommunityMessageRequests == true ?
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE".localized() :
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY".localized()
|
||||
)
|
||||
|
||||
default: return "CONVERSATION_EMPTY_STATE".localized()
|
||||
}
|
||||
}(),
|
||||
threadData.displayName
|
||||
)
|
||||
}
|
||||
|
||||
private func handleThreadUpdates(_ updatedThreadData: SessionThreadViewModel, initialLoad: Bool = false) {
|
||||
// Ensure the first load or a load when returning from a child screen runs without animations (if
|
||||
// we don't do this the cells will animate in from a frame of CGRect.zero or have a buggy transition)
|
||||
|
@ -738,17 +746,7 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
|||
)
|
||||
|
||||
// Update the empty state
|
||||
let text: String = String(
|
||||
format: {
|
||||
switch (updatedThreadData.threadIsNoteToSelf, updatedThreadData.canWrite) {
|
||||
case (true, _): return "CONVERSATION_EMPTY_STATE_NOTE_TO_SELF".localized()
|
||||
case (_, false): return "CONVERSATION_EMPTY_STATE_READ_ONLY".localized()
|
||||
default: return "CONVERSATION_EMPTY_STATE".localized()
|
||||
}
|
||||
}(),
|
||||
updatedThreadData.displayName
|
||||
)
|
||||
|
||||
let text: String = emptyStateText(for: updatedThreadData)
|
||||
emptyStateLabel.attributedText = NSAttributedString(string: text)
|
||||
.adding(
|
||||
attributes: [.font: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize)],
|
||||
|
@ -791,8 +789,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
|||
updatedThreadData.threadRequiresApproval == true
|
||||
)
|
||||
self?.messageRequestStackView.isHidden = (
|
||||
updatedThreadData.threadIsMessageRequest == false &&
|
||||
updatedThreadData.threadRequiresApproval == false
|
||||
!updatedThreadData.canWrite || (
|
||||
updatedThreadData.threadIsMessageRequest == false &&
|
||||
updatedThreadData.threadRequiresApproval == false
|
||||
)
|
||||
)
|
||||
self?.messageRequestBackgroundView.isHidden = (self?.messageRequestStackView.isHidden == true)
|
||||
self?.messageRequestDescriptionLabelBottomConstraint?.constant = (updatedThreadData.threadRequiresApproval == true ? -4 : -20)
|
||||
|
|
|
@ -178,6 +178,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
|
||||
// MARK: - Content
|
||||
|
||||
private var originalState: SessionThreadViewModel?
|
||||
override var title: String {
|
||||
switch threadVariant {
|
||||
case .contact: return "vc_settings_title".localized()
|
||||
|
@ -235,6 +236,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
threadViewModel.currentUserIsClosedGroupAdmin == true
|
||||
)
|
||||
let editIcon: UIImage? = UIImage(named: "icon_edit")
|
||||
let originalState: SessionThreadViewModel = (self?.originalState ?? threadViewModel)
|
||||
self?.originalState = threadViewModel
|
||||
|
||||
return [
|
||||
SectionModel(
|
||||
|
@ -577,7 +580,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
title: "vc_conversation_settings_notify_for_mentions_only_title".localized(),
|
||||
subtitle: "vc_conversation_settings_notify_for_mentions_only_explanation".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(threadViewModel.threadOnlyNotifyForMentions == true)
|
||||
.boolValue(
|
||||
threadViewModel.threadOnlyNotifyForMentions == true,
|
||||
oldValue: (originalState.threadOnlyNotifyForMentions == true)
|
||||
)
|
||||
),
|
||||
isEnabled: (
|
||||
(
|
||||
|
@ -615,7 +621,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
),
|
||||
title: "CONVERSATION_SETTINGS_MUTE_LABEL".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(threadViewModel.threadMutedUntilTimestamp != nil)
|
||||
.boolValue(
|
||||
threadViewModel.threadMutedUntilTimestamp != nil,
|
||||
oldValue: (originalState.threadMutedUntilTimestamp != nil)
|
||||
)
|
||||
),
|
||||
isEnabled: (
|
||||
(
|
||||
|
@ -661,7 +670,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
),
|
||||
title: "CONVERSATION_SETTINGS_BLOCK_THIS_USER".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(threadViewModel.threadIsBlocked == true)
|
||||
.boolValue(
|
||||
threadViewModel.threadIsBlocked == true,
|
||||
oldValue: (originalState.threadIsBlocked == true)
|
||||
)
|
||||
),
|
||||
accessibility: Accessibility(
|
||||
identifier: "\(ThreadSettingsViewModel.self).block",
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Bildschirmschutz";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Lesebestätigungen";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Lesebestätigungen";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Protección de pantalla";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Notificaciones de lectura";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Notificaciones de lectura";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "امنیت صفحه";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "قفل Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = " برای باز کردن قفل Session به شناسه لمسی، شناسه صورت و یا رمز عبوری ضرورت است.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "رسیدهای خواندن";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "رسیدهای خواندن";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "رسیدهای خواندن در چتهای یک به یک روان شود.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Näytön suojaus";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Lukukuittaukset";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Lukukuittaukset";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Sécurité de l’écran";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Verrouiller Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Requiert Touch ID, Face ID ou votre code pour déverrouiller Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Accusés de lecture";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Accusés de lecture";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Envoyer un accusé réception dans les conversations 1 à 1.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Sigurnost zaslona";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Potvrda o čitanju";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Potvrda o čitanju";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Layar Aman";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Pesan terbaca diterima";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Pesan terbaca diterima";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Sicurezza schermo";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Ricevute di lettura";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Ricevute di lettura";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "スクリーン・セキュリティ";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "既読確認";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "既読確認";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Scherm beveiliging";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Leesbevestigingen";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Leesbevestigingen";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Ochrona ekranu";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Potwierdzenia odczytania";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Potwierdzenia odczytania";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Segurança de Tela";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Confirmações de Leitura";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Confirmações de Leitura";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Защита экрана";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Уведомления о прочтении";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Уведомления о прочтении";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "තිරයේ ආරක්ෂාව";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "කියවූ බවට ලදුපත්";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "කියවූ බවට ලදුපත්";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Zabezpečenie obrazovky";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Potvrdenia o prečítaní";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Potvrdenia o prečítaní";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Skärmsäkerhet";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Läskvittenser";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Läskvittenser";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "ความปลอดภัยหน้าจอ";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "แจ้งการอ่านข้อความ";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "แจ้งการอ่านข้อความ";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "Screen Security";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "Read Receipts";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "螢幕顯示安全";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "已讀回條";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "已讀回條";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -483,6 +483,9 @@
|
|||
"PRIVACY_SECTION_SCREEN_SECURITY" = "屏幕安全";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE" = "Lock Session";
|
||||
"PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION" = "Require Touch ID, Face ID or your passcode to unlock Session.";
|
||||
"PRIVACY_SECTION_MESSAGE_REQUESTS" = "Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE" = "Community Message Requests";
|
||||
"PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION" = "Allow message requests from Community conversations.";
|
||||
"PRIVACY_SECTION_READ_RECEIPTS" = "已读回执";
|
||||
"PRIVACY_READ_RECEIPTS_TITLE" = "已读回执";
|
||||
"PRIVACY_READ_RECEIPTS_DESCRIPTION" = "Send read receipts in one-to-one chats.";
|
||||
|
@ -645,6 +648,7 @@
|
|||
"CONVERSATION_EMPTY_STATE" = "You have no messages from %@. Send a message to start the conversation!";
|
||||
"CONVERSATION_EMPTY_STATE_READ_ONLY" = "There are no messages in %@.";
|
||||
"CONVERSATION_EMPTY_STATE_NOTE_TO_SELF" = "You have no messages in %@.";
|
||||
"COMMUNITY_MESSAGE_REQUEST_DISABLED_EMPTY_STATE" = "%@ has message requests from Community conversations turned off, so you cannot send them a message.";
|
||||
"USER_CONFIG_OUTDATED_WARNING" = "Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.";
|
||||
"LOAD_RECOVERY_PASSWORD_ERROR" = "An error occurred when trying to load your recovery password.\n\nPlease export your logs, then upload the file though Session's Help Desk to help resolve this issue.";
|
||||
"FAILED_TO_STORE_OUTGOING_MESSAGE" = "An error occurred when trying to store the outgoing message for sending, you may need to restart the app before you can send messages.";
|
||||
|
|
|
@ -33,6 +33,11 @@ class ConversationSettingsViewModel: SessionTableViewModel<NoNav, ConversationSe
|
|||
|
||||
// MARK: - Content
|
||||
|
||||
private struct State: Equatable {
|
||||
let trimOpenGroupMessagesOlderThanSixMonths: Bool
|
||||
let shouldAutoPlayConsecutiveAudioMessages: Bool
|
||||
}
|
||||
|
||||
override var title: String { "CONVERSATION_SETTINGS_TITLE".localized() }
|
||||
|
||||
public override var observableTableData: ObservableData { _observableTableData }
|
||||
|
@ -45,7 +50,17 @@ class ConversationSettingsViewModel: SessionTableViewModel<NoNav, ConversationSe
|
|||
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
|
||||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { db -> [SectionModel] in
|
||||
.trackingConstantRegion { [weak self] db -> State in
|
||||
State(
|
||||
trimOpenGroupMessagesOlderThanSixMonths: db[.trimOpenGroupMessagesOlderThanSixMonths],
|
||||
shouldAutoPlayConsecutiveAudioMessages: db[.shouldAutoPlayConsecutiveAudioMessages]
|
||||
)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[ConversationSettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.withPrevious()
|
||||
.map { (previous: State?, current: State) -> [SectionModel] in
|
||||
return [
|
||||
SectionModel(
|
||||
model: .messageTrimming,
|
||||
|
@ -55,7 +70,11 @@ class ConversationSettingsViewModel: SessionTableViewModel<NoNav, ConversationSe
|
|||
title: "CONVERSATION_SETTINGS_MESSAGE_TRIMMING_TITLE".localized(),
|
||||
subtitle: "CONVERSATION_SETTINGS_MESSAGE_TRIMMING_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.settingBool(key: .trimOpenGroupMessagesOlderThanSixMonths)
|
||||
.boolValue(
|
||||
key: .trimOpenGroupMessagesOlderThanSixMonths,
|
||||
value: current.trimOpenGroupMessagesOlderThanSixMonths,
|
||||
oldValue: (previous ?? current).trimOpenGroupMessagesOlderThanSixMonths
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
|
@ -73,7 +92,11 @@ class ConversationSettingsViewModel: SessionTableViewModel<NoNav, ConversationSe
|
|||
title: "CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_TITLE".localized(),
|
||||
subtitle: "CONVERSATION_SETTINGS_AUDIO_MESSAGES_AUTOPLAY_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.settingBool(key: .shouldAutoPlayConsecutiveAudioMessages)
|
||||
.boolValue(
|
||||
key: .shouldAutoPlayConsecutiveAudioMessages,
|
||||
value: current.shouldAutoPlayConsecutiveAudioMessages,
|
||||
oldValue: (previous ?? current).shouldAutoPlayConsecutiveAudioMessages
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
|
@ -103,8 +126,5 @@ class ConversationSettingsViewModel: SessionTableViewModel<NoNav, ConversationSe
|
|||
)
|
||||
]
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[ConversationSettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.mapToSessionTableViewData(for: self)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import SessionUIKit
|
|||
import SessionMessagingKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSettingsViewModel.Section, NotificationSettingsViewModel.Setting> {
|
||||
class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSettingsViewModel.Section, NotificationSettingsViewModel.Item> {
|
||||
// MARK: - Config
|
||||
|
||||
public enum Section: SessionTableSection {
|
||||
|
@ -31,7 +31,7 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
}
|
||||
}
|
||||
|
||||
public enum Setting: Differentiable {
|
||||
public enum Item: Differentiable {
|
||||
case strategyUseFastMode
|
||||
case strategyDeviceSettings
|
||||
case styleSound
|
||||
|
@ -41,6 +41,13 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
|
||||
// MARK: - Content
|
||||
|
||||
private struct State: Equatable {
|
||||
let isUsingFullAPNs: Bool
|
||||
let notificationSound: Preferences.Sound
|
||||
let playNotificationSoundInForeground: Bool
|
||||
let previewType: Preferences.NotificationPreviewType
|
||||
}
|
||||
|
||||
override var title: String { "NOTIFICATIONS_TITLE".localized() }
|
||||
|
||||
public override var observableTableData: ObservableData { _observableTableData }
|
||||
|
@ -53,12 +60,30 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
|
||||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { db -> [SectionModel] in
|
||||
let notificationSound: Preferences.Sound = db[.defaultNotificationSound]
|
||||
.defaulting(to: Preferences.Sound.defaultNotificationSound)
|
||||
let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType]
|
||||
.defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType)
|
||||
|
||||
.trackingConstantRegion { db -> State in
|
||||
State(
|
||||
isUsingFullAPNs: false, // Set later the the data flow
|
||||
notificationSound: db[.defaultNotificationSound]
|
||||
.defaulting(to: Preferences.Sound.defaultNotificationSound),
|
||||
playNotificationSoundInForeground: db[.playNotificationSoundInForeground],
|
||||
previewType: db[.preferencesNotificationPreviewType]
|
||||
.defaulting(to: Preferences.NotificationPreviewType.defaultPreviewType)
|
||||
)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[NotificationSettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.manualRefreshFrom(forcedRefresh)
|
||||
.map { dbState -> State in
|
||||
State(
|
||||
isUsingFullAPNs: UserDefaults.standard[.isUsingFullAPNs],
|
||||
notificationSound: dbState.notificationSound,
|
||||
playNotificationSoundInForeground: dbState.playNotificationSoundInForeground,
|
||||
previewType: dbState.previewType
|
||||
)
|
||||
}
|
||||
.withPrevious()
|
||||
.map { (previous: State?, current: State) -> [SectionModel] in
|
||||
return [
|
||||
SectionModel(
|
||||
model: .strategy,
|
||||
|
@ -68,20 +93,24 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
title: "NOTIFICATIONS_STRATEGY_FAST_MODE_TITLE".localized(),
|
||||
subtitle: "NOTIFICATIONS_STRATEGY_FAST_MODE_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.userDefaults(UserDefaults.standard, key: "isUsingFullAPNs")
|
||||
.boolValue(
|
||||
current.isUsingFullAPNs,
|
||||
oldValue: (previous ?? current).isUsingFullAPNs
|
||||
)
|
||||
),
|
||||
styling: SessionCell.StyleInfo(
|
||||
allowedSeparators: [.top],
|
||||
customPadding: SessionCell.Padding(bottom: Values.verySmallSpacing)
|
||||
),
|
||||
onTap: {
|
||||
onTap: { [weak self] in
|
||||
UserDefaults.standard.set(
|
||||
!UserDefaults.standard.bool(forKey: "isUsingFullAPNs"),
|
||||
forKey: "isUsingFullAPNs"
|
||||
)
|
||||
|
||||
|
||||
// Force sync the push tokens on change
|
||||
SyncPushTokensJob.run(uploadOnlyIfStale: false)
|
||||
self?.forceRefresh()
|
||||
}
|
||||
),
|
||||
SessionCell.Info(
|
||||
|
@ -106,7 +135,7 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
id: .styleSound,
|
||||
title: "NOTIFICATIONS_STYLE_SOUND_TITLE".localized(),
|
||||
rightAccessory: .dropDown(
|
||||
.dynamicString { notificationSound.displayName }
|
||||
.dynamicString { current.notificationSound.displayName }
|
||||
),
|
||||
onTap: { [weak self] in
|
||||
self?.transitionToScreen(
|
||||
|
@ -117,7 +146,13 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
SessionCell.Info(
|
||||
id: .styleSoundWhenAppIsOpen,
|
||||
title: "NOTIFICATIONS_STYLE_SOUND_WHEN_OPEN_TITLE".localized(),
|
||||
rightAccessory: .toggle(.settingBool(key: .playNotificationSoundInForeground)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .playNotificationSoundInForeground,
|
||||
value: current.playNotificationSoundInForeground,
|
||||
oldValue: (previous ?? current).playNotificationSoundInForeground
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
db[.playNotificationSoundInForeground] = !db[.playNotificationSoundInForeground]
|
||||
|
@ -134,7 +169,7 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
title: "NOTIFICATIONS_STYLE_CONTENT_TITLE".localized(),
|
||||
subtitle: "NOTIFICATIONS_STYLE_CONTENT_DESCRIPTION".localized(),
|
||||
rightAccessory: .dropDown(
|
||||
.dynamicString { previewType.name }
|
||||
.dynamicString { current.previewType.name }
|
||||
),
|
||||
onTap: { [weak self] in
|
||||
self?.transitionToScreen(
|
||||
|
@ -146,8 +181,5 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
|
|||
)
|
||||
]
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[NotificationSettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.mapToSessionTableViewData(for: self)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
|
||||
public enum Section: SessionTableSection {
|
||||
case screenSecurity
|
||||
case messageRequests
|
||||
case readReceipts
|
||||
case typingIndicators
|
||||
case linkPreviews
|
||||
|
@ -36,6 +37,7 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
var title: String? {
|
||||
switch self {
|
||||
case .screenSecurity: return "PRIVACY_SECTION_SCREEN_SECURITY".localized()
|
||||
case .messageRequests: return "PRIVACY_SECTION_MESSAGE_REQUESTS".localized()
|
||||
case .readReceipts: return "PRIVACY_SECTION_READ_RECEIPTS".localized()
|
||||
case .typingIndicators: return "PRIVACY_SECTION_TYPING_INDICATORS".localized()
|
||||
case .linkPreviews: return "PRIVACY_SECTION_LINK_PREVIEWS".localized()
|
||||
|
@ -48,6 +50,7 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
|
||||
public enum Item: Differentiable {
|
||||
case screenLock
|
||||
case communityMessageRequests
|
||||
case screenshotNotifications
|
||||
case readReceipts
|
||||
case typingIndicators
|
||||
|
@ -75,6 +78,15 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
|
||||
// MARK: - Content
|
||||
|
||||
private struct State: Equatable {
|
||||
let isScreenLockEnabled: Bool
|
||||
let checkForCommunityMessageRequests: Bool
|
||||
let areReadReceiptsEnabled: Bool
|
||||
let typingIndicatorsEnabled: Bool
|
||||
let areLinkPreviewsEnabled: Bool
|
||||
let areCallsEnabled: Bool
|
||||
}
|
||||
|
||||
override var title: String { "PRIVACY_TITLE".localized() }
|
||||
|
||||
public override var observableTableData: ObservableData { _observableTableData }
|
||||
|
@ -87,7 +99,21 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
/// fetch (after the ones in `ValueConcurrentObserver.asyncStart`/`ValueConcurrentObserver.syncStart`)
|
||||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { db -> [SectionModel] in
|
||||
.trackingConstantRegion { [weak self] db -> State in
|
||||
State(
|
||||
isScreenLockEnabled: db[.isScreenLockEnabled],
|
||||
checkForCommunityMessageRequests: db[.checkForCommunityMessageRequests],
|
||||
areReadReceiptsEnabled: db[.areReadReceiptsEnabled],
|
||||
typingIndicatorsEnabled: db[.typingIndicatorsEnabled],
|
||||
areLinkPreviewsEnabled: db[.areLinkPreviewsEnabled],
|
||||
areCallsEnabled: db[.areCallsEnabled]
|
||||
)
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[PrivacySettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.withPrevious()
|
||||
.map { (previous: State?, current: State) -> [SectionModel] in
|
||||
return [
|
||||
SectionModel(
|
||||
model: .screenSecurity,
|
||||
|
@ -96,7 +122,13 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
id: .screenLock,
|
||||
title: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_TITLE".localized(),
|
||||
subtitle: "PRIVACY_SCREEN_SECURITY_LOCK_SESSION_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(.settingBool(key: .isScreenLockEnabled)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .isScreenLockEnabled,
|
||||
value: current.isScreenLockEnabled,
|
||||
oldValue: (previous ?? current).isScreenLockEnabled
|
||||
)
|
||||
),
|
||||
onTap: { [weak self] in
|
||||
// Make sure the device has a passcode set before allowing screen lock to
|
||||
// be enabled (Note: This will always return true on a simulator)
|
||||
|
@ -115,7 +147,32 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
}
|
||||
|
||||
Storage.shared.write { db in
|
||||
db[.isScreenLockEnabled] = !db[.isScreenLockEnabled]
|
||||
try db.setAndUpdateConfig(.isScreenLockEnabled, to: !db[.isScreenLockEnabled])
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
SectionModel(
|
||||
model: .messageRequests,
|
||||
elements: [
|
||||
SessionCell.Info(
|
||||
id: .communityMessageRequests,
|
||||
title: "PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_TITLE".localized(),
|
||||
subtitle: "PRIVACY_SCREEN_MESSAGE_REQUESTS_COMMUNITY_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .checkForCommunityMessageRequests,
|
||||
value: current.checkForCommunityMessageRequests,
|
||||
oldValue: (previous ?? current).checkForCommunityMessageRequests
|
||||
)
|
||||
),
|
||||
onTap: { [weak self] in
|
||||
Storage.shared.write { db in
|
||||
try db.setAndUpdateConfig(
|
||||
.checkForCommunityMessageRequests,
|
||||
to: !db[.checkForCommunityMessageRequests]
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -128,10 +185,16 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
id: .readReceipts,
|
||||
title: "PRIVACY_READ_RECEIPTS_TITLE".localized(),
|
||||
subtitle: "PRIVACY_READ_RECEIPTS_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(.settingBool(key: .areReadReceiptsEnabled)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .areReadReceiptsEnabled,
|
||||
value: current.areReadReceiptsEnabled,
|
||||
oldValue: (previous ?? current).areReadReceiptsEnabled
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
db[.areReadReceiptsEnabled] = !db[.areReadReceiptsEnabled]
|
||||
try db.setAndUpdateConfig(.areReadReceiptsEnabled, to: !db[.areReadReceiptsEnabled])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -176,10 +239,16 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
return result
|
||||
}
|
||||
),
|
||||
rightAccessory: .toggle(.settingBool(key: .typingIndicatorsEnabled)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .typingIndicatorsEnabled,
|
||||
value: current.typingIndicatorsEnabled,
|
||||
oldValue: (previous ?? current).typingIndicatorsEnabled
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
db[.typingIndicatorsEnabled] = !db[.typingIndicatorsEnabled]
|
||||
try db.setAndUpdateConfig(.typingIndicatorsEnabled, to: !db[.typingIndicatorsEnabled])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -192,10 +261,16 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
id: .linkPreviews,
|
||||
title: "PRIVACY_LINK_PREVIEWS_TITLE".localized(),
|
||||
subtitle: "PRIVACY_LINK_PREVIEWS_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(.settingBool(key: .areLinkPreviewsEnabled)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .areLinkPreviewsEnabled,
|
||||
value: current.areLinkPreviewsEnabled,
|
||||
oldValue: (previous ?? current).areLinkPreviewsEnabled
|
||||
)
|
||||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
db[.areLinkPreviewsEnabled] = !db[.areLinkPreviewsEnabled]
|
||||
try db.setAndUpdateConfig(.areLinkPreviewsEnabled, to: !db[.areLinkPreviewsEnabled])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -208,7 +283,13 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
id: .calls,
|
||||
title: "PRIVACY_CALLS_TITLE".localized(),
|
||||
subtitle: "PRIVACY_CALLS_DESCRIPTION".localized(),
|
||||
rightAccessory: .toggle(.settingBool(key: .areCallsEnabled)),
|
||||
rightAccessory: .toggle(
|
||||
.boolValue(
|
||||
key: .areCallsEnabled,
|
||||
value: current.areCallsEnabled,
|
||||
oldValue: (previous ?? current).areCallsEnabled
|
||||
)
|
||||
),
|
||||
accessibility: Accessibility(
|
||||
label: "Allow voice and video calls"
|
||||
),
|
||||
|
@ -223,7 +304,7 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
),
|
||||
onTap: {
|
||||
Storage.shared.write { db in
|
||||
db[.areCallsEnabled] = !db[.areCallsEnabled]
|
||||
try db.setAndUpdateConfig(.areCallsEnabled, to: !db[.areCallsEnabled])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -231,8 +312,5 @@ class PrivacySettingsViewModel: SessionTableViewModel<PrivacySettingsViewModel.N
|
|||
)
|
||||
]
|
||||
}
|
||||
.removeDuplicates()
|
||||
.handleEvents(didFail: { SNLog("[PrivacySettingsViewModel] Observation failed with error: \($0)") })
|
||||
.publisher(in: Storage.shared)
|
||||
.mapToSessionTableViewData(for: self)
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
reloadSectionsAnimation: .none,
|
||||
deleteRowsAnimation: .fade,
|
||||
insertRowsAnimation: .fade,
|
||||
reloadRowsAnimation: .fade,
|
||||
reloadRowsAnimation: .none,
|
||||
interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues
|
||||
) { [weak self] updatedData in
|
||||
self?.viewModel.updateTableData(updatedData)
|
||||
|
@ -339,6 +339,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
.store(in: &disposables)
|
||||
|
||||
viewModel.leftNavItems
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] maybeItems in
|
||||
self?.navigationItem.setLeftBarButtonItems(
|
||||
maybeItems.map { items in
|
||||
|
@ -360,6 +361,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
.store(in: &disposables)
|
||||
|
||||
viewModel.rightNavItems
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] maybeItems in
|
||||
self?.navigationItem.setRightBarButtonItems(
|
||||
maybeItems.map { items in
|
||||
|
@ -381,18 +383,21 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
.store(in: &disposables)
|
||||
|
||||
viewModel.emptyStateTextPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] text in
|
||||
self?.emptyStateLabel.text = text
|
||||
}
|
||||
.store(in: &disposables)
|
||||
|
||||
viewModel.footerView
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] footerView in
|
||||
self?.tableView.tableFooterView = footerView
|
||||
}
|
||||
.store(in: &disposables)
|
||||
|
||||
viewModel.footerButtonInfo
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] buttonInfo in
|
||||
if let buttonInfo: SessionButton.Info = buttonInfo {
|
||||
self?.footerButton.setTitle(buttonInfo.title, for: .normal)
|
||||
|
@ -627,7 +632,7 @@ class SessionTableViewController<NavItemId: Equatable, Section: SessionTableSect
|
|||
) {
|
||||
// Try update the existing cell to have a nice animation instead of reloading the cell
|
||||
if let existingCell: SessionCell = tableView.cellForRow(at: indexPath) as? SessionCell {
|
||||
existingCell.update(with: info)
|
||||
existingCell.update(with: info, isManualReload: true)
|
||||
}
|
||||
else {
|
||||
tableView.reloadRows(at: [indexPath], with: .none)
|
||||
|
|
|
@ -27,6 +27,9 @@ class SessionTableViewModel<NavItemId: Equatable, Section: SessionTableSection,
|
|||
open var leftNavItems: AnyPublisher<[NavItem]?, Never> { Just(nil).eraseToAnyPublisher() }
|
||||
open var rightNavItems: AnyPublisher<[NavItem]?, Never> { Just(nil).eraseToAnyPublisher() }
|
||||
|
||||
private let _forcedRefresh: PassthroughSubject<Void, Never> = PassthroughSubject()
|
||||
lazy var forcedRefresh: AnyPublisher<Void, Never> = _forcedRefresh
|
||||
.shareReplay(0)
|
||||
private let _showToast: PassthroughSubject<(String, ThemeValue), Never> = PassthroughSubject()
|
||||
lazy var showToast: AnyPublisher<(String, ThemeValue), Never> = _showToast
|
||||
.shareReplay(0)
|
||||
|
@ -62,6 +65,10 @@ class SessionTableViewModel<NavItemId: Equatable, Section: SessionTableSection,
|
|||
|
||||
// MARK: - Functions
|
||||
|
||||
func forceRefresh() {
|
||||
_forcedRefresh.send(())
|
||||
}
|
||||
|
||||
func setIsEditing(_ isEditing: Bool) {
|
||||
_isEditing.send(isEditing)
|
||||
}
|
||||
|
@ -101,7 +108,7 @@ extension Array {
|
|||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher {
|
||||
extension Publisher {
|
||||
func mapToSessionTableViewData<Nav, Section, Item>(
|
||||
for viewModel: SessionTableViewModel<Nav, Section, Item>
|
||||
) -> AnyPublisher<(Output, StagedChangeset<Output>), Failure> where Output == [ArraySection<Section, SessionCell.Info<Item>>] {
|
||||
|
|
|
@ -394,19 +394,30 @@ extension SessionCell.Accessory {
|
|||
|
||||
extension SessionCell.Accessory {
|
||||
public enum DataSource: Hashable, Equatable {
|
||||
case boolValue(Bool)
|
||||
case boolValue(key: String, value: Bool, oldValue: Bool)
|
||||
case dynamicString(() -> String?)
|
||||
case userDefaults(UserDefaults, key: String)
|
||||
case settingBool(key: Setting.BoolKey)
|
||||
|
||||
static func boolValue(_ value: Bool, oldValue: Bool) -> DataSource {
|
||||
return .boolValue(key: "", value: value, oldValue: oldValue)
|
||||
}
|
||||
|
||||
static func boolValue(key: Setting.BoolKey, value: Bool, oldValue: Bool) -> DataSource {
|
||||
return .boolValue(key: key.rawValue, value: value, oldValue: oldValue)
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
public var currentBoolValue: Bool {
|
||||
switch self {
|
||||
case .boolValue(let value): return value
|
||||
case .boolValue(_, let value, _): return value
|
||||
case .dynamicString: return false
|
||||
case .userDefaults(let defaults, let key): return defaults.bool(forKey: key)
|
||||
case .settingBool(let key): return Storage.shared[key]
|
||||
}
|
||||
}
|
||||
|
||||
public var oldBoolValue: Bool {
|
||||
switch self {
|
||||
case .boolValue(_, _, let oldValue): return oldValue
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -421,27 +432,27 @@ extension SessionCell.Accessory {
|
|||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case .boolValue(let value): value.hash(into: &hasher)
|
||||
case .boolValue(let key, let value, let oldValue):
|
||||
key.hash(into: &hasher)
|
||||
value.hash(into: &hasher)
|
||||
oldValue.hash(into: &hasher)
|
||||
|
||||
case .dynamicString(let generator): generator().hash(into: &hasher)
|
||||
case .userDefaults(_, let key): key.hash(into: &hasher)
|
||||
case .settingBool(let key): key.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
public static func == (lhs: DataSource, rhs: DataSource) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.boolValue(let lhsValue), .boolValue(let rhsValue)):
|
||||
return (lhsValue == rhsValue)
|
||||
case (.boolValue(let lhsKey, let lhsValue, let lhsOldValue), .boolValue(let rhsKey, let rhsValue, let rhsOldValue)):
|
||||
return (
|
||||
lhsKey == rhsKey &&
|
||||
lhsValue == rhsValue &&
|
||||
lhsOldValue == rhsOldValue
|
||||
)
|
||||
|
||||
case (.dynamicString(let lhsGenerator), .dynamicString(let rhsGenerator)):
|
||||
return (lhsGenerator() == rhsGenerator())
|
||||
|
||||
case (.userDefaults(_, let lhsKey), .userDefaults(_, let rhsKey)):
|
||||
return (lhsKey == rhsKey)
|
||||
|
||||
case (.settingBool(let lhsKey), .settingBool(let rhsKey)):
|
||||
return (lhsKey == rhsKey)
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,7 +277,8 @@ extension SessionCell {
|
|||
public func update(
|
||||
with accessory: Accessory?,
|
||||
tintColor: ThemeValue,
|
||||
isEnabled: Bool
|
||||
isEnabled: Bool,
|
||||
isManualReload: Bool
|
||||
) {
|
||||
guard let accessory: Accessory = accessory else { return }
|
||||
|
||||
|
@ -356,10 +357,15 @@ extension SessionCell {
|
|||
fixedWidthConstraint.isActive = true
|
||||
toggleSwitchConstraints.forEach { $0.isActive = true }
|
||||
|
||||
let newValue: Bool = dataSource.currentBoolValue
|
||||
|
||||
if newValue != toggleSwitch.isOn {
|
||||
toggleSwitch.setOn(newValue, animated: true)
|
||||
if !isManualReload {
|
||||
toggleSwitch.setOn(dataSource.oldBoolValue, animated: false)
|
||||
|
||||
// Dispatch so the cell reload doesn't conflict with the setting change animation
|
||||
if dataSource.oldBoolValue != dataSource.currentBoolValue {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) { [weak toggleSwitch] in
|
||||
toggleSwitch?.setOn(dataSource.currentBoolValue, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .dropDown(let dataSource, let accessibility):
|
||||
|
|
|
@ -313,7 +313,7 @@ public class SessionCell: UITableViewCell {
|
|||
botSeparator.isHidden = true
|
||||
}
|
||||
|
||||
public func update<ID: Hashable & Differentiable>(with info: Info<ID>) {
|
||||
public func update<ID: Hashable & Differentiable>(with info: Info<ID>, isManualReload: Bool = false) {
|
||||
interactionMode = (info.title?.interaction ?? .none)
|
||||
shouldHighlightTitle = (info.title?.interaction != .copy)
|
||||
titleExtraView = info.title?.extraViewGenerator?()
|
||||
|
@ -332,7 +332,8 @@ public class SessionCell: UITableViewCell {
|
|||
leftAccessoryView.update(
|
||||
with: info.leftAccessory,
|
||||
tintColor: info.styling.tintColor,
|
||||
isEnabled: info.isEnabled
|
||||
isEnabled: info.isEnabled,
|
||||
isManualReload: isManualReload
|
||||
)
|
||||
titleStackView.isHidden = (info.title == nil && info.subtitle == nil)
|
||||
titleLabel.isUserInteractionEnabled = (info.title?.interaction == .copy)
|
||||
|
@ -356,7 +357,8 @@ public class SessionCell: UITableViewCell {
|
|||
rightAccessoryView.update(
|
||||
with: info.rightAccessory,
|
||||
tintColor: info.styling.tintColor,
|
||||
isEnabled: info.isEnabled
|
||||
isEnabled: info.isEnabled,
|
||||
isManualReload: isManualReload
|
||||
)
|
||||
|
||||
contentStackViewLeadingConstraint.isActive = (info.styling.alignment == .leading)
|
||||
|
|
|
@ -99,7 +99,8 @@ enum MockDataGenerator {
|
|||
.compactMap { _ in stringContent.randomElement(using: &dmThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -180,7 +181,8 @@ enum MockDataGenerator {
|
|||
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
@ -310,7 +312,8 @@ enum MockDataGenerator {
|
|||
.compactMap { _ in stringContent.randomElement(using: &ogThreadRandomGenerator) }
|
||||
.joined(),
|
||||
lastNameUpdate: Date().timeIntervalSince1970,
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970
|
||||
lastProfilePictureUpdate: Date().timeIntervalSince1970,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
)
|
||||
.saved(db)
|
||||
|
||||
|
|
|
@ -37,7 +37,8 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
|
|||
(Features.useSharedUtilForUserConfig(db) ?
|
||||
_014_GenerateInitialUserConfigDumps.self :
|
||||
(nil as Migration.Type?)
|
||||
)
|
||||
),
|
||||
_015_BlockCommunityMessageRequests.self
|
||||
].compactMap { $0 }
|
||||
]
|
||||
)
|
||||
|
|
|
@ -422,7 +422,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
profilePictureUrl: legacyContact.profilePictureURL,
|
||||
profilePictureFileName: legacyContact.profilePictureFileName,
|
||||
profileEncryptionKey: legacyContact.profileEncryptionKey?.keyData,
|
||||
lastProfilePictureUpdate: 0
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
).migrationSafeInsert(db)
|
||||
|
||||
/// **Note:** The blow "shouldForce" flags are here to allow us to avoid having to run legacy migrations they
|
||||
|
@ -645,7 +646,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
id: profileId,
|
||||
name: profileId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
@ -1059,7 +1061,8 @@ enum _003_YDBToGRDBMigration: Migration {
|
|||
id: quotedMessage.authorId,
|
||||
name: quotedMessage.authorId,
|
||||
lastNameUpdate: 0,
|
||||
lastProfilePictureUpdate: 0
|
||||
lastProfilePictureUpdate: 0,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
).migrationSafeSave(db)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ enum _005_FixDeletedMessageReadState: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "FixDeletedMessageReadState"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
_ = try Interaction
|
||||
|
|
|
@ -10,7 +10,7 @@ enum _006_FixHiddenModAdminSupport: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "FixHiddenModAdminSupport"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.alter(table: GroupMember.self) { t in
|
||||
|
|
|
@ -9,7 +9,7 @@ enum _007_HomeQueryOptimisationIndexes: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "HomeQueryOptimisationIndexes"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.create(
|
||||
|
|
|
@ -9,7 +9,7 @@ enum _008_EmojiReacts: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "EmojiReacts"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.create(table: Reaction.self) { t in
|
||||
|
|
|
@ -8,7 +8,7 @@ enum _009_OpenGroupPermission: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "OpenGroupPermission"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: GRDB.Database) throws {
|
||||
try db.alter(table: OpenGroup.self) { t in
|
||||
|
|
|
@ -10,7 +10,7 @@ enum _011_AddPendingReadReceipts: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "AddPendingReadReceipts"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
try db.create(table: PendingReadReceipt.self) { t in
|
||||
|
|
|
@ -9,7 +9,7 @@ enum _012_AddFTSIfNeeded: Migration {
|
|||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "AddFTSIfNeeded"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.1
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
// Fix an issue that the fullTextSearchTable was dropped unintentionally and global search won't work.
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// This migration adds a flag indicating whether a profile has indicated it is blocking community message requests
|
||||
enum _015_BlockCommunityMessageRequests: Migration {
|
||||
static let target: TargetMigrations.Identifier = .messagingKit
|
||||
static let identifier: String = "BlockCommunityMessageRequests"
|
||||
static let needsConfigSync: Bool = false
|
||||
static let minExpectedRunDuration: TimeInterval = 0.01
|
||||
|
||||
static func migrate(_ db: Database) throws {
|
||||
// Add the new 'Profile' properties
|
||||
try db.alter(table: Profile.self) { t in
|
||||
t.add(.blocksCommunityMessageRequests, .boolean)
|
||||
t.add(.lastBlocksCommunityMessageRequests, .integer)
|
||||
.notNull()
|
||||
.defaults(to: 0)
|
||||
}
|
||||
|
||||
// If the user exists and the 'checkForCommunityMessageRequests' hasn't already been set then default it to "false"
|
||||
if
|
||||
Identity.userExists(db),
|
||||
(try Setting.exists(db, id: Setting.BoolKey.checkForCommunityMessageRequests.rawValue)) == false
|
||||
{
|
||||
db[.checkForCommunityMessageRequests] = true
|
||||
}
|
||||
|
||||
Storage.update(progress: 1, for: self, in: target) // In case this is the last migration
|
||||
}
|
||||
}
|
|
@ -27,6 +27,9 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
case profilePictureFileName
|
||||
case profileEncryptionKey
|
||||
case lastProfilePictureUpdate
|
||||
|
||||
case blocksCommunityMessageRequests
|
||||
case lastBlocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
/// The id for the user that owns the profile (Note: This could be a sessionId, a blindedId or some future variant)
|
||||
|
@ -53,6 +56,12 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
/// The timestamp (in seconds since epoch) that the profile picture was last updated
|
||||
public let lastProfilePictureUpdate: TimeInterval
|
||||
|
||||
/// A flag indicating whether this profile has reported that it blocks community message requests
|
||||
public let blocksCommunityMessageRequests: Bool?
|
||||
|
||||
/// The timestamp (in seconds since epoch) that the `blocksCommunityMessageRequests` setting was last updated
|
||||
public let lastBlocksCommunityMessageRequests: TimeInterval
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
|
@ -63,7 +72,9 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
profilePictureUrl: String? = nil,
|
||||
profilePictureFileName: String? = nil,
|
||||
profileEncryptionKey: Data? = nil,
|
||||
lastProfilePictureUpdate: TimeInterval
|
||||
lastProfilePictureUpdate: TimeInterval,
|
||||
blocksCommunityMessageRequests: Bool? = nil,
|
||||
lastBlocksCommunityMessageRequests: TimeInterval
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
@ -73,6 +84,8 @@ public struct Profile: Codable, Identifiable, Equatable, Hashable, FetchableReco
|
|||
self.profilePictureFileName = profilePictureFileName
|
||||
self.profileEncryptionKey = profileEncryptionKey
|
||||
self.lastProfilePictureUpdate = lastProfilePictureUpdate
|
||||
self.blocksCommunityMessageRequests = blocksCommunityMessageRequests
|
||||
self.lastBlocksCommunityMessageRequests = lastBlocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
// MARK: - Description
|
||||
|
@ -114,7 +127,9 @@ public extension Profile {
|
|||
profilePictureUrl: profilePictureUrl,
|
||||
profilePictureFileName: try? container.decode(String.self, forKey: .profilePictureFileName),
|
||||
profileEncryptionKey: profileKey,
|
||||
lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate)
|
||||
lastProfilePictureUpdate: try container.decode(TimeInterval.self, forKey: .lastProfilePictureUpdate),
|
||||
blocksCommunityMessageRequests: try? container.decode(Bool.self, forKey: .blocksCommunityMessageRequests),
|
||||
lastBlocksCommunityMessageRequests: try container.decode(TimeInterval.self, forKey: .lastBlocksCommunityMessageRequests)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -129,6 +144,8 @@ public extension Profile {
|
|||
try container.encodeIfPresent(profilePictureFileName, forKey: .profilePictureFileName)
|
||||
try container.encodeIfPresent(profileEncryptionKey, forKey: .profileEncryptionKey)
|
||||
try container.encode(lastProfilePictureUpdate, forKey: .lastProfilePictureUpdate)
|
||||
try container.encodeIfPresent(blocksCommunityMessageRequests, forKey: .blocksCommunityMessageRequests)
|
||||
try container.encode(lastBlocksCommunityMessageRequests, forKey: .lastBlocksCommunityMessageRequests)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +173,9 @@ public extension Profile {
|
|||
profilePictureUrl: profilePictureUrl,
|
||||
profilePictureFileName: nil,
|
||||
profileEncryptionKey: profileKey,
|
||||
lastProfilePictureUpdate: sentTimestamp
|
||||
lastProfilePictureUpdate: sentTimestamp,
|
||||
blocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? proto.blocksCommunityMessageRequests : nil),
|
||||
lastBlocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? sentTimestamp : 0)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -242,7 +261,9 @@ public extension Profile {
|
|||
profilePictureUrl: nil,
|
||||
profilePictureFileName: nil,
|
||||
profileEncryptionKey: nil,
|
||||
lastProfilePictureUpdate: 0
|
||||
lastProfilePictureUpdate: 0,
|
||||
blocksCommunityMessageRequests: nil,
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,15 +10,22 @@ public extension VisibleMessage {
|
|||
public let displayName: String?
|
||||
public let profileKey: Data?
|
||||
public let profilePictureUrl: String?
|
||||
public let blocksCommunityMessageRequests: Bool?
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
internal init(displayName: String, profileKey: Data? = nil, profilePictureUrl: String? = nil) {
|
||||
internal init(
|
||||
displayName: String,
|
||||
profileKey: Data? = nil,
|
||||
profilePictureUrl: String? = nil,
|
||||
blocksCommunityMessageRequests: Bool? = nil
|
||||
) {
|
||||
let hasUrlAndKey: Bool = (profileKey != nil && profilePictureUrl != nil)
|
||||
|
||||
self.displayName = displayName
|
||||
self.profileKey = (hasUrlAndKey ? profileKey : nil)
|
||||
self.profilePictureUrl = (hasUrlAndKey ? profilePictureUrl : nil)
|
||||
self.blocksCommunityMessageRequests = blocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
// MARK: - Proto Conversion
|
||||
|
@ -32,7 +39,8 @@ public extension VisibleMessage {
|
|||
return VMProfile(
|
||||
displayName: displayName,
|
||||
profileKey: proto.profileKey,
|
||||
profilePictureUrl: profileProto.profilePicture
|
||||
profilePictureUrl: profileProto.profilePicture,
|
||||
blocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? proto.blocksCommunityMessageRequests : nil)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -45,6 +53,10 @@ public extension VisibleMessage {
|
|||
let profileProto = SNProtoLokiProfile.builder()
|
||||
profileProto.setDisplayName(displayName)
|
||||
|
||||
if let blocksCommunityMessageRequests: Bool = self.blocksCommunityMessageRequests {
|
||||
dataMessageProto.setBlocksCommunityMessageRequests(blocksCommunityMessageRequests)
|
||||
}
|
||||
|
||||
if let profileKey = profileKey, let profilePictureUrl = profilePictureUrl {
|
||||
dataMessageProto.setProfileKey(profileKey)
|
||||
profileProto.setProfilePicture(profilePictureUrl)
|
||||
|
@ -112,10 +124,14 @@ public extension VisibleMessage {
|
|||
// MARK: - Conversion
|
||||
|
||||
extension VisibleMessage.VMProfile {
|
||||
init(profile: Profile) {
|
||||
init(
|
||||
profile: Profile,
|
||||
blocksCommunityMessageRequests: Bool?
|
||||
) {
|
||||
self.displayName = profile.name
|
||||
self.profileKey = profile.profileEncryptionKey
|
||||
self.profilePictureUrl = profile.profilePictureUrl
|
||||
self.blocksCommunityMessageRequests = blocksCommunityMessageRequests
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,10 +109,12 @@ public enum OpenGroupAPI {
|
|||
// The 'inbox' and 'outbox' only work with blinded keys so don't bother polling them if not blinded
|
||||
!capabilities.contains(.blind) ? [] :
|
||||
[
|
||||
// Inbox
|
||||
(lastInboxMessageId == 0 ?
|
||||
try preparedInbox(db, on: server, using: dependencies) :
|
||||
try preparedInboxSince(db, id: lastInboxMessageId, on: server, using: dependencies)
|
||||
// Inbox (only check the inbox if the user want's community message requests)
|
||||
(!db[.checkForCommunityMessageRequests] ? nil :
|
||||
(lastInboxMessageId == 0 ?
|
||||
try preparedInbox(db, on: server, using: dependencies) :
|
||||
try preparedInboxSince(db, id: lastInboxMessageId, on: server, using: dependencies)
|
||||
)
|
||||
),
|
||||
|
||||
// Outbox
|
||||
|
@ -120,7 +122,7 @@ public enum OpenGroupAPI {
|
|||
try preparedOutbox(db, on: server, using: dependencies) :
|
||||
try preparedOutboxSince(db, id: lastOutboxMessageId, on: server, using: dependencies)
|
||||
),
|
||||
]
|
||||
].compactMap { $0 }
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -2497,6 +2497,9 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
|
|||
if let _value = syncTarget {
|
||||
builder.setSyncTarget(_value)
|
||||
}
|
||||
if hasBlocksCommunityMessageRequests {
|
||||
builder.setBlocksCommunityMessageRequests(blocksCommunityMessageRequests)
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
|
@ -2570,6 +2573,10 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
|
|||
proto.syncTarget = valueParam
|
||||
}
|
||||
|
||||
@objc public func setBlocksCommunityMessageRequests(_ valueParam: Bool) {
|
||||
proto.blocksCommunityMessageRequests = valueParam
|
||||
}
|
||||
|
||||
@objc public func build() throws -> SNProtoDataMessage {
|
||||
return try SNProtoDataMessage.parseProto(proto)
|
||||
}
|
||||
|
@ -2646,6 +2653,13 @@ extension SNProtoDataMessageClosedGroupControlMessage.SNProtoDataMessageClosedGr
|
|||
return proto.hasSyncTarget
|
||||
}
|
||||
|
||||
@objc public var blocksCommunityMessageRequests: Bool {
|
||||
return proto.blocksCommunityMessageRequests
|
||||
}
|
||||
@objc public var hasBlocksCommunityMessageRequests: Bool {
|
||||
return proto.hasBlocksCommunityMessageRequests
|
||||
}
|
||||
|
||||
private init(proto: SessionProtos_DataMessage,
|
||||
attachments: [SNProtoAttachmentPointer],
|
||||
quote: SNProtoDataMessageQuote?,
|
||||
|
|
|
@ -600,7 +600,7 @@ struct SessionProtos_DataMessage {
|
|||
set {_uniqueStorage()._attachments = newValue}
|
||||
}
|
||||
|
||||
/// optional GroupContext group = 3; // No longer used
|
||||
/// optional GroupContext group = 3; // No longer used
|
||||
var flags: UInt32 {
|
||||
get {return _storage._flags ?? 0}
|
||||
set {_uniqueStorage()._flags = newValue}
|
||||
|
@ -696,6 +696,15 @@ struct SessionProtos_DataMessage {
|
|||
/// Clears the value of `syncTarget`. Subsequent reads from it will return its default value.
|
||||
mutating func clearSyncTarget() {_uniqueStorage()._syncTarget = nil}
|
||||
|
||||
var blocksCommunityMessageRequests: Bool {
|
||||
get {return _storage._blocksCommunityMessageRequests ?? false}
|
||||
set {_uniqueStorage()._blocksCommunityMessageRequests = newValue}
|
||||
}
|
||||
/// Returns true if `blocksCommunityMessageRequests` has been explicitly set.
|
||||
var hasBlocksCommunityMessageRequests: Bool {return _storage._blocksCommunityMessageRequests != nil}
|
||||
/// Clears the value of `blocksCommunityMessageRequests`. Subsequent reads from it will return its default value.
|
||||
mutating func clearBlocksCommunityMessageRequests() {_uniqueStorage()._blocksCommunityMessageRequests = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
enum Flags: SwiftProtobuf.Enum {
|
||||
|
@ -1665,6 +1674,43 @@ extension SessionProtos_SharedConfigMessage.Kind: CaseIterable {
|
|||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension SessionProtos_Envelope: @unchecked Sendable {}
|
||||
extension SessionProtos_Envelope.TypeEnum: @unchecked Sendable {}
|
||||
extension SessionProtos_TypingMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_TypingMessage.Action: @unchecked Sendable {}
|
||||
extension SessionProtos_UnsendRequest: @unchecked Sendable {}
|
||||
extension SessionProtos_MessageRequestResponse: @unchecked Sendable {}
|
||||
extension SessionProtos_Content: @unchecked Sendable {}
|
||||
extension SessionProtos_CallMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_CallMessage.TypeEnum: @unchecked Sendable {}
|
||||
extension SessionProtos_KeyPair: @unchecked Sendable {}
|
||||
extension SessionProtos_DataExtractionNotification: @unchecked Sendable {}
|
||||
extension SessionProtos_DataExtractionNotification.TypeEnum: @unchecked Sendable {}
|
||||
extension SessionProtos_LokiProfile: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Flags: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Quote: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Quote.QuotedAttachment: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Quote.QuotedAttachment.Flags: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Preview: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Reaction: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.Reaction.Action: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.OpenGroupInvitation: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.ClosedGroupControlMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum: @unchecked Sendable {}
|
||||
extension SessionProtos_DataMessage.ClosedGroupControlMessage.KeyPairWrapper: @unchecked Sendable {}
|
||||
extension SessionProtos_ConfigurationMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_ConfigurationMessage.ClosedGroup: @unchecked Sendable {}
|
||||
extension SessionProtos_ConfigurationMessage.Contact: @unchecked Sendable {}
|
||||
extension SessionProtos_ReceiptMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_ReceiptMessage.TypeEnum: @unchecked Sendable {}
|
||||
extension SessionProtos_AttachmentPointer: @unchecked Sendable {}
|
||||
extension SessionProtos_AttachmentPointer.Flags: @unchecked Sendable {}
|
||||
extension SessionProtos_SharedConfigMessage: @unchecked Sendable {}
|
||||
extension SessionProtos_SharedConfigMessage.Kind: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "SessionProtos"
|
||||
|
@ -2288,6 +2334,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
102: .same(proto: "openGroupInvitation"),
|
||||
104: .same(proto: "closedGroupControlMessage"),
|
||||
105: .same(proto: "syncTarget"),
|
||||
106: .same(proto: "blocksCommunityMessageRequests"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
|
@ -2304,6 +2351,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
var _openGroupInvitation: SessionProtos_DataMessage.OpenGroupInvitation? = nil
|
||||
var _closedGroupControlMessage: SessionProtos_DataMessage.ClosedGroupControlMessage? = nil
|
||||
var _syncTarget: String? = nil
|
||||
var _blocksCommunityMessageRequests: Bool? = nil
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
|
@ -2323,6 +2371,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
_openGroupInvitation = source._openGroupInvitation
|
||||
_closedGroupControlMessage = source._closedGroupControlMessage
|
||||
_syncTarget = source._syncTarget
|
||||
_blocksCommunityMessageRequests = source._blocksCommunityMessageRequests
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2366,6 +2415,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
case 102: try { try decoder.decodeSingularMessageField(value: &_storage._openGroupInvitation) }()
|
||||
case 104: try { try decoder.decodeSingularMessageField(value: &_storage._closedGroupControlMessage) }()
|
||||
case 105: try { try decoder.decodeSingularStringField(value: &_storage._syncTarget) }()
|
||||
case 106: try { try decoder.decodeSingularBoolField(value: &_storage._blocksCommunityMessageRequests) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -2417,6 +2467,9 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
try { if let v = _storage._syncTarget {
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 105)
|
||||
} }()
|
||||
try { if let v = _storage._blocksCommunityMessageRequests {
|
||||
try visitor.visitSingularBoolField(value: v, fieldNumber: 106)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
@ -2439,6 +2492,7 @@ extension SessionProtos_DataMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
|
|||
if _storage._openGroupInvitation != rhs_storage._openGroupInvitation {return false}
|
||||
if _storage._closedGroupControlMessage != rhs_storage._closedGroupControlMessage {return false}
|
||||
if _storage._syncTarget != rhs_storage._syncTarget {return false}
|
||||
if _storage._blocksCommunityMessageRequests != rhs_storage._blocksCommunityMessageRequests {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
|
|
|
@ -218,6 +218,13 @@ extension WebSocketProtos_WebSocketMessage.TypeEnum: CaseIterable {
|
|||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension WebSocketProtos_WebSocketRequestMessage: @unchecked Sendable {}
|
||||
extension WebSocketProtos_WebSocketResponseMessage: @unchecked Sendable {}
|
||||
extension WebSocketProtos_WebSocketMessage: @unchecked Sendable {}
|
||||
extension WebSocketProtos_WebSocketMessage.TypeEnum: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "WebSocketProtos"
|
||||
|
|
|
@ -192,20 +192,21 @@ message DataMessage {
|
|||
optional uint32 expirationTimer = 8;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
// optional GroupContext group = 3; // No longer used
|
||||
optional uint32 flags = 4;
|
||||
optional uint32 expireTimer = 5;
|
||||
optional bytes profileKey = 6;
|
||||
optional uint64 timestamp = 7;
|
||||
optional Quote quote = 8;
|
||||
repeated Preview preview = 10;
|
||||
optional Reaction reaction = 11;
|
||||
optional LokiProfile profile = 101;
|
||||
optional OpenGroupInvitation openGroupInvitation = 102;
|
||||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
// optional GroupContext group = 3; // No longer used
|
||||
optional uint32 flags = 4;
|
||||
optional uint32 expireTimer = 5;
|
||||
optional bytes profileKey = 6;
|
||||
optional uint64 timestamp = 7;
|
||||
optional Quote quote = 8;
|
||||
repeated Preview preview = 10;
|
||||
optional Reaction reaction = 11;
|
||||
optional LokiProfile profile = 101;
|
||||
optional OpenGroupInvitation openGroupInvitation = 102;
|
||||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional bool blocksCommunityMessageRequests = 106;
|
||||
}
|
||||
|
||||
message ConfigurationMessage {
|
||||
|
|
|
@ -31,6 +31,7 @@ extension MessageReceiver {
|
|||
db,
|
||||
publicKey: sender,
|
||||
name: profile.displayName,
|
||||
blocksCommunityMessageRequests: profile.blocksCommunityMessageRequests,
|
||||
avatarUpdate: {
|
||||
guard
|
||||
let profilePictureUrl: String = profile.profilePictureUrl,
|
||||
|
|
|
@ -436,7 +436,8 @@ public final class MessageSender {
|
|||
|
||||
// Attach the user's profile
|
||||
message.profile = VisibleMessage.VMProfile(
|
||||
profile: Profile.fetchOrCreateCurrentUser()
|
||||
profile: Profile.fetchOrCreateCurrentUser(db),
|
||||
blocksCommunityMessageRequests: !db[.checkForCommunityMessageRequests]
|
||||
)
|
||||
|
||||
if (message.profile?.displayName ?? "").isEmpty {
|
||||
|
|
|
@ -573,7 +573,8 @@ private extension SessionUtil {
|
|||
count: ProfileManager.avatarAES256KeyByteLength
|
||||
)
|
||||
),
|
||||
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000)
|
||||
lastProfilePictureUpdate: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
||||
lastBlocksCommunityMessageRequests: 0
|
||||
)
|
||||
|
||||
result[contactId] = ContactData(
|
||||
|
|
|
@ -213,6 +213,30 @@ internal extension SessionUtil {
|
|||
return updated
|
||||
}
|
||||
|
||||
static func updatingSetting(_ db: Database, _ updated: Setting?) throws {
|
||||
// Don't current support any nullable settings
|
||||
guard let updatedSetting: Setting = updated else { return }
|
||||
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
||||
// Currently the only synced setting is 'checkForCommunityMessageRequests'
|
||||
switch updatedSetting.id {
|
||||
case Setting.BoolKey.checkForCommunityMessageRequests.rawValue:
|
||||
try SessionUtil.performAndPushChange(
|
||||
db,
|
||||
for: .userProfile,
|
||||
publicKey: userPublicKey
|
||||
) { conf in
|
||||
try SessionUtil.updateSettings(
|
||||
checkForCommunityMessageRequests: updatedSetting.unsafeValue(as: Bool.self),
|
||||
in: conf
|
||||
)
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
static func kickFromConversationUIIfNeeded(removedThreadIds: [String]) {
|
||||
guard !removedThreadIds.isEmpty else { return }
|
||||
|
||||
|
|
|
@ -12,6 +12,10 @@ internal extension SessionUtil {
|
|||
Profile.Columns.profileEncryptionKey
|
||||
]
|
||||
|
||||
static let syncedSettings: [String] = [
|
||||
Setting.BoolKey.checkForCommunityMessageRequests.rawValue
|
||||
]
|
||||
|
||||
// MARK: - Incoming Changes
|
||||
|
||||
static func handleUserProfileUpdate(
|
||||
|
@ -115,6 +119,17 @@ internal extension SessionUtil {
|
|||
}
|
||||
}
|
||||
|
||||
// Update settings if needed
|
||||
let updatedAllowBlindedMessageRequests: Int32 = user_profile_get_blinded_msgreqs(conf)
|
||||
let updatedAllowBlindedMessageRequestsBoolValue: Bool = (updatedAllowBlindedMessageRequests >= 1)
|
||||
|
||||
if
|
||||
updatedAllowBlindedMessageRequests >= 0 &&
|
||||
updatedAllowBlindedMessageRequestsBoolValue != db[.checkForCommunityMessageRequests]
|
||||
{
|
||||
db[.checkForCommunityMessageRequests] = updatedAllowBlindedMessageRequestsBoolValue
|
||||
}
|
||||
|
||||
// Create a contact for the current user if needed (also force-approve the current user
|
||||
// in case the account got into a weird state or restored directly from a migration)
|
||||
let userContact: Contact = Contact.fetchOrCreate(db, id: userPublicKey)
|
||||
|
@ -159,4 +174,15 @@ internal extension SessionUtil {
|
|||
|
||||
user_profile_set_nts_priority(conf, priority)
|
||||
}
|
||||
|
||||
static func updateSettings(
|
||||
checkForCommunityMessageRequests: Bool? = nil,
|
||||
in conf: UnsafeMutablePointer<config_object>?
|
||||
) throws {
|
||||
guard conf != nil else { throw SessionUtilError.nilConfigObject }
|
||||
|
||||
if let blindedMessageRequests: Bool = checkForCommunityMessageRequests {
|
||||
user_profile_set_blinded_msgreqs(conf, (blindedMessageRequests ? 1 : 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public extension Database {
|
||||
func setAndUpdateConfig(_ key: Setting.BoolKey, to newValue: Bool) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
func setAndUpdateConfig(_ key: Setting.DoubleKey, to newValue: Double?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
func setAndUpdateConfig(_ key: Setting.IntKey, to newValue: Int?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
func setAndUpdateConfig(_ key: Setting.StringKey, to newValue: String?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
func setAndUpdateConfig<T: EnumIntSetting>(_ key: Setting.EnumKey, to newValue: T?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
func setAndUpdateConfig<T: EnumStringSetting>(_ key: Setting.EnumKey, to newValue: T?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
/// Value will be stored as a timestamp in seconds since 1970
|
||||
func setAndUpdateConfig(_ key: Setting.DateKey, to newValue: Date?) throws {
|
||||
try updateConfigIfNeeded(self, key: key.rawValue, updatedSetting: self.setting(key: key, to: newValue))
|
||||
}
|
||||
|
||||
private func updateConfigIfNeeded(
|
||||
_ db: Database,
|
||||
key: String,
|
||||
updatedSetting: Setting?
|
||||
) throws {
|
||||
// Before we do anything custom make sure the setting should trigger a change
|
||||
guard SessionUtil.syncedSettings.contains(key) else { return }
|
||||
|
||||
defer {
|
||||
// If we changed a column that requires a config update then we may as well automatically
|
||||
// enqueue a new config sync job once the transaction completes (but only enqueue it once
|
||||
// per transaction - doing it more than once is pointless)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
||||
db.afterNextTransactionNestedOnce(dedupeId: SessionUtil.syncDedupeId(userPublicKey)) { db in
|
||||
ConfigurationSyncJob.enqueue(db, publicKey: userPublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
try SessionUtil.updatingSetting(db, updatedSetting)
|
||||
}
|
||||
}
|
|
@ -104,7 +104,11 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat
|
|||
|
||||
public var canWrite: Bool {
|
||||
switch threadVariant {
|
||||
case .contact: return true
|
||||
case .contact:
|
||||
guard threadIsMessageRequest == true else { return true }
|
||||
|
||||
return (profile?.blocksCommunityMessageRequests != true)
|
||||
|
||||
case .legacyGroup, .group:
|
||||
return (
|
||||
currentUserIsClosedGroupMember == true &&
|
||||
|
|
|
@ -65,6 +65,9 @@ public extension Setting.BoolKey {
|
|||
/// Controls whether concurrent audio messages should automatically be played after the one the user starts
|
||||
/// playing finishes
|
||||
static let shouldAutoPlayConsecutiveAudioMessages: Setting.BoolKey = "shouldAutoPlayConsecutiveAudioMessages"
|
||||
|
||||
/// Controls whether the device will poll for community message requests (SOGS `/inbox` endpoint)
|
||||
static let checkForCommunityMessageRequests: Setting.BoolKey = "checkForCommunityMessageRequests"
|
||||
}
|
||||
|
||||
public extension Setting.StringKey {
|
||||
|
|
|
@ -498,6 +498,7 @@ public struct ProfileManager {
|
|||
_ db: Database,
|
||||
publicKey: String,
|
||||
name: String?,
|
||||
blocksCommunityMessageRequests: Bool? = nil,
|
||||
avatarUpdate: AvatarUpdate,
|
||||
sentTimestamp: TimeInterval,
|
||||
calledFromConfigHandling: Bool = false,
|
||||
|
@ -516,6 +517,12 @@ public struct ProfileManager {
|
|||
}
|
||||
}
|
||||
|
||||
// Blocks community message requets flag
|
||||
if let blocksCommunityMessageRequests: Bool = blocksCommunityMessageRequests, sentTimestamp > profile.lastBlocksCommunityMessageRequests {
|
||||
profileChanges.append(Profile.Columns.blocksCommunityMessageRequests.set(to: blocksCommunityMessageRequests))
|
||||
profileChanges.append(Profile.Columns.lastBlocksCommunityMessageRequests.set(to: sentTimestamp))
|
||||
}
|
||||
|
||||
// Profile picture & profile key
|
||||
var avatarNeedsDownload: Bool = false
|
||||
var targetAvatarUrl: String? = nil
|
||||
|
|
|
@ -285,6 +285,17 @@ class ConfigUserProfileSpec {
|
|||
)
|
||||
user_profile_set_pic(conf2, p2)
|
||||
|
||||
user_profile_set_nts_expiry(conf2, 86200)
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 0)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(0))
|
||||
user_profile_set_blinded_msgreqs(conf2, -1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(-1))
|
||||
user_profile_set_blinded_msgreqs(conf2, 1)
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
// Both have changes, so push need a push
|
||||
expect(config_needs_push(conf)).to(beTrue())
|
||||
expect(config_needs_push(conf2)).to(beTrue())
|
||||
|
@ -364,6 +375,10 @@ class ConfigUserProfileSpec {
|
|||
.to(equal("7177657274007975696f31323334353637383930313233343536373839303132"))
|
||||
expect(user_profile_get_nts_priority(conf)).to(equal(9))
|
||||
expect(user_profile_get_nts_priority(conf2)).to(equal(9))
|
||||
expect(user_profile_get_nts_expiry(conf)).to(equal(86400))
|
||||
expect(user_profile_get_nts_expiry(conf2)).to(equal(86400))
|
||||
expect(user_profile_get_blinded_msgreqs(conf)).to(equal(1))
|
||||
expect(user_profile_get_blinded_msgreqs(conf2)).to(equal(1))
|
||||
|
||||
let fakeHash4: String = "fakehash4"
|
||||
var cFakeHash4: [CChar] = fakeHash4.cArray.nullTerminated()
|
||||
|
|
|
@ -211,11 +211,11 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
|
|||
}
|
||||
|
||||
func shareViewWasCompleted() {
|
||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
|
||||
func shareViewWasCancelled() {
|
||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
|
||||
func shareViewFailed(error: Error) {
|
||||
|
|
|
@ -65,6 +65,23 @@ public extension Publisher {
|
|||
return self.receive(on: scheduler, options: options)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func manualRefreshFrom(_ refreshTrigger: some Publisher<Void, Never>) -> AnyPublisher<Output, Failure> {
|
||||
return Publishers
|
||||
.CombineLatest(refreshTrigger.prepend(()).setFailureType(to: Failure.self), self)
|
||||
.map { _, value in value }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func withPrevious() -> AnyPublisher<(previous: Output?, current: Output), Failure> {
|
||||
scan(Optional<(Output?, Output)>.none) { ($0?.1, $1) }
|
||||
.compactMap { $0 }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func withPrevious(_ initialPreviousValue: Output) -> AnyPublisher<(previous: Output, current: Output), Failure> {
|
||||
scan((initialPreviousValue, initialPreviousValue)) { ($0.1, $1) }.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
|
|
@ -15,6 +15,7 @@ public struct Setting: Codable, Identifiable, FetchableRecord, PersistableRecord
|
|||
}
|
||||
|
||||
public var id: String { key }
|
||||
public var rawValue: Data { value }
|
||||
|
||||
let key: String
|
||||
let value: Data
|
||||
|
@ -53,7 +54,7 @@ extension Setting {
|
|||
self.value = Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue))
|
||||
}
|
||||
|
||||
fileprivate func value(as type: Bool.Type) -> Bool? {
|
||||
public func unsafeValue(as type: Bool.Type) -> Bool? {
|
||||
// Note: The 'assumingMemoryBound' is essentially going to try to convert
|
||||
// the memory into the provided type so can result in invalid data being
|
||||
// returned if the type is incorrect. But it does seem safer than the 'load'
|
||||
|
@ -189,7 +190,7 @@ public extension Database {
|
|||
subscript(key: Setting.BoolKey) -> Bool {
|
||||
get {
|
||||
// Default to false if it doesn't exist
|
||||
(self[key.rawValue]?.value(as: Bool.self) ?? false)
|
||||
(self[key.rawValue]?.unsafeValue(as: Bool.self) ?? false)
|
||||
}
|
||||
set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) }
|
||||
}
|
||||
|
@ -245,4 +246,47 @@ public extension Database {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
func setting(key: Setting.BoolKey, to newValue: Bool) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func setting(key: Setting.DoubleKey, to newValue: Double?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func setting(key: Setting.IntKey, to newValue: Int?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func setting(key: Setting.StringKey, to newValue: String?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func setting<T: EnumIntSetting>(key: Setting.EnumKey, to newValue: T?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func setting<T: EnumStringSetting>(key: Setting.EnumKey, to newValue: T?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue?.rawValue)
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
|
||||
/// Value will be stored as a timestamp in seconds since 1970
|
||||
func setting(key: Setting.DateKey, to newValue: Date?) -> Setting? {
|
||||
let result: Setting? = Setting(key: key.rawValue, value: newValue.map { $0.timeIntervalSince1970 })
|
||||
self[key.rawValue] = result
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue