mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-14 07:10:19 +00:00
mobile: items with feature offers (#1627)
* mobile: items with feature offers * ios interactive contact/user preference change items * android: interactive preference items * add missing view * revert change
This commit is contained in:
@@ -408,8 +408,7 @@ interface SomeChat {
|
||||
val sendMsgEnabled: Boolean
|
||||
val ntfsEnabled: Boolean
|
||||
val incognito: Boolean
|
||||
val voiceMessageAllowed: Boolean
|
||||
val fullDeletionAllowed: Boolean
|
||||
fun featureEnabled(feature: ChatFeature): Boolean
|
||||
val timedMessagesTTL: Int?
|
||||
val createdAt: Instant
|
||||
val updatedAt: Instant
|
||||
@@ -472,8 +471,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val sendMsgEnabled get() = contact.sendMsgEnabled
|
||||
override val ntfsEnabled get() = contact.ntfsEnabled
|
||||
override val incognito get() = contact.incognito
|
||||
override val voiceMessageAllowed get() = contact.voiceMessageAllowed
|
||||
override val fullDeletionAllowed get() = contact.fullDeletionAllowed
|
||||
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
|
||||
override val createdAt get() = contact.createdAt
|
||||
override val updatedAt get() = contact.updatedAt
|
||||
@@ -497,8 +495,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val sendMsgEnabled get() = groupInfo.sendMsgEnabled
|
||||
override val ntfsEnabled get() = groupInfo.ntfsEnabled
|
||||
override val incognito get() = groupInfo.incognito
|
||||
override val voiceMessageAllowed get() = groupInfo.voiceMessageAllowed
|
||||
override val fullDeletionAllowed get() = groupInfo.fullDeletionAllowed
|
||||
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
|
||||
override val createdAt get() = groupInfo.createdAt
|
||||
override val updatedAt get() = groupInfo.updatedAt
|
||||
@@ -522,8 +519,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val sendMsgEnabled get() = contactRequest.sendMsgEnabled
|
||||
override val ntfsEnabled get() = contactRequest.ntfsEnabled
|
||||
override val incognito get() = contactRequest.incognito
|
||||
override val voiceMessageAllowed get() = contactRequest.voiceMessageAllowed
|
||||
override val fullDeletionAllowed get() = contactRequest.fullDeletionAllowed
|
||||
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
|
||||
override val createdAt get() = contactRequest.createdAt
|
||||
override val updatedAt get() = contactRequest.updatedAt
|
||||
@@ -547,8 +543,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val sendMsgEnabled get() = contactConnection.sendMsgEnabled
|
||||
override val ntfsEnabled get() = contactConnection.incognito
|
||||
override val incognito get() = contactConnection.incognito
|
||||
override val voiceMessageAllowed get() = contactConnection.voiceMessageAllowed
|
||||
override val fullDeletionAllowed get() = contactConnection.fullDeletionAllowed
|
||||
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
|
||||
override val createdAt get() = contactConnection.createdAt
|
||||
override val updatedAt get() = contactConnection.updatedAt
|
||||
@@ -585,8 +580,11 @@ data class Contact(
|
||||
override val sendMsgEnabled get() = true
|
||||
override val ntfsEnabled get() = chatSettings.enableNtfs
|
||||
override val incognito get() = contactConnIncognito
|
||||
override val voiceMessageAllowed get() = mergedPreferences.voice.enabled.forUser
|
||||
override val fullDeletionAllowed get() = mergedPreferences.fullDelete.enabled.forUser
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
ChatFeature.TimedMessages -> mergedPreferences.timedMessages.enabled.forUser
|
||||
ChatFeature.FullDelete -> mergedPreferences.fullDelete.enabled.forUser
|
||||
ChatFeature.Voice -> mergedPreferences.voice.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 }
|
||||
override val fullName get() = profile.fullName
|
||||
@@ -600,6 +598,18 @@ data class Contact(
|
||||
val contactConnIncognito =
|
||||
activeConn.customUserProfileId != null
|
||||
|
||||
fun allowsFeature(feature: ChatFeature): Boolean = when (feature) {
|
||||
ChatFeature.TimedMessages -> mergedPreferences.timedMessages.contactPreference.allow != FeatureAllowed.NO
|
||||
ChatFeature.FullDelete -> mergedPreferences.fullDelete.contactPreference.allow != FeatureAllowed.NO
|
||||
ChatFeature.Voice -> mergedPreferences.voice.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
|
||||
}
|
||||
|
||||
companion object {
|
||||
val sampleData = Contact(
|
||||
contactId = 1,
|
||||
@@ -720,8 +730,11 @@ data class GroupInfo (
|
||||
override val sendMsgEnabled get() = membership.memberActive
|
||||
override val ntfsEnabled get() = chatSettings.enableNtfs
|
||||
override val incognito get() = membership.memberIncognito
|
||||
override val voiceMessageAllowed get() = fullGroupPreferences.voice.on
|
||||
override val fullDeletionAllowed get() = fullGroupPreferences.fullDelete.on
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
|
||||
ChatFeature.FullDelete -> fullGroupPreferences.fullDelete.on
|
||||
ChatFeature.Voice -> fullGroupPreferences.fullDelete.on
|
||||
}
|
||||
override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null }
|
||||
override val displayName get() = groupProfile.displayName
|
||||
override val fullName get() = groupProfile.fullName
|
||||
@@ -968,8 +981,7 @@ class UserContactRequest (
|
||||
override val sendMsgEnabled get() = false
|
||||
override val ntfsEnabled get() = false
|
||||
override val incognito get() = false
|
||||
override val voiceMessageAllowed get() = false
|
||||
override val fullDeletionAllowed get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
override val displayName get() = profile.displayName
|
||||
override val fullName get() = profile.fullName
|
||||
@@ -1007,8 +1019,7 @@ class PendingContactConnection(
|
||||
override val sendMsgEnabled get() = false
|
||||
override val ntfsEnabled get() = false
|
||||
override val incognito get() = customUserProfileId != null
|
||||
override val voiceMessageAllowed get() = false
|
||||
override val fullDeletionAllowed get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
override val localDisplayName get() = String.format(generalGetString(R.string.connection_local_display_name), pccConnId)
|
||||
override val displayName: String get() {
|
||||
@@ -1150,6 +1161,8 @@ data class ChatItem (
|
||||
is CIContent.SndConnEventContent -> showNtfDir
|
||||
is CIContent.RcvChatFeature -> false
|
||||
is CIContent.SndChatFeature -> showNtfDir
|
||||
is CIContent.RcvChatPreference -> false
|
||||
is CIContent.SndChatPreference -> showNtfDir
|
||||
is CIContent.RcvGroupFeature -> false
|
||||
is CIContent.SndGroupFeature -> showNtfDir
|
||||
is CIContent.RcvChatFeatureRejected -> showNtfDir
|
||||
@@ -1385,6 +1398,8 @@ sealed class CIContent: ItemContent {
|
||||
@Serializable @SerialName("sndConnEvent") class SndConnEventContent(val sndConnEvent: SndConnEvent): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatFeature") class RcvChatFeature(val feature: ChatFeature, val enabled: FeatureEnabled, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndChatFeature") class SndChatFeature(val feature: ChatFeature, val enabled: FeatureEnabled, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatPreference") class RcvChatPreference(val feature: ChatFeature, val allowed: FeatureAllowed, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndChatPreference") class SndChatPreference(val feature: ChatFeature, val allowed: FeatureAllowed, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvGroupFeature") class RcvGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("sndGroupFeature") class SndGroupFeature(val groupFeature: GroupFeature, val preference: GroupPreference, val param: Int? = null): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@Serializable @SerialName("rcvChatFeatureRejected") class RcvChatFeatureRejected(val feature: ChatFeature): CIContent() { override val msgContent: MsgContent? get() = null }
|
||||
@@ -1406,6 +1421,8 @@ sealed class CIContent: ItemContent {
|
||||
is SndConnEventContent -> sndConnEvent.text
|
||||
is RcvChatFeature -> featureText(feature, enabled.text, param)
|
||||
is SndChatFeature -> featureText(feature, enabled.text, param)
|
||||
is RcvChatPreference -> preferenceText(feature, allowed, param)
|
||||
is SndChatPreference -> preferenceText(feature, allowed, param)
|
||||
is RcvGroupFeature -> featureText(groupFeature, preference.enable.text, param)
|
||||
is SndGroupFeature -> featureText(groupFeature, preference.enable.text, param)
|
||||
is RcvChatFeatureRejected -> "${feature.text}: ${generalGetString(R.string.feature_received_prohibited)}"
|
||||
@@ -1413,12 +1430,21 @@ sealed class CIContent: ItemContent {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun featureText(feature: Feature, value: String, param: Int?): String =
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?): String =
|
||||
if (feature.hasParam && param != null) {
|
||||
"${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
|
||||
} else {
|
||||
"${feature.text}: $value"
|
||||
"${feature.text}: $enabled"
|
||||
}
|
||||
|
||||
fun preferenceText(feature: Feature, allowed: FeatureAllowed, param: Int?): String = when {
|
||||
allowed != FeatureAllowed.NO && feature.hasParam && param != null ->
|
||||
"offered ${feature.text}: ${TimedMessagesPreference.ttlText(param)}"
|
||||
allowed != FeatureAllowed.NO ->
|
||||
"offered ${feature.text}"
|
||||
else ->
|
||||
"cancelled ${feature.text}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -985,6 +985,14 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature) {
|
||||
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature)
|
||||
val toContact = apiSetContactPrefs(contact.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
chatModel.updateContact(toContact)
|
||||
}
|
||||
}
|
||||
|
||||
private fun networkErrorAlert(r: CR): Boolean {
|
||||
return when {
|
||||
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent
|
||||
@@ -2037,6 +2045,13 @@ data class ChatPreferences(
|
||||
val fullDelete: SimpleChatPreference? = null,
|
||||
val voice: SimpleChatPreference? = null,
|
||||
) {
|
||||
fun setAllowed(feature: ChatFeature, allowed: FeatureAllowed = FeatureAllowed.YES): ChatPreferences =
|
||||
when (feature) {
|
||||
ChatFeature.TimedMessages -> this.copy(timedMessages = TimedMessagesPreference(allow = allowed, ttl = this.timedMessages?.ttl))
|
||||
ChatFeature.FullDelete -> this.copy(fullDelete = SimpleChatPreference(allow = allowed))
|
||||
ChatFeature.Voice -> this.copy(voice = SimpleChatPreference(allow = allowed))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val sampleData = ChatPreferences(
|
||||
timedMessages = TimedMessagesPreference(allow = FeatureAllowed.NO),
|
||||
|
||||
@@ -226,6 +226,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
|
||||
chatModel.callManager.acceptIncomingCall(invitation = invitation)
|
||||
}
|
||||
},
|
||||
acceptFeature = { contact, feature ->
|
||||
withApi {
|
||||
chatModel.controller.allowFeatureToContact(contact, feature)
|
||||
}
|
||||
},
|
||||
addMembers = { groupInfo ->
|
||||
hideKeyboard(view)
|
||||
withApi {
|
||||
@@ -282,6 +287,7 @@ fun ChatLayout(
|
||||
joinGroup: (Long) -> Unit,
|
||||
startCall: (CallMediaType) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit,
|
||||
addMembers: (GroupInfo) -> Unit,
|
||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||
changeNtfsState: (Boolean, currentValue: MutableState<Boolean>) -> Unit,
|
||||
@@ -322,7 +328,7 @@ fun ChatLayout(
|
||||
ChatItemsList(
|
||||
chat, unreadCount, composeState, chatItems, searchValue,
|
||||
useLinkPreviews, linkMode, chatModelIncognito, showMemberInfo, loadPrevMessages, deleteMessage,
|
||||
receiveFile, joinGroup, acceptCall, markRead, setFloatingButton, onComposed,
|
||||
receiveFile, joinGroup, acceptCall, acceptFeature, markRead, setFloatingButton, onComposed,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -497,6 +503,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
receiveFile: (Long) -> Unit,
|
||||
joinGroup: (Long) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit,
|
||||
markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit,
|
||||
setFloatingButton: (@Composable () -> Unit) -> Unit,
|
||||
onComposed: () -> Unit,
|
||||
@@ -602,11 +609,11 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
} else {
|
||||
Spacer(Modifier.size(42.dp))
|
||||
}
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem)
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
|
||||
}
|
||||
} else {
|
||||
Box(Modifier.padding(start = 104.dp, end = 12.dp).then(swipeableModifier)) {
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem)
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
|
||||
}
|
||||
}
|
||||
} else { // direct message
|
||||
@@ -617,7 +624,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||
end = if (sent) 12.dp else 76.dp,
|
||||
).then(swipeableModifier)
|
||||
) {
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = joinGroup, acceptCall = acceptCall, scrollToItem = scrollToItem)
|
||||
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,6 +1026,7 @@ fun PreviewChatLayout() {
|
||||
joinGroup = {},
|
||||
startCall = {},
|
||||
acceptCall = { _ -> },
|
||||
acceptFeature = { _, _ -> },
|
||||
addMembers = { _ -> },
|
||||
markRead = { _, _ -> },
|
||||
changeNtfsState = { _, _ -> },
|
||||
@@ -1077,6 +1085,7 @@ fun PreviewGroupChatLayout() {
|
||||
joinGroup = {},
|
||||
startCall = {},
|
||||
acceptCall = { _ -> },
|
||||
acceptFeature = { _, _ -> },
|
||||
addMembers = { _ -> },
|
||||
markRead = { _, _ -> },
|
||||
changeNtfsState = { _, _ -> },
|
||||
|
||||
@@ -516,12 +516,8 @@ fun ComposeView(
|
||||
|
||||
fun allowVoiceToContact() {
|
||||
val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return
|
||||
val prefs = contact.mergedPreferences.toPreferences().copy(voice = SimpleChatPreference(allow = FeatureAllowed.YES))
|
||||
withApi {
|
||||
val toContact = chatModel.controller.apiSetContactPrefs(contact.contactId, prefs)
|
||||
if (toContact != null) {
|
||||
chatModel.updateContact(toContact)
|
||||
}
|
||||
chatModel.controller.allowFeatureToContact(contact, ChatFeature.Voice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,7 +670,7 @@ fun ComposeView(
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.voiceMessageAllowed }
|
||||
val allowedVoiceByPrefs = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.Voice) }
|
||||
LaunchedEffect(allowedVoiceByPrefs) {
|
||||
if (!allowedVoiceByPrefs && chosenAudio.value != null) {
|
||||
// Voice was disabled right when this user records it, just cancel it
|
||||
|
||||
+4
-2
@@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.model.*
|
||||
@@ -14,14 +15,15 @@ import chat.simplex.app.model.*
|
||||
fun CIChatFeatureView(
|
||||
chatItem: ChatItem,
|
||||
feature: Feature,
|
||||
iconColor: Color
|
||||
iconColor: Color,
|
||||
icon: ImageVector? = null
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(feature.iconFilled, feature.text, Modifier.size(15.dp), tint = iconColor)
|
||||
Icon(icon ?: feature.iconFilled, feature.text, Modifier.size(15.dp), tint = iconColor)
|
||||
Text(
|
||||
chatEventText(chatItem),
|
||||
Modifier,
|
||||
|
||||
@@ -43,7 +43,7 @@ fun CIEventView(ci: ChatItem) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun withChatEventStyle(builder: AnnotatedString.Builder, text: String) {
|
||||
fun withChatEventStyle(builder: AnnotatedString.Builder, text: String) {
|
||||
return builder.withStyle(SpanStyle(fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)) { append(text) }
|
||||
}
|
||||
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package chat.simplex.app.views.chat.item
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleButton
|
||||
|
||||
@Composable
|
||||
fun CIFeaturePreferenceView(
|
||||
chatItem: ChatItem,
|
||||
chatInfo: ChatInfo,
|
||||
feature: ChatFeature,
|
||||
allowed: FeatureAllowed,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(feature.icon, feature.text, Modifier.size(15.dp), tint = HighOrLowlight)
|
||||
Text(chatItem.content.text, fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)
|
||||
if (chatInfo is ChatInfo.Direct && allowed != FeatureAllowed.NO) {
|
||||
val ct = chatInfo.contact
|
||||
if (ct.allowsFeature(feature) && !ct.userAllowsFeature(feature)) {
|
||||
Text(stringResource(R.string.accept), modifier = Modifier.clickable { acceptFeature(ct, feature) },
|
||||
fontSize = 12.sp, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
Text(chatItem.timestampText, fontSize = 12.sp, fontWeight = FontWeight.Light, color = HighOrLowlight)
|
||||
// buildAnnotatedString {
|
||||
// withChatEventStyle(this, chatItem.content.text)
|
||||
// append(" ")
|
||||
// withChatEventStyle(this, chatItem.timestampText)
|
||||
// },
|
||||
// Modifier,
|
||||
// // this is important. Otherwise, aligning will be bad because annotated string has a Span with size 12.sp
|
||||
// fontSize = 12.sp
|
||||
// )
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.*
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.ComposeContextItem
|
||||
import chat.simplex.app.views.chat.ComposeState
|
||||
@@ -42,6 +43,7 @@ fun ChatItemView(
|
||||
joinGroup: (Long) -> Unit,
|
||||
acceptCall: (Contact) -> Unit,
|
||||
scrollToItem: (Long) -> Unit,
|
||||
acceptFeature: (Contact, ChatFeature) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
@@ -49,7 +51,7 @@ fun ChatItemView(
|
||||
val alignment = if (sent) Alignment.CenterEnd else Alignment.CenterStart
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
val revealed = remember { mutableStateOf(false) }
|
||||
val fullDeleteAllowed = remember(cInfo) { cInfo.fullDeletionAllowed }
|
||||
val fullDeleteAllowed = remember(cInfo) { cInfo.featureEnabled(ChatFeature.FullDelete) }
|
||||
val saveFileLauncher = rememberSaveFileLauncher(cxt = context, ciFile = cItem.file)
|
||||
val onLinkLongClick = { _: String -> showMenu.value = true }
|
||||
|
||||
@@ -224,6 +226,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.RcvChatPreference -> CIFeaturePreferenceView(cItem, cInfo, c.feature, c.allowed, acceptFeature)
|
||||
is CIContent.SndChatPreference -> CIChatFeatureView(cItem, c.feature, HighOrLowlight, icon = c.feature.icon,)
|
||||
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)
|
||||
@@ -319,6 +323,7 @@ fun PreviewChatItemView() {
|
||||
joinGroup = {},
|
||||
acceptCall = { _ -> },
|
||||
scrollToItem = {},
|
||||
acceptFeature = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -338,6 +343,7 @@ fun PreviewChatItemViewDeletedContent() {
|
||||
joinGroup = {},
|
||||
acceptCall = { _ -> },
|
||||
scrollToItem = {},
|
||||
acceptFeature = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import SimpleXChat
|
||||
struct CIChatFeatureView: View {
|
||||
var chatItem: ChatItem
|
||||
var feature: Feature
|
||||
var icon: String? = nil
|
||||
var iconColor: Color
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 4) {
|
||||
Image(systemName: feature.iconFilled)
|
||||
Image(systemName: icon ?? feature.iconFilled)
|
||||
.foregroundColor(iconColor)
|
||||
.scaleEffect(feature.iconScale)
|
||||
chatEventText(chatItem)
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// CIFeaturePreferenceView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 21/12/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct CIFeaturePreferenceView: View {
|
||||
@EnvironmentObject var chat: Chat
|
||||
var chatItem: ChatItem
|
||||
var feature: ChatFeature
|
||||
var allowed: FeatureAllowed
|
||||
var param: Int?
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 4) {
|
||||
Image(systemName: feature.icon)
|
||||
.foregroundColor(.secondary)
|
||||
.scaleEffect(feature.iconScale)
|
||||
Text(CIContent.preferenceText(feature, allowed, param))
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.fontWeight(.light)
|
||||
if let ct = chat.chatInfo.contact,
|
||||
allowed != .no && ct.allowsFeature(feature) && !ct.userAllowsFeature(feature) {
|
||||
Button("Accept") { allowFeatureToContact(ct, feature) }
|
||||
.font(.caption)
|
||||
}
|
||||
chatItem.timestampText
|
||||
.font(.caption)
|
||||
.foregroundColor(Color.secondary)
|
||||
.fontWeight(.light)
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
.padding(.bottom, 6)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
func allowFeatureToContact(_ contact: Contact, _ feature: ChatFeature) {
|
||||
Task {
|
||||
do {
|
||||
let prefs = contactUserPreferencesToPreferences(contact.mergedPreferences).setAllowed(feature)
|
||||
if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) {
|
||||
await MainActor.run {
|
||||
ChatModel.shared.updateContact(toContact)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("allowFeatureToContact apiSetContactPrefs error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CIFeaturePreferenceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let content = CIContent.rcvChatPreference(feature: .timedMessages, allowed: .yes, param: 30)
|
||||
let chatItem = ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, content.text, .rcvRead, false, false, false),
|
||||
content: content,
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
)
|
||||
CIFeaturePreferenceView(chatItem: chatItem, feature: ChatFeature.timedMessages, allowed: .yes, param: 30)
|
||||
.environmentObject(Chat.sampleData)
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,10 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case .sndConnEvent: eventItemView()
|
||||
case let .rcvChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .sndChatFeature(feature, enabled, _): chatFeatureView(feature, enabled.iconColor)
|
||||
case let .rcvChatPreference(feature, allowed, param):
|
||||
CIFeaturePreferenceView(chatItem: chatItem, feature: feature, allowed: allowed, param: param)
|
||||
case let .sndChatPreference(feature, _, _):
|
||||
CIChatFeatureView(chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: .secondary)
|
||||
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)
|
||||
|
||||
@@ -595,7 +595,7 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private var broadcastDeleteButtonText: LocalizedStringKey {
|
||||
chat.chatInfo.fullDeletionAllowed ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,15 +222,15 @@ struct ComposeView: View {
|
||||
},
|
||||
sendLiveMessage: sendLiveMessage,
|
||||
updateLiveMessage: updateLiveMessage,
|
||||
voiceMessageAllowed: chat.chatInfo.voiceMessageAllowed,
|
||||
voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice),
|
||||
showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert,
|
||||
startVoiceMessageRecording: {
|
||||
Task {
|
||||
await startVoiceMessageRecording()
|
||||
}
|
||||
},
|
||||
finishVoiceMessageRecording: { finishVoiceMessageRecording() },
|
||||
allowVoiceMessagesToContact: { allowVoiceMessagesToContact() },
|
||||
finishVoiceMessageRecording: finishVoiceMessageRecording,
|
||||
allowVoiceMessagesToContact: allowVoiceMessagesToContact,
|
||||
onImageAdded: { image in chosenImages = [image] },
|
||||
keyboardVisible: $keyboardVisible
|
||||
)
|
||||
@@ -344,7 +344,7 @@ struct ComposeView: View {
|
||||
startingRecording = false
|
||||
}
|
||||
}
|
||||
.onChange(of: chat.chatInfo.voiceMessageAllowed) { vmAllowed in
|
||||
.onChange(of: chat.chatInfo.featureEnabled(.voice)) { vmAllowed in
|
||||
if !vmAllowed && composeState.voicePreview,
|
||||
let fileName = composeState.voiceMessageRecordingFileName {
|
||||
cancelVoiceMessageRecording(fileName)
|
||||
@@ -639,19 +639,7 @@ struct ComposeView: View {
|
||||
|
||||
private func allowVoiceMessagesToContact() {
|
||||
if case let .direct(contact) = chat.chatInfo {
|
||||
Task {
|
||||
do {
|
||||
var prefs = contactUserPreferencesToPreferences(contact.mergedPreferences)
|
||||
prefs.voice = SimplePreference(allow: .yes)
|
||||
if let toContact = try await apiSetContactPrefs(contactId: contact.contactId, preferences: prefs) {
|
||||
await MainActor.run {
|
||||
chatModel.updateContact(toContact)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("ComposeView allowVoiceMessagesToContact, apiSetContactPrefs error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
allowFeatureToContact(contact, .voice)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */; };
|
||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6AD81227A834E300348BD7 /* NewChatButton.swift */; };
|
||||
5C6BA667289BD954009B8ECC /* DismissSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6BA666289BD954009B8ECC /* DismissSheets.swift */; };
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */; };
|
||||
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A127B65FDB00BE3227 /* CIMetaView.swift */; };
|
||||
5C7505A527B679EE00BE3227 /* NavLinkPlain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */; };
|
||||
5C7505A827B6D34800BE3227 /* ChatInfoToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */; };
|
||||
@@ -260,6 +261,7 @@
|
||||
5C5F2B6F27EBC704006A9D5F /* ProfileImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileImage.swift; sourceTree = "<group>"; };
|
||||
5C6AD81227A834E300348BD7 /* NewChatButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton.swift; sourceTree = "<group>"; };
|
||||
5C6BA666289BD954009B8ECC /* DismissSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissSheets.swift; sourceTree = "<group>"; };
|
||||
5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIFeaturePreferenceView.swift; sourceTree = "<group>"; };
|
||||
5C7505A127B65FDB00BE3227 /* CIMetaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMetaView.swift; sourceTree = "<group>"; };
|
||||
5C7505A427B679EE00BE3227 /* NavLinkPlain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavLinkPlain.swift; sourceTree = "<group>"; };
|
||||
5C7505A727B6D34800BE3227 /* ChatInfoToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoToolbar.swift; sourceTree = "<group>"; };
|
||||
@@ -688,6 +690,7 @@
|
||||
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */,
|
||||
6440C9FF288857A10062C672 /* CIEventView.swift */,
|
||||
5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */,
|
||||
5C7031152953C97F00150A12 /* CIFeaturePreferenceView.swift */,
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */,
|
||||
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */,
|
||||
);
|
||||
@@ -1037,6 +1040,7 @@
|
||||
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */,
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */,
|
||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */,
|
||||
5C7031162953C97F00150A12 /* CIFeaturePreferenceView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -153,6 +153,22 @@ public struct Preferences: Codable {
|
||||
self.voice = voice
|
||||
}
|
||||
|
||||
func copy(timedMessages: TimedMessagesPreference? = nil, fullDelete: SimplePreference? = nil, voice: SimplePreference? = nil) -> Preferences {
|
||||
Preferences(
|
||||
timedMessages: timedMessages ?? self.timedMessages,
|
||||
fullDelete: fullDelete ?? self.fullDelete,
|
||||
voice: voice ?? self.voice
|
||||
)
|
||||
}
|
||||
|
||||
public func setAllowed(_ feature: ChatFeature, allowed: FeatureAllowed = .yes) -> Preferences {
|
||||
switch feature {
|
||||
case .timedMessages: return copy(timedMessages: TimedMessagesPreference(allow: allowed, ttl: timedMessages?.ttl))
|
||||
case .fullDelete: return copy(fullDelete: SimplePreference(allow: allowed))
|
||||
case .voice: return copy(voice: SimplePreference(allow: allowed))
|
||||
}
|
||||
}
|
||||
|
||||
public static let sampleData = Preferences(
|
||||
timedMessages: TimedMessagesPreference(allow: .no),
|
||||
fullDelete: SimplePreference(allow: .no),
|
||||
@@ -256,7 +272,7 @@ public struct ContactUserPreferences: Decodable {
|
||||
public static let sampleData = ContactUserPreferences(
|
||||
timedMessages: ContactUserPreference<TimedMessagesPreference>(
|
||||
enabled: FeatureEnabled(forUser: false, forContact: false),
|
||||
userPreference: ContactUserPref<TimedMessagesPreference>.user(preference: TimedMessagesPreference(allow: .no)),
|
||||
userPreference: ContactUserPref<TimedMessagesPreference>.user(preference: TimedMessagesPreference(allow: .yes)),
|
||||
contactPreference: TimedMessagesPreference(allow: .no)
|
||||
),
|
||||
fullDelete: ContactUserPreference<SimplePreference>(
|
||||
@@ -335,7 +351,9 @@ public enum ContactUserPref<P: Preference>: Decodable {
|
||||
}
|
||||
|
||||
public protocol Feature {
|
||||
var icon: String { get }
|
||||
var iconFilled: String { get }
|
||||
var iconScale: CGFloat { get }
|
||||
var hasParam: Bool { get }
|
||||
var text: String { get }
|
||||
}
|
||||
@@ -373,7 +391,7 @@ public enum ChatFeature: String, Decodable, Feature {
|
||||
|
||||
public var icon: String {
|
||||
switch self {
|
||||
case .timedMessages: return "timer"
|
||||
case .timedMessages: return "stopwatch"
|
||||
case .fullDelete: return "trash.slash"
|
||||
case .voice: return "mic"
|
||||
}
|
||||
@@ -381,12 +399,19 @@ public enum ChatFeature: String, Decodable, Feature {
|
||||
|
||||
public var iconFilled: String {
|
||||
switch self {
|
||||
case .timedMessages: return "timer"
|
||||
case .timedMessages: return "stopwatch.fill"
|
||||
case .fullDelete: return "trash.slash.fill"
|
||||
case .voice: return "mic.fill"
|
||||
}
|
||||
}
|
||||
|
||||
public var iconScale: CGFloat {
|
||||
switch self {
|
||||
case .timedMessages: return 0.9
|
||||
default: return 1
|
||||
}
|
||||
}
|
||||
|
||||
public func allowDescription(_ allowed: FeatureAllowed) -> LocalizedStringKey {
|
||||
switch self {
|
||||
case .timedMessages:
|
||||
@@ -484,6 +509,13 @@ public enum GroupFeature: String, Decodable, Feature {
|
||||
}
|
||||
}
|
||||
|
||||
public var iconScale: CGFloat {
|
||||
switch self {
|
||||
case .timedMessages: return 0.9
|
||||
default: return 1
|
||||
}
|
||||
}
|
||||
|
||||
public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey {
|
||||
if canEdit {
|
||||
switch self {
|
||||
@@ -879,18 +911,23 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
}
|
||||
}
|
||||
|
||||
public var voiceMessageAllowed: Bool {
|
||||
// this works for features that are common for contacts and groups
|
||||
public func featureEnabled(_ feature: ChatFeature) -> Bool {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.mergedPreferences.voice.enabled.forUser
|
||||
case let .group(groupInfo): return groupInfo.fullGroupPreferences.voice.on
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
public var fullDeletionAllowed: Bool {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.mergedPreferences.fullDelete.enabled.forUser
|
||||
case let .group(groupInfo): return groupInfo.fullGroupPreferences.fullDelete.on
|
||||
case let .direct(contact):
|
||||
let cups = contact.mergedPreferences
|
||||
switch feature {
|
||||
case .timedMessages: return cups.timedMessages.enabled.forUser
|
||||
case .fullDelete: return cups.fullDelete.enabled.forUser
|
||||
case .voice: return cups.voice.enabled.forUser
|
||||
}
|
||||
case let .group(groupInfo):
|
||||
let prefs = groupInfo.fullGroupPreferences
|
||||
switch feature {
|
||||
case .timedMessages: return prefs.timedMessages.on
|
||||
case .fullDelete: return prefs.fullDelete.on
|
||||
case .voice: return prefs.voice.on
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
@@ -1028,6 +1065,22 @@ public struct Contact: Identifiable, Decodable, NamedChat {
|
||||
activeConn.customUserProfileId != nil
|
||||
}
|
||||
|
||||
public func allowsFeature(_ feature: ChatFeature) -> Bool {
|
||||
switch feature {
|
||||
case .timedMessages: return mergedPreferences.timedMessages.contactPreference.allow != .no
|
||||
case .fullDelete: return mergedPreferences.fullDelete.contactPreference.allow != .no
|
||||
case .voice: return mergedPreferences.voice.contactPreference.allow != .no
|
||||
}
|
||||
}
|
||||
|
||||
public func userAllowsFeature(_ feature: ChatFeature) -> Bool {
|
||||
switch feature {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public static let sampleData = Contact(
|
||||
contactId: 1,
|
||||
localDisplayName: "alice",
|
||||
@@ -1637,6 +1690,8 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
case .sndConnEvent: return showNtfDir
|
||||
case .rcvChatFeature: return false
|
||||
case .sndChatFeature: return showNtfDir
|
||||
case .rcvChatPreference: return false
|
||||
case .sndChatPreference: return showNtfDir
|
||||
case .rcvGroupFeature: return false
|
||||
case .sndGroupFeature: return showNtfDir
|
||||
case .rcvChatFeatureRejected: return showNtfDir
|
||||
@@ -1892,6 +1947,8 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case sndConnEvent(sndConnEvent: SndConnEvent)
|
||||
case rcvChatFeature(feature: ChatFeature, enabled: FeatureEnabled, param: Int?)
|
||||
case sndChatFeature(feature: ChatFeature, enabled: FeatureEnabled, param: Int?)
|
||||
case rcvChatPreference(feature: ChatFeature, allowed: FeatureAllowed, param: Int?)
|
||||
case sndChatPreference(feature: ChatFeature, allowed: FeatureAllowed, param: Int?)
|
||||
case rcvGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
|
||||
case sndGroupFeature(groupFeature: GroupFeature, preference: GroupPreference, param: Int?)
|
||||
case rcvChatFeatureRejected(feature: ChatFeature)
|
||||
@@ -1915,6 +1972,8 @@ public enum CIContent: Decodable, ItemContent {
|
||||
case let .sndConnEvent(sndConnEvent): return sndConnEvent.text
|
||||
case let .rcvChatFeature(feature, enabled, param): return CIContent.featureText(feature, enabled.text, param)
|
||||
case let .sndChatFeature(feature, enabled, param): return CIContent.featureText(feature, enabled.text, param)
|
||||
case let .rcvChatPreference(feature, allowed, param): return CIContent.preferenceText(feature, allowed, param)
|
||||
case let .sndChatPreference(feature, allowed, param): return CIContent.preferenceText(feature, allowed, param)
|
||||
case let .rcvGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
|
||||
case let .sndGroupFeature(feature, preference, param): return CIContent.featureText(feature, preference.enable.text, param)
|
||||
case let .rcvChatFeatureRejected(feature): return String.localizedStringWithFormat("%@: received, prohibited", feature.text)
|
||||
@@ -1923,10 +1982,18 @@ public enum CIContent: Decodable, ItemContent {
|
||||
}
|
||||
}
|
||||
|
||||
static func featureText(_ feature: Feature, _ value: String, _ param: Int?) -> String {
|
||||
static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String {
|
||||
feature.hasParam && param != nil
|
||||
? "\(feature.text): \(TimedMessagesPreference.ttlText(param))"
|
||||
: "\(feature.text): \(value)"
|
||||
: "\(feature.text): \(enabled)"
|
||||
}
|
||||
|
||||
public static func preferenceText(_ feature: Feature, _ allowed: FeatureAllowed, _ param: Int?) -> String {
|
||||
allowed != .no && feature.hasParam && param != nil
|
||||
? "offered \(feature.text): \(TimedMessagesPreference.ttlText(param))"
|
||||
: allowed != .no
|
||||
? "offered \(feature.text)"
|
||||
: "cancelled \(feature.text)"
|
||||
}
|
||||
|
||||
public var msgContent: MsgContent? {
|
||||
|
||||
Reference in New Issue
Block a user