diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 8080f52f7f..863a0ab475 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -729,6 +729,7 @@ data class Contact( ChatFeature.TimedMessages -> mergedPreferences.timedMessages.enabled.forUser ChatFeature.FullDelete -> mergedPreferences.fullDelete.enabled.forUser ChatFeature.Voice -> mergedPreferences.voice.enabled.forUser + ChatFeature.Calls -> mergedPreferences.calls.enabled.forUser } override val timedMessagesTTL: Int? get() = with(mergedPreferences.timedMessages) { if (enabled.forUser) userPreference.pref.ttl else null } override val displayName get() = localAlias.ifEmpty { profile.displayName } @@ -747,12 +748,14 @@ data class Contact( ChatFeature.TimedMessages -> mergedPreferences.timedMessages.contactPreference.allow != FeatureAllowed.NO ChatFeature.FullDelete -> mergedPreferences.fullDelete.contactPreference.allow != FeatureAllowed.NO ChatFeature.Voice -> mergedPreferences.voice.contactPreference.allow != FeatureAllowed.NO + ChatFeature.Calls -> mergedPreferences.calls.contactPreference.allow != FeatureAllowed.NO } fun userAllowsFeature(feature: ChatFeature): Boolean = when (feature) { ChatFeature.TimedMessages -> mergedPreferences.timedMessages.userPreference.pref.allow != FeatureAllowed.NO ChatFeature.FullDelete -> mergedPreferences.fullDelete.userPreference.pref.allow != FeatureAllowed.NO ChatFeature.Voice -> mergedPreferences.voice.userPreference.pref.allow != FeatureAllowed.NO + ChatFeature.Calls -> mergedPreferences.calls.userPreference.pref.allow != FeatureAllowed.NO } companion object { @@ -882,6 +885,7 @@ data class GroupInfo ( ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on ChatFeature.Voice -> fullGroupPreferences.voice.on + ChatFeature.Calls -> false } override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null } override val displayName get() = groupProfile.displayName diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index a0f1c5cf88..e3e79b28bb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -2410,14 +2410,16 @@ data class FullChatPreferences( val timedMessages: TimedMessagesPreference, val fullDelete: SimpleChatPreference, val voice: SimpleChatPreference, + val calls: SimpleChatPreference, ) { - fun toPreferences(): ChatPreferences = ChatPreferences(timedMessages = timedMessages, fullDelete = fullDelete, voice = voice) + fun toPreferences(): ChatPreferences = ChatPreferences(timedMessages = timedMessages, fullDelete = fullDelete, voice = voice, calls = calls) companion object { val sampleData = FullChatPreferences( timedMessages = TimedMessagesPreference(allow = FeatureAllowed.NO), fullDelete = SimpleChatPreference(allow = FeatureAllowed.NO), - voice = SimpleChatPreference(allow = FeatureAllowed.YES) + voice = SimpleChatPreference(allow = FeatureAllowed.YES), + calls = SimpleChatPreference(allow = FeatureAllowed.YES), ) } } @@ -2427,19 +2429,22 @@ data class ChatPreferences( val timedMessages: TimedMessagesPreference?, val fullDelete: SimpleChatPreference?, val voice: SimpleChatPreference?, + val calls: SimpleChatPreference?, ) { fun setAllowed(feature: ChatFeature, allowed: FeatureAllowed = FeatureAllowed.YES, param: Int? = null): ChatPreferences = when (feature) { ChatFeature.TimedMessages -> this.copy(timedMessages = TimedMessagesPreference(allow = allowed, ttl = param ?: this.timedMessages?.ttl)) ChatFeature.FullDelete -> this.copy(fullDelete = SimpleChatPreference(allow = allowed)) ChatFeature.Voice -> this.copy(voice = SimpleChatPreference(allow = allowed)) + ChatFeature.Calls -> this.copy(calls = SimpleChatPreference(allow = allowed)) } companion object { val sampleData = ChatPreferences( timedMessages = TimedMessagesPreference(allow = FeatureAllowed.NO), fullDelete = SimpleChatPreference(allow = FeatureAllowed.NO), - voice = SimpleChatPreference(allow = FeatureAllowed.YES) + voice = SimpleChatPreference(allow = FeatureAllowed.YES), + calls = SimpleChatPreference(allow = FeatureAllowed.YES), ) } } @@ -2511,11 +2516,13 @@ data class ContactUserPreferences( val timedMessages: ContactUserPreferenceTimed, val fullDelete: ContactUserPreference, val voice: ContactUserPreference, + val calls: ContactUserPreference, ) { fun toPreferences(): ChatPreferences = ChatPreferences( timedMessages = timedMessages.userPreference.pref, fullDelete = fullDelete.userPreference.pref, - voice = voice.userPreference.pref + voice = voice.userPreference.pref, + calls = calls.userPreference.pref ) companion object { @@ -2534,7 +2541,12 @@ data class ContactUserPreferences( enabled = FeatureEnabled(forUser = true, forContact = true), userPreference = ContactUserPref.User(preference = SimpleChatPreference(allow = FeatureAllowed.YES)), contactPreference = SimpleChatPreference(allow = FeatureAllowed.YES) - ) + ), + calls = ContactUserPreference( + enabled = FeatureEnabled(forUser = true, forContact = true), + userPreference = ContactUserPref.User(preference = SimpleChatPreference(allow = FeatureAllowed.YES)), + contactPreference = SimpleChatPreference(allow = FeatureAllowed.YES) + ), ) } } @@ -2620,7 +2632,8 @@ interface Feature { enum class ChatFeature: Feature { @SerialName("timedMessages") TimedMessages, @SerialName("fullDelete") FullDelete, - @SerialName("voice") Voice; + @SerialName("voice") Voice, + @SerialName("calls") Calls; val asymmetric: Boolean get() = when (this) { TimedMessages -> false @@ -2637,6 +2650,7 @@ enum class ChatFeature: Feature { TimedMessages -> generalGetString(R.string.timed_messages) FullDelete -> generalGetString(R.string.full_deletion) Voice -> generalGetString(R.string.voice_messages) + Calls -> generalGetString(R.string.audio_video_calls) } val icon: ImageVector @@ -2644,6 +2658,7 @@ enum class ChatFeature: Feature { TimedMessages -> Icons.Outlined.Timer FullDelete -> Icons.Outlined.DeleteForever Voice -> Icons.Outlined.KeyboardVoice + Calls -> Icons.Outlined.Phone } override val iconFilled: ImageVector @@ -2651,6 +2666,7 @@ enum class ChatFeature: Feature { TimedMessages -> Icons.Filled.Timer FullDelete -> Icons.Filled.DeleteForever Voice -> Icons.Filled.KeyboardVoice + Calls -> Icons.Filled.Phone } fun allowDescription(allowed: FeatureAllowed): String = @@ -2670,6 +2686,11 @@ enum class ChatFeature: Feature { FeatureAllowed.YES -> generalGetString(R.string.allow_voice_messages_only_if) FeatureAllowed.NO -> generalGetString(R.string.prohibit_sending_voice_messages) } + Calls -> when (allowed) { + FeatureAllowed.ALWAYS -> generalGetString(R.string.allow_your_contacts_to_call) + FeatureAllowed.YES -> generalGetString(R.string.allow_calls_only_if) + FeatureAllowed.NO -> generalGetString(R.string.prohibit_calls) + } } fun enabledDescription(enabled: FeatureEnabled): String = @@ -2692,7 +2713,13 @@ enum class ChatFeature: Feature { enabled.forContact -> generalGetString(R.string.only_your_contact_can_send_voice) else -> generalGetString(R.string.voice_prohibited_in_this_chat) } - } + Calls -> when { + enabled.forUser && enabled.forContact -> generalGetString(R.string.both_you_and_your_contact_can_make_calls) + enabled.forUser -> generalGetString(R.string.only_you_can_make_calls) + enabled.forContact -> generalGetString(R.string.only_your_contact_can_make_calls) + else -> generalGetString(R.string.calls_prohibited_with_this_contact) + } + } } @Serializable @@ -2805,14 +2832,16 @@ data class ContactFeaturesAllowed( val timedMessagesAllowed: Boolean, val timedMessagesTTL: Int?, val fullDelete: ContactFeatureAllowed, - val voice: ContactFeatureAllowed + val voice: ContactFeatureAllowed, + val calls: ContactFeatureAllowed, ) { companion object { val sampleData = ContactFeaturesAllowed( timedMessagesAllowed = false, timedMessagesTTL = null, fullDelete = ContactFeatureAllowed.UserDefault(FeatureAllowed.NO), - voice = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES) + voice = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), + calls = ContactFeatureAllowed.UserDefault(FeatureAllowed.YES), ) } } @@ -2824,7 +2853,8 @@ fun contactUserPrefsToFeaturesAllowed(contactUserPreferences: ContactUserPrefere timedMessagesAllowed = allow == FeatureAllowed.YES || allow == FeatureAllowed.ALWAYS, timedMessagesTTL = pref.pref.ttl, fullDelete = contactUserPrefToFeatureAllowed(contactUserPreferences.fullDelete), - voice = contactUserPrefToFeatureAllowed(contactUserPreferences.voice) + voice = contactUserPrefToFeatureAllowed(contactUserPreferences.voice), + calls = contactUserPrefToFeatureAllowed(contactUserPreferences.calls), ) } @@ -2842,7 +2872,8 @@ fun contactFeaturesAllowedToPrefs(contactFeaturesAllowed: ContactFeaturesAllowed ChatPreferences( timedMessages = TimedMessagesPreference(if (contactFeaturesAllowed.timedMessagesAllowed) FeatureAllowed.YES else FeatureAllowed.NO, contactFeaturesAllowed.timedMessagesTTL), fullDelete = contactFeatureAllowedToPref(contactFeaturesAllowed.fullDelete), - voice = contactFeatureAllowedToPref(contactFeaturesAllowed.voice) + voice = contactFeatureAllowedToPref(contactFeaturesAllowed.voice), + calls = contactFeatureAllowedToPref(contactFeaturesAllowed.calls), ) fun contactFeatureAllowedToPref(contactFeatureAllowed: ContactFeatureAllowed): SimpleChatPreference? = diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 121c5a6cd3..ef60e219f6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -398,7 +398,7 @@ fun ChatInfoToolbar( }) } - if (chat.chatInfo is ChatInfo.Direct) { + if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.allowsFeature(ChatFeature.Calls)) { barButtons.add { IconButton({ showMenu = false diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt index 75bec92bf8..84c9b23362 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt @@ -104,6 +104,11 @@ private fun ContactPreferencesLayout( applyPrefs(featuresAllowed.copy(voice = it)) } SectionSpacer() + val allowCalls: MutableState = remember(featuresAllowed) { mutableStateOf(featuresAllowed.calls) } + FeatureSection(ChatFeature.Calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, allowCalls) { + applyPrefs(featuresAllowed.copy(calls = it)) + } + SectionSpacer() ResetSaveButtons( reset = reset, save = savePrefs, @@ -138,6 +143,7 @@ private fun FeatureSection( ContactFeatureAllowed.values(userDefault).map { it to it.text }, allowFeature, icon = null, + enabled = remember { mutableStateOf(feature != ChatFeature.Calls) }, onSelected = onSelected ) } @@ -147,7 +153,7 @@ private fun FeatureSection( pref.contactPreference.allow.text ) } - SectionTextFooter(feature.enabledDescription(enabled)) + SectionTextFooter(feature.enabledDescription(enabled) + (if (feature == ChatFeature.Calls) generalGetString(R.string.available_in_v51) else "")) } @Composable diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt index 113e454b1f..e5de1edac0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/Preferences.kt @@ -80,6 +80,11 @@ private fun PreferencesLayout( applyPrefs(preferences.copy(voice = SimpleChatPreference(allow = it))) } SectionSpacer() + val allowCalls = remember(preferences) { mutableStateOf(preferences.calls.allow) } + FeatureSection(ChatFeature.Calls, allowCalls) { + applyPrefs(preferences.copy(calls = SimpleChatPreference(allow = it))) + } + SectionSpacer() ResetSaveButtons( reset = reset, save = savePrefs, @@ -97,11 +102,12 @@ private fun FeatureSection(feature: ChatFeature, allowFeature: StateDirect messages Delete for everyone Voice messages + Audio/video calls + \nAvailable in v5.1 enabled enabled for you enabled for contact @@ -1192,6 +1194,9 @@ Allow your contacts to send voice messages. Allow voice messages only if your contact allows them. Prohibit sending voice messages. + Allow your contacts to call you. + Allow calls only if your contact allows them. + Prohibit audio/video calls. Both you and your contact can send disappearing messages. Only you can send disappearing messages. Only your contact can send disappearing messages. @@ -1204,6 +1209,10 @@ Only you can send voice messages. Only your contact can send voice messages. Voice messages are prohibited in this chat. + Both you and your contact can make calls. + Only you can make calls. + Only your contact can make calls. + Audio/video calls are prohibited. Allow to send disappearing messages. Prohibit sending disappearing messages. Allow sending direct messages to members. diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 59783f25fa..9c16badf6f 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -140,12 +140,16 @@ struct ChatView: View { switch cInfo { case let .direct(contact): HStack { - callButton(contact, .audio, imageName: "phone") + if contact.allowsFeature(.calls) { + callButton(contact, .audio, imageName: "phone") + } Menu { - Button { - CallController.shared.startCall(contact, .video) - } label: { - Label("Video call", systemImage: "video") + if contact.allowsFeature(.calls) { + Button { + CallController.shared.startCall(contact, .video) + } label: { + Label("Video call", systemImage: "video") + } } searchButton() toggleNtfsButton(chat) diff --git a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift index af16d221e2..d0306c4cd4 100644 --- a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift @@ -25,6 +25,7 @@ struct ContactPreferencesView: View { timedMessagesFeatureSection() featureSection(.fullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, $featuresAllowed.fullDelete) featureSection(.voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, $featuresAllowed.voice) + featureSection(.calls, user.fullPreferences.calls.allow, contact.mergedPreferences.calls, $featuresAllowed.calls).disabled(true) Section { Button("Reset") { featuresAllowed = currentFeaturesAllowed } @@ -106,7 +107,7 @@ struct ContactPreferencesView: View { } private func featureFooter(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> some View { - Text(feature.enabledDescription(enabled)) + (Text(feature.enabledDescription(enabled)) + (feature == .calls ? Text("\nAvailable in v5.1").bold() : Text(""))) .frame(height: 36, alignment: .topLeading) } diff --git a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift index fbce7193fa..4869c3bde3 100644 --- a/apps/ios/Shared/Views/UserSettings/PreferencesView.swift +++ b/apps/ios/Shared/Views/UserSettings/PreferencesView.swift @@ -21,6 +21,7 @@ struct PreferencesView: View { timedMessagesFeatureSection($preferences.timedMessages.allow) featureSection(.fullDelete, $preferences.fullDelete.allow) featureSection(.voice, $preferences.voice.allow) + featureSection(.calls, $preferences.calls.allow).disabled(true) Section { Button("Reset") { preferences = currentPreferences } @@ -60,7 +61,7 @@ struct PreferencesView: View { } private func featureFooter(_ feature: ChatFeature, _ allowFeature: Binding) -> some View { - Text(feature.allowDescription(allowFeature.wrappedValue)) + (Text(feature.allowDescription(allowFeature.wrappedValue)) + (feature == .calls ? Text("\nAvailable in v5.1").bold() : Text(""))) .frame(height: 36, alignment: .topLeading) } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 516d7ec8fd..a89ef26fed 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -162,17 +162,20 @@ public struct FullPreferences: Decodable, Equatable { public var timedMessages: TimedMessagesPreference public var fullDelete: SimplePreference public var voice: SimplePreference + public var calls: SimplePreference - public init(timedMessages: TimedMessagesPreference, fullDelete: SimplePreference, voice: SimplePreference) { + public init(timedMessages: TimedMessagesPreference, fullDelete: SimplePreference, voice: SimplePreference, calls: SimplePreference) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.voice = voice + self.calls = calls } public static let sampleData = FullPreferences( timedMessages: TimedMessagesPreference(allow: .no), fullDelete: SimplePreference(allow: .no), - voice: SimplePreference(allow: .yes) + voice: SimplePreference(allow: .yes), + calls: SimplePreference(allow: .yes) ) } @@ -180,18 +183,21 @@ public struct Preferences: Codable { public var timedMessages: TimedMessagesPreference? public var fullDelete: SimplePreference? public var voice: SimplePreference? + public var calls: SimplePreference? - public init(timedMessages: TimedMessagesPreference?, fullDelete: SimplePreference?, voice: SimplePreference?) { + public init(timedMessages: TimedMessagesPreference?, fullDelete: SimplePreference?, voice: SimplePreference?, calls: SimplePreference?) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.voice = voice + self.calls = calls } - func copy(timedMessages: TimedMessagesPreference? = nil, fullDelete: SimplePreference? = nil, voice: SimplePreference? = nil) -> Preferences { + func copy(timedMessages: TimedMessagesPreference? = nil, fullDelete: SimplePreference? = nil, voice: SimplePreference? = nil, calls: SimplePreference? = nil) -> Preferences { Preferences( timedMessages: timedMessages ?? self.timedMessages, fullDelete: fullDelete ?? self.fullDelete, - voice: voice ?? self.voice + voice: voice ?? self.voice, + calls: calls ?? self.calls ) } @@ -200,13 +206,15 @@ public struct Preferences: Codable { case .timedMessages: return copy(timedMessages: TimedMessagesPreference(allow: allowed, ttl: param ?? timedMessages?.ttl)) case .fullDelete: return copy(fullDelete: SimplePreference(allow: allowed)) case .voice: return copy(voice: SimplePreference(allow: allowed)) + case .calls: return copy(calls: SimplePreference(allow: allowed)) } } public static let sampleData = Preferences( timedMessages: TimedMessagesPreference(allow: .no), fullDelete: SimplePreference(allow: .no), - voice: SimplePreference(allow: .yes) + voice: SimplePreference(allow: .yes), + calls: SimplePreference(allow: .yes) ) } @@ -214,7 +222,8 @@ public func fullPreferencesToPreferences(_ fullPreferences: FullPreferences) -> Preferences( timedMessages: fullPreferences.timedMessages, fullDelete: fullPreferences.fullDelete, - voice: fullPreferences.voice + voice: fullPreferences.voice, + calls: fullPreferences.calls ) } @@ -222,7 +231,8 @@ public func contactUserPreferencesToPreferences(_ contactUserPreferences: Contac Preferences( timedMessages: contactUserPreferences.timedMessages.userPreference.preference, fullDelete: contactUserPreferences.fullDelete.userPreference.preference, - voice: contactUserPreferences.voice.userPreference.preference + voice: contactUserPreferences.voice.userPreference.preference, + calls: contactUserPreferences.calls.userPreference.preference ) } @@ -308,15 +318,18 @@ public struct ContactUserPreferences: Decodable { public var timedMessages: ContactUserPreference public var fullDelete: ContactUserPreference public var voice: ContactUserPreference + public var calls: ContactUserPreference public init( timedMessages: ContactUserPreference, fullDelete: ContactUserPreference, - voice: ContactUserPreference + voice: ContactUserPreference, + calls: ContactUserPreference ) { self.timedMessages = timedMessages self.fullDelete = fullDelete self.voice = voice + self.calls = calls } public static let sampleData = ContactUserPreferences( @@ -334,6 +347,11 @@ public struct ContactUserPreferences: Decodable { enabled: FeatureEnabled(forUser: true, forContact: true), userPreference: ContactUserPref.user(preference: SimplePreference(allow: .yes)), contactPreference: SimplePreference(allow: .yes) + ), + calls: ContactUserPreference( + enabled: FeatureEnabled(forUser: true, forContact: true), + userPreference: ContactUserPref.user(preference: SimplePreference(allow: .yes)), + contactPreference: SimplePreference(allow: .yes) ) ) } @@ -405,6 +423,7 @@ public enum ChatFeature: String, Decodable, Feature { case timedMessages case fullDelete case voice + case calls public var values: [ChatFeature] { [.fullDelete, .voice] } @@ -429,6 +448,7 @@ public enum ChatFeature: String, Decodable, Feature { case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature") case .fullDelete: return NSLocalizedString("Delete for everyone", comment: "chat feature") case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") + case .calls: return NSLocalizedString("Audio/video calls", comment: "chat feature") } } @@ -437,6 +457,7 @@ public enum ChatFeature: String, Decodable, Feature { case .timedMessages: return "stopwatch" case .fullDelete: return "trash.slash" case .voice: return "mic" + case .calls: return "phone" } } @@ -445,6 +466,7 @@ public enum ChatFeature: String, Decodable, Feature { case .timedMessages: return "stopwatch.fill" case .fullDelete: return "trash.slash.fill" case .voice: return "mic.fill" + case .calls: return "phone.fill" } } @@ -475,6 +497,12 @@ public enum ChatFeature: String, Decodable, Feature { case .yes: return "Allow voice messages only if your contact allows them." case .no: return "Prohibit sending voice messages." } + case .calls: + switch allowed { + case .always: return "Allow your contacts to call you." + case .yes: return "Allow calls only if your contact allows them." + case .no: return "Prohibit audio/video calls." + } } } @@ -504,6 +532,14 @@ public enum ChatFeature: String, Decodable, Feature { : enabled.forContact ? "Only your contact can send voice messages." : "Voice messages are prohibited in this chat." + case .calls: + return enabled.forUser && enabled.forContact + ? "Both you and your contact can make calls." + : enabled.forUser + ? "Only you can make calls." + : enabled.forContact + ? "Only your contact can make calls." + : "Audio/video calls are prohibited." } } } @@ -646,19 +682,22 @@ public struct ContactFeaturesAllowed: Equatable { public var timedMessagesTTL: Int? public var fullDelete: ContactFeatureAllowed public var voice: ContactFeatureAllowed + public var calls: ContactFeatureAllowed - public init(timedMessagesAllowed: Bool, timedMessagesTTL: Int?, fullDelete: ContactFeatureAllowed, voice: ContactFeatureAllowed) { + public init(timedMessagesAllowed: Bool, timedMessagesTTL: Int?, fullDelete: ContactFeatureAllowed, voice: ContactFeatureAllowed, calls: ContactFeatureAllowed) { self.timedMessagesAllowed = timedMessagesAllowed self.timedMessagesTTL = timedMessagesTTL self.fullDelete = fullDelete self.voice = voice + self.calls = calls } public static let sampleData = ContactFeaturesAllowed( timedMessagesAllowed: false, timedMessagesTTL: nil, fullDelete: ContactFeatureAllowed.userDefault(.no), - voice: ContactFeatureAllowed.userDefault(.yes) + voice: ContactFeatureAllowed.userDefault(.yes), + calls: ContactFeatureAllowed.userDefault(.yes) ) } @@ -669,7 +708,8 @@ public func contactUserPrefsToFeaturesAllowed(_ contactUserPreferences: ContactU timedMessagesAllowed: allow == .yes || allow == .always, timedMessagesTTL: pref.preference.ttl, fullDelete: contactUserPrefToFeatureAllowed(contactUserPreferences.fullDelete), - voice: contactUserPrefToFeatureAllowed(contactUserPreferences.voice) + voice: contactUserPrefToFeatureAllowed(contactUserPreferences.voice), + calls: contactUserPrefToFeatureAllowed(contactUserPreferences.calls) ) } @@ -689,7 +729,8 @@ public func contactFeaturesAllowedToPrefs(_ contactFeaturesAllowed: ContactFeatu Preferences( timedMessages: TimedMessagesPreference(allow: contactFeaturesAllowed.timedMessagesAllowed ? .yes : .no, ttl: contactFeaturesAllowed.timedMessagesTTL), fullDelete: contactFeatureAllowedToPref(contactFeaturesAllowed.fullDelete), - voice: contactFeatureAllowedToPref(contactFeaturesAllowed.voice) + voice: contactFeatureAllowedToPref(contactFeaturesAllowed.voice), + calls: contactFeatureAllowedToPref(contactFeaturesAllowed.calls) ) } @@ -977,6 +1018,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat { case .timedMessages: return cups.timedMessages.enabled.forUser case .fullDelete: return cups.fullDelete.enabled.forUser case .voice: return cups.voice.enabled.forUser + case .calls: return cups.calls.enabled.forUser } case let .group(groupInfo): let prefs = groupInfo.fullGroupPreferences @@ -984,6 +1026,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat { case .timedMessages: return prefs.timedMessages.on case .fullDelete: return prefs.fullDelete.on case .voice: return prefs.voice.on + case .calls: return false } default: return false } @@ -1137,6 +1180,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { case .timedMessages: return mergedPreferences.timedMessages.contactPreference.allow != .no case .fullDelete: return mergedPreferences.fullDelete.contactPreference.allow != .no case .voice: return mergedPreferences.voice.contactPreference.allow != .no + case .calls: return mergedPreferences.calls.contactPreference.allow != .no } } @@ -1145,6 +1189,7 @@ public struct Contact: Identifiable, Decodable, NamedChat { case .timedMessages: return mergedPreferences.timedMessages.userPreference.preference.allow != .no case .fullDelete: return mergedPreferences.fullDelete.userPreference.preference.allow != .no case .voice: return mergedPreferences.voice.userPreference.preference.allow != .no + case .calls: return mergedPreferences.calls.userPreference.preference.allow != .no } }