diff --git a/apps/ios/Shared/SharedAssets.xcassets/Contents.json b/apps/ios/Shared/SharedAssets.xcassets/Contents.json
new file mode 100644
index 0000000000..4aa7c5350b
--- /dev/null
+++ b/apps/ios/Shared/SharedAssets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
\ No newline at end of file
diff --git a/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/Contents.json b/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/Contents.json
new file mode 100644
index 0000000000..300553af5f
--- /dev/null
+++ b/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "agent.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
\ No newline at end of file
diff --git a/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/agent.svg b/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/agent.svg
new file mode 100644
index 0000000000..fd0fdb485a
--- /dev/null
+++ b/apps/ios/Shared/SharedAssets.xcassets/agent.imageset/agent.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatLinkHeader.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatLinkHeader.swift
index aaaa29929d..1eebc5cf19 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatLinkHeader.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatLinkHeader.swift
@@ -50,7 +50,7 @@ struct CIChatLinkHeader: View {
HStack(alignment: .top) {
ProfileImage(
imageStr: chatLink.image,
- iconName: chatLink.iconName,
+ icon: Image(systemName: chatLink.iconName),
size: 44,
color: Color(uiColor: .tertiaryLabel)
)
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
index fecd1ba891..efec44e943 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift
@@ -115,7 +115,7 @@ struct CIGroupInvitationView: View {
return HStack(alignment: .top) {
ProfileImage(
imageStr: groupInvitation.groupProfile.image,
- iconName: "person.2.circle.fill",
+ icon: Image(systemName: "person.2.circle.fill"),
size: 44,
color: color
)
diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift
index d18d8ae145..29a1be3d65 100644
--- a/apps/ios/Shared/Views/Chat/ChatView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatView.swift
@@ -1968,7 +1968,7 @@ struct ChatView: View {
.padding(.trailing, 12)
}
HStack(alignment: .top, spacing: 10) {
- ProfileImage(imageStr: groupInfo.image, iconName: groupInfo.chatIconName, size: memberImageSize, backgroundColor: theme.colors.background)
+ ProfileImage(imageStr: groupInfo.image, icon: Image(systemName: groupInfo.chatIconName), size: memberImageSize, backgroundColor: theme.colors.background)
.simultaneousGesture(TapGesture().onEnded {
showChatInfoSheet = true
})
@@ -2052,7 +2052,7 @@ struct ChatView: View {
}
HStack(alignment: .top, spacing: 10) {
if ci.meta.showGroupAsSender {
- ProfileImage(imageStr: groupInfo.image, iconName: groupInfo.chatIconName, size: memberImageSize, backgroundColor: theme.colors.background)
+ ProfileImage(imageStr: groupInfo.image, icon: Image(systemName: groupInfo.chatIconName), size: memberImageSize, backgroundColor: theme.colors.background)
.simultaneousGesture(TapGesture().onEnded {
showChatInfoSheet = true
})
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeChatLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeChatLinkView.swift
index 650ea8a87f..78f29c3b60 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeChatLinkView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeChatLinkView.swift
@@ -11,7 +11,7 @@ struct ComposeChatLinkView: View {
HStack(alignment: .center, spacing: 8) {
ProfileImage(
imageStr: chatLink.image,
- iconName: chatLink.iconName,
+ icon: Image(systemName: chatLink.iconName),
size: 44
)
.padding(.leading, 12)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift
index 126fcc57b3..19478753af 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift
@@ -37,7 +37,7 @@ struct GroupProfileView: View {
var body: some View {
List {
- EditProfileImage(profileImage: $groupProfile.image, iconName: groupInfo.chatIconName, showChooseSource: $showChooseSource)
+ EditProfileImage(profileImage: $groupProfile.image, icon: Image(systemName: groupInfo.chatIconName), showChooseSource: $showChooseSource)
.if(!focusDisplayName) { $0.padding(.top) }
Section {
diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
index 5ee991a466..8156ea81c7 100644
--- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
@@ -444,7 +444,7 @@ struct ChatPreviewView: View {
smallContentPreview(size: dynamicMediaSize, borderColor: chatLink.image != nil ? .secondary : .clear) {
ProfileImage(
imageStr: chatLink.image,
- iconName: chatLink.iconName,
+ icon: Image(systemName: chatLink.iconName),
size: dynamicMediaSize,
color: Color(uiColor: .tertiaryLabel)
)
diff --git a/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift b/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift
index 40d62e009b..a0f21aef9b 100644
--- a/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift
+++ b/apps/ios/Shared/Views/Helpers/ChatInfoImage.swift
@@ -19,7 +19,7 @@ struct ChatInfoImage: View {
let iconColor = if case .local = chat.chatInfo { theme.appColors.primaryVariant2 } else { color }
return ProfileImage(
imageStr: chat.chatInfo.image,
- iconName: chatIconName(chat.chatInfo),
+ icon: chatIcon(chat.chatInfo),
size: size,
color: iconColor
)
diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift
index 9c2916880c..05b4a06967 100644
--- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift
+++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift
@@ -12,7 +12,7 @@ import SimpleXChat
struct ProfileImage: View {
@EnvironmentObject var theme: AppTheme
var imageStr: String? = nil
- var iconName: String = "person.crop.circle.fill"
+ var icon: Image = Image(systemName: "person.crop.circle.fill")
var size: CGFloat
var color = Color(uiColor: .tertiarySystemGroupedBackground)
var backgroundColor: Color? = nil
@@ -24,7 +24,7 @@ struct ProfileImage: View {
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius, blurred: blurred)
} else {
let c = color.asAnotherColorFromSecondaryVariant(theme)
- Image(systemName: iconName)
+ icon
.resizable()
.foregroundColor(c)
.scaledToFit()
diff --git a/apps/ios/Shared/Views/NewChat/AddChannelView.swift b/apps/ios/Shared/Views/NewChat/AddChannelView.swift
index 7d1e5ce827..fa953b2ec1 100644
--- a/apps/ios/Shared/Views/NewChat/AddChannelView.swift
+++ b/apps/ios/Shared/Views/NewChat/AddChannelView.swift
@@ -50,7 +50,7 @@ struct AddChannelView: View {
Spacer(minLength: 0)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
- ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
+ ProfileImage(imageStr: profile.image, icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"), size: 128)
if profile.image != nil {
Button {
profile.image = nil
@@ -285,7 +285,7 @@ struct AddChannelView: View {
let total = groupRelays.count
return List {
Group {
- ProfileImage(imageStr: gInfo.groupProfile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
+ ProfileImage(imageStr: gInfo.groupProfile.image, icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"), size: 128)
.frame(maxWidth: .infinity, alignment: .center)
Text(gInfo.groupProfile.displayName)
diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift
index 47afee5f06..a4f02e51ba 100644
--- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift
+++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift
@@ -71,7 +71,7 @@ struct AddGroupView: View {
Spacer(minLength: 0)
ZStack(alignment: .center) {
ZStack(alignment: .topTrailing) {
- ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128)
+ ProfileImage(imageStr: profile.image, icon: Image(systemName: "person.2.circle.fill"), size: 128)
if profile.image != nil {
Button {
profile.image = nil
diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift
index ef691b10f9..b558802d36 100644
--- a/apps/ios/Shared/Views/NewChat/NewChatView.swift
+++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift
@@ -1129,11 +1129,9 @@ private func showPrepareContactAlert(
profileImage:
ProfileImage(
imageStr: contactShortLinkData.profile.image,
- iconName: contactShortLinkData.business
- ? "briefcase.circle.fill"
- : contactShortLinkData.profile.peerType == .bot
- ? "cube.fill"
- : "person.crop.circle.fill",
+ icon: contactShortLinkData.business
+ ? Image(systemName: "briefcase.circle.fill")
+ : peerTypeIcon(contactShortLinkData.profile.peerType),
size: alertProfileImageSize
),
profileBadge: contactShortLinkData.localBadge,
@@ -1186,9 +1184,9 @@ private func showPrepareGroupAlert(
profileImage:
ProfileImage(
imageStr: groupShortLinkData.groupProfile.image,
- iconName: isChannel
+ icon: Image(systemName: isChannel
? "antenna.radiowaves.left.and.right.circle.fill"
- : "person.2.circle.fill",
+ : "person.2.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
@@ -1240,7 +1238,7 @@ private func showOpenKnownContactAlert(
profileImage:
ProfileImage(
imageStr: contact.profile.image,
- iconName: contact.chatIconName,
+ icon: contact.chatIcon,
size: alertProfileImageSize
),
profileBadge: contact.active ? contact.profile.localBadge : nil,
@@ -1288,7 +1286,7 @@ private func showOpenKnownGroupAlert(
profileImage:
ProfileImage(
imageStr: groupInfo.groupProfile.image,
- iconName: groupInfo.chatIconName,
+ icon: Image(systemName: groupInfo.chatIconName),
size: alertProfileImageSize
),
theme: theme,
@@ -1605,7 +1603,7 @@ func planAndConnect(
profileImage:
ProfileImage(
imageStr: groupSLinkData.groupProfile.image,
- iconName: "antenna.radiowaves.left.and.right.circle.fill",
+ icon: Image(systemName: "antenna.radiowaves.left.and.right.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
@@ -1632,7 +1630,7 @@ func planAndConnect(
profileImage:
ProfileImage(
imageStr: groupSLinkData.groupProfile.image,
- iconName: "person.2.circle.fill",
+ icon: Image(systemName: "person.2.circle.fill"),
size: alertProfileImageSize
),
theme: theme,
diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift
index 8fe8a1c45a..64c877f9cd 100644
--- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift
@@ -29,7 +29,7 @@ struct UserProfile: View {
var body: some View {
List {
- EditProfileImage(profileImage: $profile.image, iconName: "person.crop.circle.fill", showChooseSource: $showChooseSource)
+ EditProfileImage(profileImage: $profile.image, icon: Image(systemName: "person.crop.circle.fill"), showChooseSource: $showChooseSource)
.padding(.top)
Section {
@@ -221,7 +221,7 @@ struct EditProfileImage: View {
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
@Binding var profileImage: String?
- var iconName: String
+ var icon: Image
@Binding var showChooseSource: Bool
var body: some View {
@@ -237,7 +237,7 @@ struct EditProfileImage: View {
}
} else {
ZStack(alignment: .center) {
- ProfileImage(imageStr: profileImage, iconName: iconName, size: 160)
+ ProfileImage(imageStr: profileImage, icon: icon, size: 160)
editImageButton { showChooseSource = true }
}
}
diff --git a/apps/ios/SimpleX SE/ShareView.swift b/apps/ios/SimpleX SE/ShareView.swift
index 3bdbc950ba..fd163f74e5 100644
--- a/apps/ios/SimpleX SE/ShareView.swift
+++ b/apps/ios/SimpleX SE/ShareView.swift
@@ -25,7 +25,7 @@ struct ShareView: View {
HStack {
profileImage(
chatInfoId: chat.chatInfo.id,
- iconName: chatIconName(chat.chatInfo),
+ icon: chatIcon(chat.chatInfo),
size: 30
)
Text(chat.chatInfo.displayName).foregroundStyle(
@@ -209,11 +209,11 @@ struct ShareView: View {
.background(Material.ultraThin)
}
- @ViewBuilder private func profileImage(chatInfoId: ChatInfo.ID, iconName: String, size: Double) -> some View {
+ @ViewBuilder private func profileImage(chatInfoId: ChatInfo.ID, icon: Image, size: Double) -> some View {
if let uiImage = model.profileImages[chatInfoId] {
clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius)
} else {
- Image(systemName: iconName)
+ icon
.resizable()
.foregroundColor(Color(uiColor: .tertiaryLabel))
.frame(width: size, height: size)
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index fb29f753b0..389017cce0 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -271,6 +271,8 @@
E5AEC0AF2F91A73500270665 /* ComposeChatLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */; };
E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; };
E5C0BBE92F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; };
+ E5C0BBEB2F82B45500EA7527 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */; };
+ E5C0BBEC2F82B45500EA7527 /* SharedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */; };
E5DBF1932F88169800E1D7FD /* ConnectBannerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */; };
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
@@ -662,6 +664,7 @@
E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatLinkHeader.swift; sourceTree = ""; };
E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeChatLinkView.swift; sourceTree = ""; };
E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SimpleXAssets.xcassets; sourceTree = ""; };
+ E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SharedAssets.xcassets; sourceTree = ""; };
E5C0BBFD2F82BBC000EA7527 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
E5C0BBFE2F82BBC900EA7527 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectBannerCard.swift; sourceTree = ""; };
@@ -953,6 +956,7 @@
5C2E260D27A30E2400F70299 /* Views */,
5CA059C5279559F40002BEB4 /* Assets.xcassets */,
E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */,
+ E5C0BBEA2F82B45500EA7527 /* SharedAssets.xcassets */,
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */,
5C13730C2815740A00F43030 /* DebugJSON.playground */,
);
@@ -1506,6 +1510,7 @@
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */,
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */,
E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */,
+ E5C0BBEB2F82B45500EA7527 /* SharedAssets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1538,6 +1543,7 @@
files = (
E5DCF9712C590272007928CC /* Localizable.strings in Resources */,
E5C0BBE92F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */,
+ E5C0BBEC2F82B45500EA7527 /* SharedAssets.xcassets in Resources */,
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index a27f396a54..a4d65a6d10 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -228,6 +228,7 @@ public enum ChatPeerType: Hashable {
case human
case bot
case business
+ case agent
case unknown(String)
public var text: String {
@@ -235,6 +236,7 @@ public enum ChatPeerType: Hashable {
case .human: "human"
case .bot: "bot"
case .business: "business"
+ case .agent: "agent"
case let .unknown(s): s
}
}
@@ -246,6 +248,7 @@ extension ChatPeerType: Codable {
case "human": self = .human
case "bot": self = .bot
case "business": self = .business
+ case "agent": self = .agent
case let s: self = .unknown(s)
}
}
@@ -256,6 +259,14 @@ extension ChatPeerType: Codable {
}
}
+public func peerTypeIcon(_ peerType: ChatPeerType?) -> Image {
+ switch peerType {
+ case .bot: Image(systemName: "cube.fill")
+ case .agent: Image("agent")
+ default: Image(systemName: "person.crop.circle.fill")
+ }
+}
+
// Supporter badge. The credential/proof bytes stay core-side; the UI only sees the disclosed type + status.
// Unknown types keep their string so a verified badge's real name can be shown, while the icon falls back to supporter.
public enum BadgeType: Hashable {
@@ -2532,8 +2543,8 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
activeConn?.customUserProfileId != nil
}
- public var chatIconName: String {
- isBot ? "cube.fill" : "person.crop.circle.fill"
+ public var chatIcon: Image {
+ peerTypeIcon(profile.peerType)
}
public func allowsFeature(_ feature: ChatFeature) -> Bool {
diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift
index 45320c5b93..ab9cad9de6 100644
--- a/apps/ios/SimpleXChat/ChatUtils.swift
+++ b/apps/ios/SimpleXChat/ChatUtils.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import SwiftUI
public protocol ChatLike {
var chatInfo: ChatInfo { get }
@@ -91,13 +92,13 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
}
}
-public func chatIconName(_ cInfo: ChatInfo) -> String {
+public func chatIcon(_ cInfo: ChatInfo) -> Image {
switch cInfo {
- case let .direct(contact): contact.chatIconName
- case let .group(groupInfo, _): groupInfo.chatIconName
- case .local: "folder.circle.fill"
- case .contactRequest: "person.crop.circle.fill"
- default: "circle.fill"
+ case let .direct(contact): contact.chatIcon
+ case let .group(groupInfo, _): Image(systemName: groupInfo.chatIconName)
+ case .local: Image(systemName: "folder.circle.fill")
+ case .contactRequest: Image(systemName: "person.crop.circle.fill")
+ default: Image(systemName: "circle.fill")
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index 3db735ecea..bf856c13af 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -1954,7 +1954,7 @@ data class Contact(
activeConn?.customUserProfileId != null
val chatIconName: ImageResource get() =
- if (isBot) MR.images.ic_cube else MR.images.ic_account_circle_filled
+ peerTypeIcon(profile.peerType)
fun allowsFeature(feature: ChatFeature): Boolean = when (feature) {
ChatFeature.TimedMessages -> mergedPreferences.timedMessages.contactPreference.allow != FeatureAllowed.NO
@@ -2165,6 +2165,7 @@ sealed class ChatPeerType {
@Serializable @SerialName("human") object Human: ChatPeerType()
@Serializable @SerialName("bot") object Bot: ChatPeerType()
@Serializable @SerialName("business") object Business: ChatPeerType()
+ @Serializable @SerialName("agent") object Agent: ChatPeerType()
@Serializable @SerialName("unknown") data class Unknown(val type: String): ChatPeerType()
val text: String
@@ -2172,6 +2173,7 @@ sealed class ChatPeerType {
is Human -> "human"
is Bot -> "bot"
is Business -> "business"
+ is Agent -> "agent"
is Unknown -> type
}
}
@@ -2183,11 +2185,19 @@ object ChatPeerTypeSerializer : KSerializer {
"human" -> ChatPeerType.Human
"bot" -> ChatPeerType.Bot
"business" -> ChatPeerType.Business
+ "agent" -> ChatPeerType.Agent
else -> ChatPeerType.Unknown(v)
}
override fun serialize(encoder: Encoder, value: ChatPeerType) = encoder.encodeString(value.text)
}
+fun peerTypeIcon(peerType: ChatPeerType?): ImageResource =
+ when (peerType) {
+ ChatPeerType.Bot -> MR.images.ic_cube
+ ChatPeerType.Agent -> MR.images.ic_agent
+ else -> MR.images.ic_account_circle_filled
+ }
+
// Supporter badge. The credential/proof bytes stay core-side; the UI only sees the disclosed type + status.
// Unknown types keep their string so a verified badge's real name can be shown, while the icon falls back to supporter.
@Serializable(with = BadgeTypeSerializer::class)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 785965eff4..54d684160e 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -8139,7 +8139,7 @@ sealed class StoreError {
is UserChatRelayNotFound -> "userChatRelayNotFound $chatRelayId"
is GroupRelayNotFound -> "groupRelayNotFound $groupRelayId"
is GroupRelayNotFoundByMemberId -> "groupRelayNotFoundByMemberId $groupMemberId"
- is DuplicateGroupMessage -> "duplicateGroupMessage $groupId $sharedMsgId $authorGroupMemberId $authorGroupMemberId"
+ is DuplicateGroupMessage -> "duplicateGroupMessage $groupId $sharedMsgId $authorGroupMemberId $forwardedByGroupMemberId"
is RemoteHostNotFound -> "remoteHostNotFound $remoteHostId"
is RemoteHostUnknown -> "remoteHostUnknown"
is RemoteHostDuplicateCA -> "remoteHostDuplicateCA"
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt
index 61cb73ca0b..8b145ef206 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt
@@ -717,8 +717,7 @@ fun showPrepareContactAlert(
image = contactShortLinkData.profile.image,
icon =
if (contactShortLinkData.business) MR.images.ic_work_filled_padded
- else if (contactShortLinkData.profile.peerType == ChatPeerType.Bot) MR.images.ic_cube
- else MR.images.ic_account_circle_filled
+ else peerTypeIcon(contactShortLinkData.profile.peerType)
)
},
profileBadge = if (contactShortLinkData.localBadge?.status == BadgeStatus.ExpiredOld) null else contactShortLinkData.localBadge,
diff --git a/apps/multiplatform/common/src/commonMain/moko-resources/images/ic_agent.svg b/apps/multiplatform/common/src/commonMain/moko-resources/images/ic_agent.svg
new file mode 100644
index 0000000000..e03226d798
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/moko-resources/images/ic_agent.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md
index fe79d847e2..6ca0d67ac4 100644
--- a/bots/api/TYPES.md
+++ b/bots/api/TYPES.md
@@ -1545,6 +1545,7 @@ Search:
- "human"
- "bot"
- "business"
+- "agent"
---
diff --git a/docs/protocol/simplex-chat.schema.json b/docs/protocol/simplex-chat.schema.json
index 4e9c3c73a0..5fb4794094 100644
--- a/docs/protocol/simplex-chat.schema.json
+++ b/docs/protocol/simplex-chat.schema.json
@@ -25,7 +25,7 @@
"metadata": {"comment": "profile bio (160 characters)"}
},
"contactLink": {"ref": "connReqUri"},
- "peerType": {"enum": ["human", "bot"]},
+ "peerType": {"enum": ["human", "bot", "agent"]},
"preferences": {
"optionalProperties": {
"commands": {
diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts
index cebc68abb4..35711c62cf 100644
--- a/packages/simplex-chat-client/types/typescript/src/types.ts
+++ b/packages/simplex-chat-client/types/typescript/src/types.ts
@@ -1796,6 +1796,7 @@ export enum ChatPeerType {
Human = "human",
Bot = "bot",
Business = "business",
+ Agent = "agent",
}
// Used in API commands. Chat scope can only be passed with groups.
diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py
index 1befa750c3..f732d7faf2 100644
--- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py
+++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py
@@ -1247,7 +1247,7 @@ ChatListQuery = ChatListQuery_filters | ChatListQuery_search
ChatListQuery_Tag = Literal["filters", "search"]
-ChatPeerType = Literal["human", "bot", "business"]
+ChatPeerType = Literal["human", "bot", "business", "agent"]
# Used in API commands. Chat scope can only be passed with groups.
diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs
index 81ed6ac5a8..94fd5d4fcd 100644
--- a/src/Simplex/Chat/Types.hs
+++ b/src/Simplex/Chat/Types.hs
@@ -749,7 +749,7 @@ data Profile = Profile
}
deriving (Eq, Show)
-data ChatPeerType = CPTHuman | CPTBot | CPTBusiness | CPTUnknown Text
+data ChatPeerType = CPTHuman | CPTBot | CPTBusiness | CPTAgent | CPTUnknown Text
deriving (Eq, Show)
instance FromJSON ChatPeerType where
@@ -768,11 +768,13 @@ instance TextEncoding ChatPeerType where
"human" -> CPTHuman
"bot" -> CPTBot
"business" -> CPTBusiness
+ "agent" -> CPTAgent
tag -> CPTUnknown tag
textEncode = \case
CPTHuman -> "human"
CPTBot -> "bot"
CPTBusiness -> "business"
+ CPTAgent -> "agent"
CPTUnknown tag -> tag
profileFromName :: ContactName -> Profile
diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs
index 35f1ea5885..fb9e8317c3 100644
--- a/tests/ProtocolTests.hs
+++ b/tests/ProtocolTests.hs
@@ -332,6 +332,9 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
it "x.info with empty full name" $
"{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} Nothing
+ it "x.info with agent peer type" $
+ "{\"v\":\"9\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"peerType\":\"agent\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
+ #==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTAgent, preferences = testChatPreferences, badge = Nothing, contactDomain = Nothing} Nothing
it "x.contact with xContactId" $
"{\"v\":\"9\",\"event\":\"x.contact\",\"params\":{\"contactReqId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
#==# XContact testProfile Nothing (Just $ XContactId "\1\2\3\4") Nothing Nothing