core, mobile: add group feature to allow direct messages (#1465)

* core, mobile: split group features to a separate type (to add directAllowed later)

* add directMessages group feature, update tests
This commit is contained in:
Evgeny Poberezkin
2022-11-29 15:19:20 +00:00
committed by GitHub
parent 303aeaaba5
commit 1872744543
25 changed files with 334 additions and 164 deletions

View File

@@ -1098,6 +1098,7 @@ data class ChatItem (
is CIContent.RcvGroupFeature -> false
is CIContent.SndGroupFeature -> showNtfDir
is CIContent.RcvChatFeatureRejected -> showNtfDir
is CIContent.RcvGroupFeatureRejected -> showNtfDir
}
fun withStatus(status: CIStatus): ChatItem = this.copy(meta = meta.copy(itemStatus = status))
@@ -1171,7 +1172,7 @@ data class ChatItem (
file = null
)
fun getChatFeatureSample(feature: Feature, enabled: FeatureEnabled): ChatItem {
fun getChatFeatureSample(feature: ChatFeature, enabled: FeatureEnabled): ChatItem {
val content = CIContent.RcvChatFeature(feature = feature, enabled = enabled)
return ChatItem(
chatDir = CIDirection.DirectRcv(),
@@ -1277,11 +1278,12 @@ sealed class CIContent: ItemContent {
@Serializable @SerialName("sndGroupEvent") class SndGroupEventContent(val sndGroupEvent: SndGroupEvent): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvConnEvent") class RcvConnEventContent(val rcvConnEvent: RcvConnEvent): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndConnEvent") class SndConnEventContent(val sndConnEvent: SndConnEvent): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvChatFeature") class RcvChatFeature(val feature: Feature, val enabled: FeatureEnabled): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndChatFeature") class SndChatFeature(val feature: Feature, val enabled: FeatureEnabled): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvGroupFeature") class RcvGroupFeature(val feature: Feature, val preference: GroupPreference): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val feature: Feature, val preference: GroupPreference): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: Feature): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvChatFeature") class RcvChatFeature(val feature: ChatFeature, val enabled: FeatureEnabled): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndChatFeature") class SndChatFeature(val feature: ChatFeature, val enabled: FeatureEnabled): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvGroupFeature") class RcvGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: ChatFeature): CIContent() { override val msgContent: MsgContent? get() = null }
@Serializable @SerialName("rcvGroupFeatureRejected") class RcvGroupFeatureRejected(val groupFeature: GroupFeature): CIContent() { override val msgContent: MsgContent? get() = null }
override val text: String get() = when (this) {
is SndMsgContent -> msgContent.text
@@ -1299,9 +1301,10 @@ sealed class CIContent: ItemContent {
is SndConnEventContent -> sndConnEvent.text
is RcvChatFeature -> "${feature.text}: ${enabled.text}"
is SndChatFeature -> "${feature.text}: ${enabled.text}"
is RcvGroupFeature -> "${feature.text}: ${preference.enable.text}"
is SndGroupFeature -> "${feature.text}: ${preference.enable.text}"
is RcvGroupFeature -> "${groupFeature.text}: ${preference.enable.text}"
is SndGroupFeature -> "${groupFeature.text}: ${preference.enable.text}"
is RcvChatFeatureRejected -> "${feature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
is RcvGroupFeatureRejected -> "${groupFeature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
}
}

View File

@@ -12,8 +12,7 @@ import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.KeyboardVoice
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -2048,12 +2047,18 @@ sealed class ContactUserPref {
@Serializable @SerialName("user") data class User(val preference: ChatPreference): ContactUserPref() // global user default is used
}
interface Feature {
// val icon: ImageVector
val text: String
val iconFilled: ImageVector
}
@Serializable
enum class Feature {
enum class ChatFeature: Feature {
@SerialName("fullDelete") FullDelete,
@SerialName("voice") Voice;
val text: String
override val text: String
get() = when(this) {
FullDelete -> generalGetString(R.string.full_deletion)
Voice -> generalGetString(R.string.voice_messages)
@@ -2065,7 +2070,7 @@ enum class Feature {
Voice -> Icons.Outlined.KeyboardVoice
}
val iconFilled: ImageVector
override val iconFilled: ImageVector
get() = when(this) {
FullDelete -> Icons.Filled.DeleteForever
Voice -> Icons.Filled.KeyboardVoice
@@ -2100,31 +2105,67 @@ enum class Feature {
else -> generalGetString(R.string.voice_prohibited_in_this_chat)
}
}
}
fun enableGroupPrefDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
if (canEdit) {
when(this) {
FullDelete -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_delete_messages)
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_message_deletion)
@Serializable
enum class GroupFeature: Feature {
@SerialName("directMessages") DirectMessages,
@SerialName("fullDelete") FullDelete,
@SerialName("voice") Voice;
override val text: String
get() = when(this) {
DirectMessages -> generalGetString(R.string.direct_messages)
FullDelete -> generalGetString(R.string.full_deletion)
Voice -> generalGetString(R.string.voice_messages)
}
val icon: ImageVector
get() = when(this) {
DirectMessages -> Icons.Outlined.SwapHorizontalCircle
FullDelete -> Icons.Outlined.DeleteForever
Voice -> Icons.Outlined.KeyboardVoice
}
override val iconFilled: ImageVector
get() = when(this) {
DirectMessages -> Icons.Filled.SwapHorizontalCircle
FullDelete -> Icons.Filled.DeleteForever
Voice -> Icons.Filled.KeyboardVoice
}
fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
if (canEdit) {
when(this) {
DirectMessages -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_direct_messages)
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_direct_messages)
}
FullDelete -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_delete_messages)
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_message_deletion)
}
Voice -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_send_voice)
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_sending_voice)
}
}
Voice -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.allow_to_send_voice)
GroupFeatureEnabled.OFF -> generalGetString(R.string.prohibit_sending_voice)
} else {
when(this) {
DirectMessages -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_send_dms)
GroupFeatureEnabled.OFF -> generalGetString(R.string.direct_messages_are_prohibited_in_chat)
}
FullDelete -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_delete)
GroupFeatureEnabled.OFF -> generalGetString(R.string.message_deletion_prohibited_in_chat)
}
Voice -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_send_voice)
GroupFeatureEnabled.OFF -> generalGetString(R.string.voice_messages_are_prohibited)
}
}
}
} else {
when(this) {
FullDelete -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_delete)
GroupFeatureEnabled.OFF -> generalGetString(R.string.message_deletion_prohibited_in_chat)
}
Voice -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(R.string.group_members_can_send_voice)
GroupFeatureEnabled.OFF -> generalGetString(R.string.voice_messages_are_prohibited)
}
}
}
}
@Serializable
@@ -2212,24 +2253,26 @@ enum class FeatureAllowed {
@Serializable
data class FullGroupPreferences(
val directMessages: GroupPreference,
val fullDelete: GroupPreference,
val voice: GroupPreference
) {
fun toGroupPreferences(): GroupPreferences =
GroupPreferences(fullDelete = fullDelete, voice = voice)
GroupPreferences(directMessages = directMessages, fullDelete = fullDelete, voice = voice)
companion object {
val sampleData = FullGroupPreferences(fullDelete = GroupPreference(enable = GroupFeatureEnabled.OFF), voice = GroupPreference(enable = GroupFeatureEnabled.ON))
val sampleData = FullGroupPreferences(directMessages = GroupPreference(GroupFeatureEnabled.OFF), fullDelete = GroupPreference(GroupFeatureEnabled.OFF), voice = GroupPreference(GroupFeatureEnabled.ON))
}
}
@Serializable
data class GroupPreferences(
val directMessages: GroupPreference?,
val fullDelete: GroupPreference?,
val voice: GroupPreference?
) {
companion object {
val sampleData = GroupPreferences(fullDelete = GroupPreference(enable = GroupFeatureEnabled.OFF), voice = GroupPreference(enable = GroupFeatureEnabled.ON))
val sampleData = GroupPreferences(directMessages = GroupPreference(GroupFeatureEnabled.OFF), fullDelete = GroupPreference(GroupFeatureEnabled.OFF), voice = GroupPreference(GroupFeatureEnabled.ON))
}
}

View File

@@ -78,7 +78,7 @@ private fun ContactPreferencesLayout(
// }
// SectionSpacer()
val allowVoice: MutableState<ContactFeatureAllowed> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.voice) }
FeatureSection(Feature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
FeatureSection(ChatFeature.Voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, allowVoice) {
applyPrefs(featuresAllowed.copy(voice = it))
}
SectionSpacer()
@@ -92,7 +92,7 @@ private fun ContactPreferencesLayout(
@Composable
private fun FeatureSection(
feature: Feature,
feature: ChatFeature,
userDefault: FeatureAllowed,
pref: ContactUserPreference,
allowFeature: State<ContactFeatureAllowed>,

View File

@@ -133,10 +133,12 @@ fun GroupMemberInfoLayout(
}
SectionSpacer()
SectionView {
OpenChatButton(openDirectChat)
if (member.memberContactId != null && groupInfo.fullGroupPreferences.directMessages.enable == GroupFeatureEnabled.ON) {
SectionView {
OpenChatButton(openDirectChat)
}
SectionSpacer()
}
SectionSpacer()
SectionView(title = stringResource(R.string.member_info_section_title_member)) {
InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName)

View File

@@ -62,13 +62,17 @@ private fun GroupPreferencesLayout(
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.group_preferences))
val allowDirectMessages = remember(preferences) { mutableStateOf(preferences.directMessages.enable) }
FeatureSection(GroupFeature.DirectMessages, allowDirectMessages, groupInfo) {
applyPrefs(preferences.copy(directMessages = GroupPreference(enable = it)))
}
// val allowFullDeletion = remember(preferences) { mutableStateOf(preferences.fullDelete.enable) }
// FeatureSection(Feature.FullDelete, allowFullDeletion, groupInfo) {
// applyPrefs(preferences.copy(fullDelete = GroupPreference(enable = it)))
// }
// SectionSpacer()
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.enable) }
FeatureSection(Feature.Voice, allowVoice, groupInfo) {
FeatureSection(GroupFeature.Voice, allowVoice, groupInfo) {
applyPrefs(preferences.copy(voice = GroupPreference(enable = it)))
}
if (groupInfo.canEdit) {
@@ -83,7 +87,7 @@ private fun GroupPreferencesLayout(
}
@Composable
private fun FeatureSection(feature: Feature, enableFeature: State<GroupFeatureEnabled>, groupInfo: GroupInfo, onSelected: (GroupFeatureEnabled) -> Unit) {
private fun FeatureSection(feature: GroupFeature, enableFeature: State<GroupFeatureEnabled>, groupInfo: GroupInfo, onSelected: (GroupFeatureEnabled) -> Unit) {
SectionView {
if (groupInfo.canEdit) {
SectionItemView {
@@ -102,7 +106,7 @@ private fun FeatureSection(feature: Feature, enableFeature: State<GroupFeatureEn
)
}
}
SectionTextFooter(feature.enableGroupPrefDescription(enableFeature.value, groupInfo.canEdit))
SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.canEdit))
}
@Composable

View File

@@ -172,8 +172,8 @@ fun ChatItemView(
is CIContent.SndConnEventContent -> CIEventView(cItem)
is CIContent.RcvChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor)
is CIContent.SndChatFeature -> CIChatFeatureView(cItem, c.feature, c.enabled.iconColor)
is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.feature, c.preference.enable.iconColor)
is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.feature, c.preference.enable.iconColor)
is CIContent.RcvGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor)
is CIContent.SndGroupFeature -> CIChatFeatureView(cItem, c.groupFeature, c.preference.enable.iconColor)
is CIContent.RcvChatFeatureRejected -> CIChatFeatureView(cItem, c.feature, Color.Red)
}
}

View File

@@ -67,7 +67,7 @@ private fun PreferencesLayout(
// }
// SectionSpacer()
val allowVoice = remember(preferences) { mutableStateOf(preferences.voice.allow) }
FeatureSection(Feature.Voice, allowVoice) {
FeatureSection(ChatFeature.Voice, allowVoice) {
applyPrefs(preferences.copy(voice = ChatPreference(allow = it)))
}
SectionSpacer()
@@ -80,7 +80,7 @@ private fun PreferencesLayout(
}
@Composable
private fun FeatureSection(feature: Feature, allowFeature: State<FeatureAllowed>, onSelected: (FeatureAllowed) -> Unit) {
private fun FeatureSection(feature: ChatFeature, allowFeature: State<FeatureAllowed>, onSelected: (FeatureAllowed) -> Unit) {
SectionView {
SectionItemView {
ExposedDropDownSettingRow(

View File

@@ -947,6 +947,7 @@
<string name="contact_preferences">Contact preferences</string>
<string name="group_preferences">Group preferences</string>
<string name="your_preferences">Your preferences</string>
<string name="direct_messages">Direct messages</string>
<string name="full_deletion">Full deletion</string>
<string name="voice_messages">Voice messages</string>
<string name="feature_enabled">enabled</string>
@@ -968,13 +969,17 @@
<string name="only_you_can_send_voice">Only you can send voice messages.</string>
<string name="only_your_contact_can_send_voice">Only your contact can send voice messages.</string>
<string name="voice_prohibited_in_this_chat">Voice messages are prohibited in this chat.</string>
<string name="allow_direct_messages">Allow sending direct messages to members.</string>
<string name="prohibit_direct_messages">Prohibit sending direct messages to members.</string>
<string name="allow_to_delete_messages">Allow to irreversibly delete sent messages.</string>
<string name="prohibit_message_deletion">Prohibit irreversible message deletion.</string>
<string name="allow_to_send_voice">Allow to send voice messages.</string>
<string name="prohibit_sending_voice">Prohibit sending voice messages.</string>
<string name="group_members_can_send_dms">Group members can send direct messages.</string>
<string name="direct_messages_are_prohibited_in_chat">Direct messages between members are prohibited in this group.</string>
<string name="group_members_can_delete">Group members can irreversibly delete sent messages.</string>
<string name="message_deletion_prohibited_in_chat">Irreversible message deletion is prohibited in this chat.</string>
<string name="message_deletion_prohibited_in_chat">Irreversible message deletion is prohibited in this group.</string>
<string name="group_members_can_send_voice">Group members can send voice messages.</string>
<string name="voice_messages_are_prohibited">Voice messages are prohibited in this chat.</string>
<string name="voice_messages_are_prohibited">Voice messages are prohibited in this group.</string>
</resources>

View File

@@ -29,6 +29,6 @@ struct CIChatFeatureView: View {
struct CIChatFeatureView_Previews: PreviewProvider {
static var previews: some View {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: .fullDelete, iconColor: enabled.iconColor)
CIChatFeatureView(chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor)
}
}

View File

@@ -36,6 +36,7 @@ struct ChatItemView: View {
case let .rcvGroupFeature(feature, preference): chatFeatureView(feature, preference.enable.iconColor)
case let .sndGroupFeature(feature, preference): chatFeatureView(feature, preference.enable.iconColor)
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
case let .rcvGroupFeatureRejected(feature): chatFeatureView(feature, .red)
}
}

View File

@@ -32,7 +32,7 @@ struct ContactPreferencesView: View {
}
}
private func featureSection(_ feature: Feature, _ userDefault: FeatureAllowed, _ pref: ContactUserPreference, _ allowFeature: Binding<ContactFeatureAllowed>) -> some View {
private func featureSection(_ feature: ChatFeature, _ userDefault: FeatureAllowed, _ pref: ContactUserPreference, _ allowFeature: Binding<ContactFeatureAllowed>) -> some View {
let enabled = FeatureEnabled.enabled(
user: Preference(allow: allowFeature.wrappedValue.allowed),
contact: pref.contactPreference

View File

@@ -42,7 +42,7 @@ struct GroupMemberInfoView: View {
groupMemberInfoHeader(member)
.listRowBackground(Color.clear)
if let contactId = member.memberContactId {
if let contactId = member.memberContactId, groupInfo.fullGroupPreferences.directMessages.enable == .on {
Section {
openDirectChatButton(contactId)
}

View File

@@ -19,6 +19,7 @@ struct GroupPreferencesView: View {
VStack {
List {
// featureSection(.fullDelete, $preferences.fullDelete.enable)
featureSection(.directMessages, $preferences.directMessages.enable)
featureSection(.voice, $preferences.voice.enable)
if groupInfo.canEdit {
@@ -32,7 +33,7 @@ struct GroupPreferencesView: View {
}
}
private func featureSection(_ feature: Feature, _ enableFeature: Binding<GroupFeatureEnabled>) -> some View {
private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding<GroupFeatureEnabled>) -> some View {
Section {
if (groupInfo.canEdit) {
settingsRow(feature.icon) {
@@ -50,7 +51,7 @@ struct GroupPreferencesView: View {
}
}
} footer: {
Text(feature.enableGroupPrefDescription(enableFeature.wrappedValue, groupInfo.canEdit))
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.canEdit))
.frame(height: 36, alignment: .topLeading)
}
}

View File

@@ -30,7 +30,7 @@ struct PreferencesView: View {
}
}
private func featureSection(_ feature: Feature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
private func featureSection(_ feature: ChatFeature, _ allowFeature: Binding<FeatureAllowed>) -> some View {
Section {
settingsRow(feature.icon) {
Picker(feature.text, selection: allowFeature) {

View File

@@ -245,11 +245,15 @@ public enum ContactUserPref: Decodable {
}
}
public enum Feature: String, Decodable {
public protocol Feature {
var iconFilled: String { get }
}
public enum ChatFeature: String, Decodable, Feature {
case fullDelete
case voice
public var values: [Feature] { [.fullDelete, .voice] }
public var values: [ChatFeature] { [.fullDelete, .voice] }
public var id: Self { self }
@@ -311,10 +315,49 @@ public enum Feature: String, Decodable {
: "Voice messages are prohibited in this chat."
}
}
}
public func enableGroupPrefDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey {
public enum GroupFeature: String, Decodable, Feature {
case fullDelete
case voice
case directMessages
public var values: [GroupFeature] { [.directMessages, .fullDelete, .voice] }
public var id: Self { self }
public var text: String {
switch self {
case .directMessages: return NSLocalizedString("Direct messages", comment: "chat feature")
case .fullDelete: return NSLocalizedString("Full deletion", comment: "chat feature")
case .voice: return NSLocalizedString("Voice messages", comment: "chat feature")
}
}
public var icon: String {
switch self {
case .directMessages: return "arrow.left.and.right.circle"
case .fullDelete: return "trash.slash"
case .voice: return "mic"
}
}
public var iconFilled: String {
switch self {
case .directMessages: return "arrow.left.and.right.circle.fill"
case .fullDelete: return "trash.slash.fill"
case .voice: return "mic.fill"
}
}
public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey {
if canEdit {
switch self {
case .directMessages:
switch enabled {
case .on: return "Allow sending direct messages to members."
case .off: return "Prohibit sending direct messages to members."
}
case .fullDelete:
switch enabled {
case .on: return "Allow to irreversibly delete sent messages."
@@ -328,15 +371,20 @@ public enum Feature: String, Decodable {
}
} else {
switch self {
case .directMessages:
switch enabled {
case .on: return "Group members can send direct messages."
case .off: return "Direct messages between members are prohibited in this group."
}
case .fullDelete:
switch enabled {
case .on: return "Group members can irreversibly delete sent messages."
case .off: return "Irreversible message deletion is prohibited in this chat."
case .off: return "Irreversible message deletion is prohibited in this group."
}
case .voice:
switch enabled {
case .on: return "Group members can send voice messages."
case .off: return "Voice messages are prohibited in this chat."
case .off: return "Voice messages are prohibited in this group."
}
}
}
@@ -443,31 +491,35 @@ public enum FeatureAllowed: String, Codable, Identifiable {
}
public struct FullGroupPreferences: Decodable, Equatable {
public var directMessages: GroupPreference
public var fullDelete: GroupPreference
public var voice: GroupPreference
public init(fullDelete: GroupPreference, voice: GroupPreference) {
public init(directMessages: GroupPreference, fullDelete: GroupPreference, voice: GroupPreference) {
self.directMessages = directMessages
self.fullDelete = fullDelete
self.voice = voice
}
public static let sampleData = FullGroupPreferences(fullDelete: GroupPreference(enable: .off), voice: GroupPreference(enable: .on))
public static let sampleData = FullGroupPreferences(directMessages: GroupPreference(enable: .off), fullDelete: GroupPreference(enable: .off), voice: GroupPreference(enable: .on))
}
public struct GroupPreferences: Codable {
public var directMessages: GroupPreference?
public var fullDelete: GroupPreference?
public var voice: GroupPreference?
public init(fullDelete: GroupPreference?, voice: GroupPreference?) {
public init(directMessages: GroupPreference?, fullDelete: GroupPreference?, voice: GroupPreference?) {
self.directMessages = directMessages
self.fullDelete = fullDelete
self.voice = voice
}
public static let sampleData = GroupPreferences(fullDelete: GroupPreference(enable: .off), voice: GroupPreference(enable: .on))
public static let sampleData = GroupPreferences(directMessages: GroupPreference(enable: .off), fullDelete: GroupPreference(enable: .off), voice: GroupPreference(enable: .on))
}
public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> GroupPreferences {
GroupPreferences(fullDelete: fullPreferences.fullDelete, voice: fullPreferences.voice)
GroupPreferences(directMessages: fullPreferences.directMessages, fullDelete: fullPreferences.fullDelete, voice: fullPreferences.voice)
}
public struct GroupPreference: Codable, Equatable {
@@ -1371,6 +1423,7 @@ public struct ChatItem: Identifiable, Decodable {
case .rcvGroupFeature: return false
case .sndGroupFeature: return showNtfDir
case .rcvChatFeatureRejected: return showNtfDir
case .rcvGroupFeatureRejected: return showNtfDir
}
}
@@ -1462,7 +1515,7 @@ public struct ChatItem: Identifiable, Decodable {
)
}
public static func getChatFeatureSample(_ feature: Feature, _ enabled: FeatureEnabled) -> ChatItem {
public static func getChatFeatureSample(_ feature: ChatFeature, _ enabled: FeatureEnabled) -> ChatItem {
let content = CIContent.rcvChatFeature(feature: feature, enabled: enabled)
return ChatItem(
chatDir: .directRcv,
@@ -1573,11 +1626,12 @@ public enum CIContent: Decodable, ItemContent {
case sndGroupEvent(sndGroupEvent: SndGroupEvent)
case rcvConnEvent(rcvConnEvent: RcvConnEvent)
case sndConnEvent(sndConnEvent: SndConnEvent)
case rcvChatFeature(feature: Feature, enabled: FeatureEnabled)
case sndChatFeature(feature: Feature, enabled: FeatureEnabled)
case rcvGroupFeature(feature: Feature, preference: GroupPreference)
case sndGroupFeature(feature: Feature, preference: GroupPreference)
case rcvChatFeatureRejected(feature: Feature)
case rcvChatFeature(feature: ChatFeature, enabled: FeatureEnabled)
case sndChatFeature(feature: ChatFeature, enabled: FeatureEnabled)
case rcvGroupFeature(groupFeature: GroupFeature, preference: GroupPreference)
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference)
case rcvChatFeatureRejected(feature: ChatFeature)
case rcvGroupFeatureRejected(groupFeature: GroupFeature)
public var text: String {
get {
@@ -1600,6 +1654,7 @@ public enum CIContent: Decodable, ItemContent {
case let .rcvGroupFeature(feature, preference): return "\(feature.text): \(preference.enable.text)"
case let .sndGroupFeature(feature, preference): return "\(feature.text): \(preference.enable.text)"
case let .rcvChatFeatureRejected(feature): return String.localizedStringWithFormat("%@: received, prohibited", feature.text)
case let .rcvGroupFeatureRejected(groupFeature): return String.localizedStringWithFormat("%@: received, prohibited", groupFeature.text)
}
}
}

View File

@@ -63,6 +63,7 @@ library
Simplex.Chat.Migrations.M20221029_group_link_id
Simplex.Chat.Migrations.M20221112_server_password
Simplex.Chat.Migrations.M20221115_server_cfg
Simplex.Chat.Migrations.M20221129_delete_group_feature_items
Simplex.Chat.Mobile
Simplex.Chat.Options
Simplex.Chat.ProfileGenerator

View File

@@ -340,7 +340,7 @@ processChatCommand = \case
Group gInfo@GroupInfo {membership, localDisplayName = gName} ms <- withStore $ \db -> getGroup db user chatId
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
case groupFeatureProhibited gInfo mc of
Just f -> pure $ chatCmdError $ "feature not allowed " <> T.unpack (chatFeatureToText f)
Just f -> pure $ chatCmdError $ "feature not allowed " <> T.unpack (groupFeatureToText f)
_ -> do
(fileInvitation_, ciFile_, ft_) <- unzipMaybe3 <$> setupSndFileTransfer gInfo (length ms)
(msgContainer, quotedItem_) <- prepareMsg fileInvitation_ membership
@@ -2278,7 +2278,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
newGroupContentMessage gInfo@GroupInfo {chatSettings} m@GroupMember {localDisplayName = c} mc msg msgMeta = do
let (ExtMsgContent content fInv_) = mcExtMsgContent mc
case groupFeatureProhibited gInfo content of
Just f -> void $ newChatItem (CIRcvChatFeatureRejected f) Nothing
Just f -> void $ newChatItem (CIRcvGroupFeatureRejected f) Nothing
_ -> do
ciFile_ <- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m
ChatItem {formattedText} <- newChatItem (CIRcvMsgContent content) ciFile_
@@ -2531,7 +2531,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage =
createGroupFeatureItems :: GroupInfo -> GroupMember -> m ()
createGroupFeatureItems g@GroupInfo {groupProfile} m = do
let prefs = mergeGroupPreferences $ groupPreferences groupProfile
forM_ allChatFeatures $ \f -> do
forM_ allGroupFeatures $ \f -> do
let p = getGroupPreference f prefs
createInternalChatItem user (CDGroupRcv g m) (CIRcvGroupFeature f p) Nothing
@@ -3113,9 +3113,9 @@ createFeatureChangedItems user Contact {mergedPreferences = cups} ct'@Contact {m
unless (enabled == enabled') $
createInternalChatItem user (chatDir ct') (ciContent f enabled') Nothing
createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (ChatFeature -> GroupPreference -> CIContent d) -> GroupProfile -> GroupProfile -> m ()
createGroupFeatureChangedItems :: (MsgDirectionI d, ChatMonad m) => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> CIContent d) -> GroupProfile -> GroupProfile -> m ()
createGroupFeatureChangedItems user cd ciContent p p' =
forM_ allChatFeatures $ \f -> do
forM_ allGroupFeatures $ \f -> do
let pref = getGroupPreference f $ groupPreferences p
pref' = getGroupPreference f $ groupPreferences p'
unless (pref == pref') $
@@ -3132,11 +3132,11 @@ featureProhibited forWhom Contact {mergedPreferences} = \case
in if forWhom enabled then Nothing else Just CFVoice
_ -> Nothing
groupFeatureProhibited :: GroupInfo -> MsgContent -> Maybe ChatFeature
groupFeatureProhibited :: GroupInfo -> MsgContent -> Maybe GroupFeature
groupFeatureProhibited GroupInfo {fullGroupPreferences} = \case
MCVoice {} ->
let GroupPreference {enable} = getGroupPreference CFVoice fullGroupPreferences
in case enable of FEOn -> Nothing; FEOff -> Just CFVoice
let GroupPreference {enable} = getGroupPreference GFVoice fullGroupPreferences
in case enable of FEOn -> Nothing; FEOff -> Just GFVoice
_ -> Nothing
createInternalChatItem :: forall c d m. (ChatTypeI c, MsgDirectionI d, ChatMonad m) => User -> ChatDirection c d -> CIContent d -> Maybe UTCTime -> m ()
@@ -3399,9 +3399,10 @@ chatCommandP =
"/profile_image" $> UpdateProfileImage Nothing,
("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> userNames),
("/profile" <|> "/p") $> ShowProfile,
"/voice #" *> (SetGroupFeature CFVoice <$> displayName <*> (A.space *> strP)),
"/voice #" *> (SetGroupFeature GFVoice <$> displayName <*> (A.space *> strP)),
"/voice @" *> (SetContactFeature CFVoice <$> displayName <*> optional (A.space *> strP)),
"/voice " *> (SetUserFeature CFVoice <$> strP),
"/dms #" *> (SetGroupFeature GFDirectMessages <$> displayName <*> (A.space *> strP)),
"/incognito " *> (SetIncognito <$> onOffP),
("/quit" <|> "/q" <|> "/exit") $> QuitChat,
("/version" <|> "/v") $> ShowVersion,

View File

@@ -252,7 +252,7 @@ data ChatCommand
| UpdateProfileImage (Maybe ImageData)
| SetUserFeature ChatFeature FeatureAllowed
| SetContactFeature ChatFeature ContactName (Maybe FeatureAllowed)
| SetGroupFeature ChatFeature GroupName GroupFeatureEnabled
| SetGroupFeature GroupFeature GroupName GroupFeatureEnabled
| QuitChat
| ShowVersion
| DebugLocks

View File

@@ -560,9 +560,10 @@ data CIContent (d :: MsgDirection) where
CISndConnEvent :: SndConnEvent -> CIContent 'MDSnd
CIRcvChatFeature :: ChatFeature -> PrefEnabled -> CIContent 'MDRcv
CISndChatFeature :: ChatFeature -> PrefEnabled -> CIContent 'MDSnd
CIRcvGroupFeature :: ChatFeature -> GroupPreference -> CIContent 'MDRcv
CISndGroupFeature :: ChatFeature -> GroupPreference -> CIContent 'MDSnd
CIRcvGroupFeature :: GroupFeature -> GroupPreference -> CIContent 'MDRcv
CISndGroupFeature :: GroupFeature -> GroupPreference -> CIContent 'MDSnd
CIRcvChatFeatureRejected :: ChatFeature -> CIContent 'MDRcv
CIRcvGroupFeatureRejected :: GroupFeature -> CIContent 'MDRcv
-- ^ This type is used both in API and in DB, so we use different JSON encodings for the database and for the API
-- ! ^ Nested sum types also have to use different encodings for database and API
-- ! ^ to avoid breaking cross-platform compatibility, see RcvGroupEvent and SndGroupEvent
@@ -589,6 +590,7 @@ ciCreateStatus = \case
CIRcvGroupFeature {} -> CISRcvRead
CISndGroupFeature {} -> ciStatusNew
CIRcvChatFeatureRejected _ -> ciStatusNew
CIRcvGroupFeatureRejected _ -> ciStatusNew
data RcvGroupEvent
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
@@ -750,9 +752,10 @@ ciContentToText = \case
CISndConnEvent event -> sndConnEventToText event
CIRcvChatFeature feature enabled -> chatFeatureToText feature <> ": " <> prefEnabledToText enabled
CISndChatFeature feature enabled -> chatFeatureToText feature <> ": " <> prefEnabledToText enabled
CIRcvGroupFeature feature pref -> chatFeatureToText feature <> ": " <> groupPrefToText pref
CISndGroupFeature feature pref -> chatFeatureToText feature <> ": " <> groupPrefToText pref
CIRcvGroupFeature feature pref -> groupFeatureToText feature <> ": " <> groupPrefToText pref
CISndGroupFeature feature pref -> groupFeatureToText feature <> ": " <> groupPrefToText pref
CIRcvChatFeatureRejected feature -> chatFeatureToText feature <> ": received, prohibited"
CIRcvGroupFeatureRejected feature -> groupFeatureToText feature <> ": received, prohibited"
msgIntegrityError :: MsgErrorType -> Text
msgIntegrityError = \case
@@ -805,9 +808,10 @@ data JSONCIContent
| JCISndConnEvent {sndConnEvent :: SndConnEvent}
| JCIRcvChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| JCISndChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| JCIRcvGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| JCISndGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| JCIRcvGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| JCISndGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| JCIRcvChatFeatureRejected {feature :: ChatFeature}
| JCIRcvGroupFeatureRejected {groupFeature :: GroupFeature}
deriving (Generic)
instance FromJSON JSONCIContent where
@@ -834,9 +838,10 @@ jsonCIContent = \case
CISndConnEvent sndConnEvent -> JCISndConnEvent {sndConnEvent}
CIRcvChatFeature feature enabled -> JCIRcvChatFeature {feature, enabled}
CISndChatFeature feature enabled -> JCISndChatFeature {feature, enabled}
CIRcvGroupFeature feature preference -> JCIRcvGroupFeature {feature, preference}
CISndGroupFeature feature preference -> JCISndGroupFeature {feature, preference}
CIRcvGroupFeature groupFeature preference -> JCIRcvGroupFeature {groupFeature, preference}
CISndGroupFeature groupFeature preference -> JCISndGroupFeature {groupFeature, preference}
CIRcvChatFeatureRejected feature -> JCIRcvChatFeatureRejected {feature}
CIRcvGroupFeatureRejected groupFeature -> JCIRcvGroupFeatureRejected {groupFeature}
aciContentJSON :: JSONCIContent -> ACIContent
aciContentJSON = \case
@@ -855,9 +860,10 @@ aciContentJSON = \case
JCISndConnEvent {sndConnEvent} -> ACIContent SMDSnd $ CISndConnEvent sndConnEvent
JCIRcvChatFeature {feature, enabled} -> ACIContent SMDRcv $ CIRcvChatFeature feature enabled
JCISndChatFeature {feature, enabled} -> ACIContent SMDSnd $ CISndChatFeature feature enabled
JCIRcvGroupFeature {feature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature feature preference
JCISndGroupFeature {feature, preference} -> ACIContent SMDSnd $ CISndGroupFeature feature preference
JCIRcvGroupFeature {groupFeature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature groupFeature preference
JCISndGroupFeature {groupFeature, preference} -> ACIContent SMDSnd $ CISndGroupFeature groupFeature preference
JCIRcvChatFeatureRejected {feature} -> ACIContent SMDRcv $ CIRcvChatFeatureRejected feature
JCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature
-- platform independent
data DBJSONCIContent
@@ -876,9 +882,10 @@ data DBJSONCIContent
| DBJCISndConnEvent {sndConnEvent :: DBSndConnEvent}
| DBJCIRcvChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| DBJCISndChatFeature {feature :: ChatFeature, enabled :: PrefEnabled}
| DBJCIRcvGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| DBJCISndGroupFeature {feature :: ChatFeature, preference :: GroupPreference}
| DBJCIRcvGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| DBJCISndGroupFeature {groupFeature :: GroupFeature, preference :: GroupPreference}
| DBJCIRcvChatFeatureRejected {feature :: ChatFeature}
| DBJCIRcvGroupFeatureRejected {groupFeature :: GroupFeature}
deriving (Generic)
instance FromJSON DBJSONCIContent where
@@ -905,9 +912,10 @@ dbJsonCIContent = \case
CISndConnEvent sce -> DBJCISndConnEvent $ SCE sce
CIRcvChatFeature feature enabled -> DBJCIRcvChatFeature {feature, enabled}
CISndChatFeature feature enabled -> DBJCISndChatFeature {feature, enabled}
CIRcvGroupFeature feature preference -> DBJCIRcvGroupFeature {feature, preference}
CISndGroupFeature feature preference -> DBJCISndGroupFeature {feature, preference}
CIRcvGroupFeature groupFeature preference -> DBJCIRcvGroupFeature {groupFeature, preference}
CISndGroupFeature groupFeature preference -> DBJCISndGroupFeature {groupFeature, preference}
CIRcvChatFeatureRejected feature -> DBJCIRcvChatFeatureRejected {feature}
CIRcvGroupFeatureRejected groupFeature -> DBJCIRcvGroupFeatureRejected {groupFeature}
aciContentDBJSON :: DBJSONCIContent -> ACIContent
aciContentDBJSON = \case
@@ -926,9 +934,10 @@ aciContentDBJSON = \case
DBJCISndConnEvent (SCE sce) -> ACIContent SMDSnd $ CISndConnEvent sce
DBJCIRcvChatFeature {feature, enabled} -> ACIContent SMDRcv $ CIRcvChatFeature feature enabled
DBJCISndChatFeature {feature, enabled} -> ACIContent SMDSnd $ CISndChatFeature feature enabled
DBJCIRcvGroupFeature {feature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature feature preference
DBJCISndGroupFeature {feature, preference} -> ACIContent SMDSnd $ CISndGroupFeature feature preference
DBJCIRcvGroupFeature {groupFeature, preference} -> ACIContent SMDRcv $ CIRcvGroupFeature groupFeature preference
DBJCISndGroupFeature {groupFeature, preference} -> ACIContent SMDSnd $ CISndGroupFeature groupFeature preference
DBJCIRcvChatFeatureRejected {feature} -> ACIContent SMDRcv $ CIRcvChatFeatureRejected feature
DBJCIRcvGroupFeatureRejected {groupFeature} -> ACIContent SMDRcv $ CIRcvGroupFeatureRejected groupFeature
data CICallStatus
= CISCallPending

View File

@@ -0,0 +1,13 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20221129_delete_group_feature_items where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20221129_delete_group_feature_items :: Query
m20221129_delete_group_feature_items =
[sql|
DELETE FROM chat_items WHERE item_content LIKE '%{"rcvGroupFeature":{%';
DELETE FROM chat_items WHERE item_content LIKE '%{"sndGroupFeature":{%';
|]

View File

@@ -298,6 +298,7 @@ import Simplex.Chat.Migrations.M20221025_chat_settings
import Simplex.Chat.Migrations.M20221029_group_link_id
import Simplex.Chat.Migrations.M20221112_server_password
import Simplex.Chat.Migrations.M20221115_server_cfg
import Simplex.Chat.Migrations.M20221129_delete_group_feature_items
import Simplex.Chat.Protocol
import Simplex.Chat.Types
import Simplex.Messaging.Agent.Protocol (ACorrId, AgentMsgId, ConnId, InvitationId, MsgMeta (..))
@@ -346,7 +347,8 @@ schemaMigrations =
("20221025_chat_settings", m20221025_chat_settings),
("20221029_group_link_id", m20221029_group_link_id),
("20221112_server_password", m20221112_server_password),
("20221115_server_cfg", m20221115_server_cfg)
("20221115_server_cfg", m20221115_server_cfg),
("20221129_delete_group_feature_items", m20221129_delete_group_feature_items)
]
-- | The list of migrations in ascending order by date

View File

@@ -281,12 +281,6 @@ chatPrefSel = \case
-- CFReceipts -> receipts
CFVoice -> voice
chatPrefName :: ChatFeature -> Text
chatPrefName = \case
CFFullDelete -> "full message deletion"
-- CFReceipts -> "delivery receipts"
CFVoice -> "voice messages"
class PreferenceI p where
getPreference :: ChatFeature -> p -> Preference
@@ -329,14 +323,43 @@ instance ToField Preferences where
instance FromField Preferences where
fromField = fromTextField_ decodeJSON
groupPrefSel :: ChatFeature -> GroupPreferences -> Maybe GroupPreference
data GroupFeature
= GFDirectMessages
| GFFullDelete
| -- | GFReceipts
GFVoice
deriving (Show, Generic)
groupFeatureToText :: GroupFeature -> Text
groupFeatureToText = \case
GFDirectMessages -> "Direct messages"
GFFullDelete -> "Full deletion"
GFVoice -> "Voice messages"
instance ToJSON GroupFeature where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF"
toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF"
instance FromJSON GroupFeature where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF"
allGroupFeatures :: [GroupFeature]
allGroupFeatures =
[ GFDirectMessages,
GFFullDelete,
-- GFReceipts,
GFVoice
]
groupPrefSel :: GroupFeature -> GroupPreferences -> Maybe GroupPreference
groupPrefSel = \case
CFFullDelete -> fullDelete
-- CFReceipts -> receipts
CFVoice -> voice
GFDirectMessages -> directMessages
GFFullDelete -> fullDelete
-- GFReceipts -> receipts
GFVoice -> voice
class GroupPreferenceI p where
getGroupPreference :: ChatFeature -> p -> GroupPreference
getGroupPreference :: GroupFeature -> p -> GroupPreference
instance GroupPreferenceI GroupPreferences where
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt prefs)
@@ -346,14 +369,16 @@ instance GroupPreferenceI (Maybe GroupPreferences) where
instance GroupPreferenceI FullGroupPreferences where
getGroupPreference = \case
CFFullDelete -> fullDelete
-- CFReceipts -> receipts
CFVoice -> voice
GFDirectMessages -> directMessages
GFFullDelete -> fullDelete
-- GFReceipts -> receipts
GFVoice -> voice
{-# INLINE getGroupPreference #-}
-- collection of optional group preferences
data GroupPreferences = GroupPreferences
{ fullDelete :: Maybe GroupPreference,
{ directMessages :: Maybe GroupPreference,
fullDelete :: Maybe GroupPreference,
-- receipts :: Maybe GroupPreference,
voice :: Maybe GroupPreference
}
@@ -369,13 +394,14 @@ instance ToField GroupPreferences where
instance FromField GroupPreferences where
fromField = fromTextField_ decodeJSON
setGroupPreference :: ChatFeature -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
setGroupPreference :: GroupFeature -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
setGroupPreference f enable prefs_ =
let prefs = mergeGroupPreferences prefs_
pref = (getGroupPreference f prefs :: GroupPreference) {enable}
in toGroupPreferences $ case f of
CFVoice -> prefs {voice = pref}
CFFullDelete -> prefs {fullDelete = pref}
GFDirectMessages -> prefs {directMessages = pref}
GFVoice -> prefs {voice = pref}
GFFullDelete -> prefs {fullDelete = pref}
-- full collection of chat preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
-- if some of the preferences are not defined in Preferences, defaults from defaultChatPrefs are used here.
@@ -391,7 +417,8 @@ instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.default
-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here.
data FullGroupPreferences = FullGroupPreferences
{ fullDelete :: GroupPreference,
{ directMessages :: GroupPreference,
fullDelete :: GroupPreference,
-- receipts :: GroupPreference,
voice :: GroupPreference
}
@@ -447,7 +474,8 @@ emptyChatPrefs = Preferences Nothing Nothing
defaultGroupPrefs :: FullGroupPreferences
defaultGroupPrefs =
FullGroupPreferences
{ fullDelete = GroupPreference {enable = FEOff},
{ directMessages = GroupPreference {enable = FEOff},
fullDelete = GroupPreference {enable = FEOff},
-- receipts = GroupPreference {enable = FEOff},
voice = GroupPreference {enable = FEOn}
}
@@ -543,9 +571,10 @@ mergeUserChatPrefs' user connectedIncognito userPreferences =
mergeGroupPreferences :: Maybe GroupPreferences -> FullGroupPreferences
mergeGroupPreferences groupPreferences =
FullGroupPreferences
{ fullDelete = pref CFFullDelete,
-- receipts = pref CFReceipts,
voice = pref CFVoice
{ directMessages = pref GFDirectMessages,
fullDelete = pref GFFullDelete,
-- receipts = pref GFReceipts,
voice = pref GFVoice
}
where
pref pt = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPreferences >>= groupPrefSel pt)
@@ -553,9 +582,10 @@ mergeGroupPreferences groupPreferences =
toGroupPreferences :: FullGroupPreferences -> GroupPreferences
toGroupPreferences groupPreferences =
GroupPreferences
{ fullDelete = pref CFFullDelete,
-- receipts = pref CFReceipts,
voice = pref CFVoice
{ directMessages = pref GFDirectMessages,
fullDelete = pref GFFullDelete,
-- receipts = pref GFReceipts,
voice = pref GFVoice
}
where
pref f = Just $ getGroupPreference f groupPreferences

View File

@@ -745,7 +745,7 @@ viewContactPreferences user ct ct' cups =
viewContactPref :: FullPreferences -> FullPreferences -> Maybe Preferences -> ContactUserPreferences -> ChatFeature -> Maybe StyledString
viewContactPref userPrefs userPrefs' ctPrefs cups pt
| userPref == userPref' && ctPref == contactPreference = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> ": " <> plain (prefEnabledToText enabled) <> " (you allow: " <> viewCountactUserPref userPreference <> ", contact allows: " <> viewPreference contactPreference <> ")"
| otherwise = Just $ plain (chatFeatureToText pt) <> ": " <> plain (prefEnabledToText enabled) <> " (you allow: " <> viewCountactUserPref userPreference <> ", contact allows: " <> viewPreference contactPreference <> ")"
where
userPref = getPreference pt userPrefs
userPref' = getPreference pt userPrefs'
@@ -760,7 +760,7 @@ viewPrefsUpdated ps ps'
prefs = mapMaybe viewPref allChatFeatures
viewPref pt
| pref ps == pref ps' = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> " allowed: " <> viewPreference (pref ps')
| otherwise = Just $ plain (chatFeatureToText pt) <> " allowed: " <> viewPreference (pref ps')
where
pref pss = getPreference pt $ mergePreferences pss Nothing
@@ -796,10 +796,10 @@ viewGroupUpdated
| null prefs = []
| otherwise = "updated group preferences:" : prefs
where
prefs = mapMaybe viewPref allChatFeatures
prefs = mapMaybe viewPref allGroupFeatures
viewPref pt
| pref gps == pref gps' = Nothing
| otherwise = Just $ plain (chatPrefName pt) <> " enabled: " <> plain (groupPrefToText $ pref gps')
| otherwise = Just $ plain (groupFeatureToText pt) <> " enabled: " <> plain (groupPrefToText $ pref gps')
where
pref pss = getGroupPreference pt $ mergeGroupPreferences pss

View File

@@ -1266,7 +1266,7 @@ testGroupMessageDelete =
cath #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((0, "hi alic"), Just (0, "hello!"))])
-- alice: msg id 5
bob #$> ("/_update item #1 " <> groupItemId 2 6 <> " text hi alice", id, "message updated")
bob #$> ("/_update item #1 " <> groupItemId 2 7 <> " text hi alice", id, "message updated")
concurrently_
(alice <# "#team bob> [edited] hi alice")
( do
@@ -1285,7 +1285,7 @@ testGroupMessageDelete =
(alice <# "#team cath> how are you?")
(bob <# "#team cath> how are you?")
cath #$> ("/_delete item #1 " <> groupItemId 2 6 <> " broadcast", id, "message deleted")
cath #$> ("/_delete item #1 " <> groupItemId 2 7 <> " broadcast", id, "message deleted")
concurrently_
(alice <# "#team cath> [deleted] how are you?")
(bob <# "#team cath> [deleted] how are you?")
@@ -2610,17 +2610,17 @@ testConnectIncognitoInvitationLink = testChat3 aliceProfile bobProfile cathProfi
(bob </)
alice ##> "/_set prefs @2 {\"fullDelete\": {\"allow\": \"always\"}}"
alice <## ("you updated preferences for " <> bobIncognito <> ":")
alice <## "full message deletion: enabled for contact (you allow: always, contact allows: no)"
alice <## "Full deletion: enabled for contact (you allow: always, contact allows: no)"
bob <## (aliceIncognito <> " updated preferences for you:")
bob <## "full message deletion: enabled for you (you allow: no, contact allows: always)"
bob <## "Full deletion: enabled for you (you allow: no, contact allows: always)"
bob ##> "/_set prefs @2 {}"
bob <## ("your preferences for " <> aliceIncognito <> " did not change")
(alice </)
alice ##> "/_set prefs @2 {\"fullDelete\": {\"allow\": \"no\"}}"
alice <## ("you updated preferences for " <> bobIncognito <> ":")
alice <## "full message deletion: off (you allow: no, contact allows: no)"
alice <## "Full deletion: off (you allow: no, contact allows: no)"
bob <## (aliceIncognito <> " updated preferences for you:")
bob <## "full message deletion: off (you allow: no, contact allows: no)"
bob <## "Full deletion: off (you allow: no, contact allows: no)"
testConnectIncognitoContactAddress :: IO ()
testConnectIncognitoContactAddress = testChat2 aliceProfile bobProfile $
@@ -2919,32 +2919,32 @@ testCantSeeGlobalPrefsUpdateIncognito = testChat3 aliceProfile bobProfile cathPr
alice ##> "/_profile {\"displayName\": \"alice\", \"fullName\": \"\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}}}"
alice <## "user full name removed (your contacts are notified)"
alice <## "updated preferences:"
alice <## "full message deletion allowed: always"
alice <## "Full deletion allowed: always"
(alice </)
-- bob doesn't receive profile update
(bob </)
cath <## "contact alice removed full name"
cath <## "alice updated preferences for you:"
cath <## "full message deletion: enabled for you (you allow: default (no), contact allows: always)"
cath <## "Full deletion: enabled for you (you allow: default (no), contact allows: always)"
(cath </)
bob ##> "/_set prefs @2 {\"fullDelete\": {\"allow\": \"always\"}}"
bob <## ("you updated preferences for " <> aliceIncognito <> ":")
bob <## "full message deletion: enabled for contact (you allow: always, contact allows: no)"
bob <## "Full deletion: enabled for contact (you allow: always, contact allows: no)"
alice <## "bob updated preferences for you:"
alice <## "full message deletion: enabled for you (you allow: no, contact allows: always)"
alice <## "Full deletion: enabled for you (you allow: no, contact allows: always)"
alice ##> "/_set prefs @2 {\"fullDelete\": {\"allow\": \"yes\"}}"
alice <## "you updated preferences for bob:"
alice <## "full message deletion: enabled (you allow: yes, contact allows: always)"
alice <## "Full deletion: enabled (you allow: yes, contact allows: always)"
bob <## (aliceIncognito <> " updated preferences for you:")
bob <## "full message deletion: enabled (you allow: always, contact allows: yes)"
bob <## "Full deletion: enabled (you allow: always, contact allows: yes)"
(cath </)
alice ##> "/_set prefs @3 {\"fullDelete\": {\"allow\": \"always\"}}"
alice <## "your preferences for cath did not change"
alice ##> "/_set prefs @3 {\"fullDelete\": {\"allow\": \"yes\"}}"
alice <## "you updated preferences for cath:"
alice <## "full message deletion: off (you allow: yes, contact allows: no)"
alice <## "Full deletion: off (you allow: yes, contact allows: no)"
cath <## "alice updated preferences for you:"
cath <## "full message deletion: off (you allow: default (no), contact allows: yes)"
cath <## "Full deletion: off (you allow: default (no), contact allows: yes)"
testSetAlias :: IO ()
testSetAlias = testChat2 aliceProfile bobProfile $
@@ -2986,7 +2986,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
bob ##> "/_profile {\"displayName\": \"bob\", \"fullName\": \"Bob\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}"
bob <## "profile image removed"
bob <## "updated preferences:"
bob <## "voice messages allowed: no"
bob <## "Voice messages allowed: no"
(bob </)
connectUsers alice bob
alice ##> "/_set prefs @2 {}"
@@ -3004,10 +3004,10 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
-- alice ##> "/_set prefs @2 {\"voice\": {\"allow\": \"always\"}}"
alice ##> "/voice @bob always"
alice <## "you updated preferences for bob:"
alice <## "voice messages: enabled for contact (you allow: always, contact allows: no)"
alice <## "Voice messages: enabled for contact (you allow: always, contact allows: no)"
alice #$> ("/_get chat @2 count=100", chat, startFeatures <> [(1, "Voice messages: enabled for contact")])
bob <## "alice updated preferences for you:"
bob <## "voice messages: enabled for you (you allow: default (no), contact allows: always)"
bob <## "Voice messages: enabled for you (you allow: default (no), contact allows: always)"
bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you")])
alice ##> sendVoice
alice <## voiceNotAllowed
@@ -3023,25 +3023,25 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
-- alice ##> "/_profile {\"displayName\": \"alice\", \"fullName\": \"Alice\", \"preferences\": {\"voice\": {\"allow\": \"no\"}}}"
alice ##> "/voice no"
alice <## "updated preferences:"
alice <## "voice messages allowed: no"
alice <## "Voice messages allowed: no"
(alice </)
alice ##> "/_set prefs @2 {\"voice\": {\"allow\": \"yes\"}}"
alice <## "you updated preferences for bob:"
alice <## "voice messages: off (you allow: yes, contact allows: no)"
alice <## "Voice messages: off (you allow: yes, contact allows: no)"
alice #$> ("/_get chat @2 count=100", chat, startFeatures <> [(1, "Voice messages: enabled for contact"), (0, "voice message (00:10)"), (1, "Voice messages: off")])
bob <## "alice updated preferences for you:"
bob <## "voice messages: off (you allow: default (no), contact allows: yes)"
bob <## "Voice messages: off (you allow: default (no), contact allows: yes)"
bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you"), (1, "voice message (00:10)"), (0, "Voice messages: off")])
(bob </)
bob ##> "/_profile {\"displayName\": \"bob\", \"fullName\": \"\", \"preferences\": {\"voice\": {\"allow\": \"yes\"}}}"
bob <## "user full name removed (your contacts are notified)"
bob <## "updated preferences:"
bob <## "voice messages allowed: yes"
bob <## "Voice messages allowed: yes"
bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you"), (1, "voice message (00:10)"), (0, "Voice messages: off"), (1, "Voice messages: enabled")])
(bob </)
alice <## "contact bob removed full name"
alice <## "bob updated preferences for you:"
alice <## "voice messages: enabled (you allow: yes, contact allows: yes)"
alice <## "Voice messages: enabled (you allow: yes, contact allows: yes)"
alice #$> ("/_get chat @2 count=100", chat, startFeatures <> [(1, "Voice messages: enabled for contact"), (0, "voice message (00:10)"), (1, "Voice messages: off"), (0, "Voice messages: enabled")])
(alice </)
bob ##> "/_set prefs @2 {}"
@@ -3052,10 +3052,10 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
(alice </)
alice ##> "/_set prefs @2 {\"voice\": {\"allow\": \"no\"}}"
alice <## "you updated preferences for bob:"
alice <## "voice messages: off (you allow: no, contact allows: yes)"
alice <## "Voice messages: off (you allow: no, contact allows: yes)"
alice #$> ("/_get chat @2 count=100", chat, startFeatures <> [(1, "Voice messages: enabled for contact"), (0, "voice message (00:10)"), (1, "Voice messages: off"), (0, "Voice messages: enabled"), (1, "Voice messages: off")])
bob <## "alice updated preferences for you:"
bob <## "voice messages: off (you allow: default (yes), contact allows: no)"
bob <## "Voice messages: off (you allow: default (yes), contact allows: no)"
bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you"), (1, "voice message (00:10)"), (0, "Voice messages: off"), (1, "Voice messages: enabled"), (0, "Voice messages: off")])
testUpdateGroupPrefs :: IO ()
@@ -3068,32 +3068,32 @@ testUpdateGroupPrefs =
threadDelay 1000000
alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"on\"}}}"
alice <## "updated group preferences:"
alice <## "full message deletion enabled: on"
alice <## "Full deletion enabled: on"
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on")])
bob <## "alice updated group #team:"
bob <## "updated group preferences:"
bob <## "full message deletion enabled: on"
bob <## "Full deletion enabled: on"
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on")])
threadDelay 1000000
alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"off\"}}}"
alice <## "updated group preferences:"
alice <## "full message deletion enabled: off"
alice <## "voice messages enabled: off"
alice <## "Full deletion enabled: off"
alice <## "Voice messages enabled: off"
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off")])
bob <## "alice updated group #team:"
bob <## "updated group preferences:"
bob <## "full message deletion enabled: off"
bob <## "voice messages enabled: off"
bob <## "Full deletion enabled: off"
bob <## "Voice messages enabled: off"
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off")])
threadDelay 1000000
-- alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}}}"
alice ##> "/voice #team on"
alice <## "updated group preferences:"
alice <## "voice messages enabled: on"
alice <## "Voice messages enabled: on"
alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")])
bob <## "alice updated group #team:"
bob <## "updated group preferences:"
bob <## "voice messages enabled: on"
bob <## "Voice messages enabled: on"
bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off"), (0, "Voice messages: on")])
threadDelay 1000000
alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"team\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}}}"
@@ -4243,7 +4243,7 @@ groupFeatures :: [(Int, String)]
groupFeatures = map (\(a, _, _) -> a) groupFeatures''
groupFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)]
groupFeatures'' = [((0, "Full deletion: off"), Nothing, Nothing), ((0, "Voice messages: on"), Nothing, Nothing)]
groupFeatures'' = [((0, "Direct messages: off"), Nothing, Nothing), ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Voice messages: on"), Nothing, Nothing)]
itemId :: Int -> String
itemId i = show $ length chatFeatures + i

View File

@@ -83,7 +83,7 @@ testChatPreferences :: Maybe Preferences
testChatPreferences = Just Preferences {voice = Just Preference {allow = FAYes}, fullDelete = Nothing}
testGroupPreferences :: Maybe GroupPreferences
testGroupPreferences = Just GroupPreferences {voice = Just GroupPreference {enable = FEOn}, fullDelete = Nothing}
testGroupPreferences = Just GroupPreferences {directMessages = Nothing, voice = Just GroupPreference {enable = FEOn}, fullDelete = Nothing}
testProfile :: Profile
testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), preferences = testChatPreferences}