From a5768e903d9d1849ce7fb9d115407682d1d00012 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 11 Jul 2025 21:28:10 +0100 Subject: [PATCH] core, ui: short bio/description for contact and group profiles (#6055) * core: short bio/description for contact and group profiles * use short description in commands * fix tests * query plans * ios: show contact and group profile descriptions * android, desktop: short description/bio * ui: layout for chat info, correct copy * comment out fields to add/edit short descriptions * remove short description from events * postgres schema --- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 84 ++++---- .../Views/Chat/Group/GroupChatInfoView.swift | 38 ++-- .../Chat/Group/GroupMemberInfoView.swift | 16 +- .../Views/Chat/Group/GroupProfileView.swift | 2 + .../Views/Onboarding/CreateProfile.swift | 7 +- .../Views/UserSettings/UserProfile.swift | 2 + apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 +- apps/ios/SimpleXChat/ChatTypes.swift | 47 ++++- .../chat/simplex/common/model/ChatModel.kt | 28 ++- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../chat/simplex/common/views/WelcomeView.kt | 19 +- .../simplex/common/views/chat/ChatInfoView.kt | 53 +++-- .../views/chat/group/GroupChatInfoView.kt | 26 +-- .../views/chat/group/GroupMemberInfoView.kt | 22 +- .../views/chat/group/GroupProfileView.kt | 25 ++- .../views/chat/item/CIGroupInvitationView.kt | 2 +- .../common/views/localauth/LocalAuthView.kt | 2 +- .../common/views/newchat/AddGroupView.kt | 1 + .../views/usersettings/UserProfileView.kt | 34 +++- .../commonMain/resources/MR/base/strings.xml | 3 + .../src/Directory/Service.hs | 23 ++- simplex-chat.cabal | 2 + src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Core.hs | 2 +- src/Simplex/Chat/Help.hs | 8 +- src/Simplex/Chat/Library/Commands.hs | 33 +-- src/Simplex/Chat/Library/Internal.hs | 6 +- src/Simplex/Chat/Library/Subscriber.hs | 6 +- src/Simplex/Chat/Messages/CIContent.hs | 4 +- src/Simplex/Chat/ProfileGenerator.hs | 2 +- src/Simplex/Chat/Store/Connections.hs | 12 +- src/Simplex/Chat/Store/ContactRequest.hs | 13 +- src/Simplex/Chat/Store/Direct.hs | 24 +-- src/Simplex/Chat/Store/Groups.hs | 106 +++++----- src/Simplex/Chat/Store/Messages.hs | 44 ++-- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../M20250709_profile_short_descr.hs | 23 +++ .../Store/Postgres/Migrations/chat_schema.sql | 6 +- src/Simplex/Chat/Store/Profiles.hs | 14 +- src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 +- .../M20250709_profile_short_descr.hs | 20 ++ .../SQLite/Migrations/chat_query_plans.txt | 189 ++++++++---------- .../Store/SQLite/Migrations/chat_schema.sql | 6 +- src/Simplex/Chat/Store/Shared.hs | 63 +++--- src/Simplex/Chat/Types.hs | 25 ++- src/Simplex/Chat/View.hs | 75 +++---- tests/Bots/BroadcastTests.hs | 2 +- tests/Bots/DirectoryTests.hs | 48 ++--- tests/ChatTests/Groups.hs | 62 ++++-- tests/ChatTests/Local.hs | 2 +- tests/ChatTests/Profiles.hs | 71 +++---- tests/ChatTests/Utils.hs | 23 ++- tests/JSONFixtures.hs | 6 +- tests/ProtocolTests.hs | 6 +- tests/RemoteTests.hs | 8 +- 55 files changed, 810 insertions(+), 563 deletions(-) create mode 100644 src/Simplex/Chat/Store/Postgres/Migrations/M20250709_profile_short_descr.hs create mode 100644 src/Simplex/Chat/Store/SQLite/Migrations/M20250709_profile_short_descr.hs diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 1cf017d8ea..42a14c2395 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -111,7 +111,7 @@ struct ChatInfoView: View { @State private var sendReceiptsUserDefault = true @State private var progressIndicator = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false - + enum ChatInfoViewAlert: Identifiable { case clearChatAlert case networkStatusAlert @@ -135,7 +135,7 @@ struct ChatInfoView: View { } } } - + var body: some View { NavigationView { ZStack { @@ -146,12 +146,12 @@ struct ChatInfoView: View { .onTapGesture { aliasTextFieldFocused = false } - + localAliasTextEdit() .listRowBackground(Color.clear) .listRowSeparator(.hidden) .padding(.bottom, 18) - + GeometryReader { g in HStack(alignment: .center, spacing: 8) { let buttonWidth = g.size.width / 4 @@ -169,7 +169,7 @@ struct ChatInfoView: View { .listRowBackground(Color.clear) .listRowSeparator(.hidden) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8)) - + if let customUserProfile = customUserProfile { Section(header: Text("Incognito").foregroundColor(theme.colors.secondary)) { HStack { @@ -180,7 +180,7 @@ struct ChatInfoView: View { } } } - + Section { if let code = connectionCode { verifyCodeButton(code) } contactPreferencesButton() @@ -203,19 +203,19 @@ struct ChatInfoView: View { // } } .disabled(!contact.ready || !contact.active) - + Section { ChatTTLOption(chat: chat, progressIndicator: $progressIndicator) } footer: { Text("Delete chat messages from your device.") } - + if let conn = contact.activeConn { Section { infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard") } } - + if let contactLink = contact.contactLink { Section { SimpleXLinkQRCode(uri: contactLink) @@ -232,7 +232,7 @@ struct ChatInfoView: View { .foregroundColor(theme.colors.secondary) } } - + if contact.ready && contact.active { Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { networkStatusRow() @@ -261,12 +261,12 @@ struct ChatInfoView: View { } } } - + Section { clearChatButton() deleteContactButton() } - + if developerTools { Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { infoRow("Local name", chat.chatInfo.localDisplayName) @@ -291,7 +291,7 @@ struct ChatInfoView: View { .navigationBarHidden(true) .disabled(progressIndicator) .opacity(progressIndicator ? 0.6 : 1) - + if progressIndicator { ProgressView().scaleEffect(2) } @@ -303,7 +303,7 @@ struct ChatInfoView: View { sendReceiptsUserDefault = currentUser.sendRcptsContacts } sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault) - + Task { do { let (stats, profile) = try await apiContactInfo(chat.chatInfo.apiId) @@ -342,7 +342,7 @@ struct ChatInfoView: View { } } .actionSheet(item: $actionSheet) { $0.actionSheet } - .sheet(item: $sheet) { + .sheet(item: $sheet) { if #available(iOS 16.0, *) { $0.content .presentationDetents([.fraction($0.fraction)]) @@ -361,41 +361,51 @@ struct ChatInfoView: View { } } } - + private func contactInfoHeader() -> some View { VStack(spacing: 8) { let cInfo = chat.chatInfo ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.vertical, 12) + // show actual display name, alias can be edited in this view + let displayName = contact.profile.displayName.trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = cInfo.fullName.trimmingCharacters(in: .whitespacesAndNewlines) if contact.verified { ( Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) + textSpace - + Text(contact.profile.displayName) + + Text(displayName) .font(.largeTitle) ) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } else { - Text(contact.profile.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } - if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName { + if fullName != "" && fullName != displayName && fullName != cInfo.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(cInfo.fullName) .font(.title2) .multilineTextAlignment(.center) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = cInfo.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + Text(descr) + .font(.subheadline) + .multilineTextAlignment(.center) .lineLimit(4) } } .frame(maxWidth: .infinity, alignment: .center) } - + private func localAliasTextEdit() -> some View { TextField("Set contact name…", text: $localAlias) .disableAutocorrection(true) @@ -412,7 +422,7 @@ struct ChatInfoView: View { .multilineTextAlignment(.center) .foregroundColor(theme.colors.secondary) } - + private func setContactAlias() { Task { do { @@ -475,7 +485,7 @@ struct ChatInfoView: View { ) } } - + private func contactPreferencesButton() -> some View { NavigationLink { ContactPreferencesView( @@ -491,7 +501,7 @@ struct ChatInfoView: View { Label("Contact preferences", systemImage: "switch.2") } } - + private func sendReceiptsOption() -> some View { WrappedPicker(selection: $sendReceipts) { ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in @@ -504,7 +514,7 @@ struct ChatInfoView: View { setSendReceipts() } } - + private func setSendReceipts() { var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults chatSettings.sendRcpts = sendReceipts.bool() @@ -524,7 +534,7 @@ struct ChatInfoView: View { .foregroundColor(.orange) } } - + private func synchronizeConnectionButtonForce() -> some View { Button { alert = .syncConnectionForceAlert @@ -533,7 +543,7 @@ struct ChatInfoView: View { .foregroundColor(.red) } } - + private func networkStatusRow() -> some View { HStack { Text("Network status") @@ -546,14 +556,14 @@ struct ChatInfoView: View { serverImage() } } - + private func serverImage() -> some View { let status = networkModel.contactNetworkStatus(contact) return Image(systemName: status.imageName) .foregroundColor(status == .connected ? .green : theme.colors.secondary) .font(.system(size: 12)) } - + private func deleteContactButton() -> some View { Button(role: .destructive) { deleteContactDialog( @@ -569,7 +579,7 @@ struct ChatInfoView: View { .foregroundColor(Color.red) } } - + private func clearChatButton() -> some View { Button() { alert = .clearChatAlert @@ -578,7 +588,7 @@ struct ChatInfoView: View { .foregroundColor(Color.orange) } } - + private func clearChatAlert() -> Alert { Alert( title: Text("Clear conversation?"), @@ -592,14 +602,14 @@ struct ChatInfoView: View { secondaryButton: .cancel() ) } - + private func networkStatusAlert() -> Alert { Alert( title: Text("Network status"), message: Text(networkModel.contactNetworkStatus(contact).statusExplanation) ) } - + private func switchContactAddress() { Task { do { @@ -618,7 +628,7 @@ struct ChatInfoView: View { } } } - + private func abortSwitchContactAddress() { Task { do { @@ -636,7 +646,7 @@ struct ChatInfoView: View { } } } - + private func savePreferences() { Task { do { @@ -668,7 +678,7 @@ struct ChatTTLOption: View { } let defaultTTL = ChatTTL.userDefault(ChatModel.shared.chatItemTTL) Text(defaultTTL.text).tag(defaultTTL) - + if case .chat(let ttl) = chatItemTTL, case .seconds = ttl { Text(ttl.deleteAfterText).tag(chatItemTTL) } @@ -1136,13 +1146,13 @@ func setChatTTL(_ ttl: ChatTTL, hasPreviousTTL: Bool, onCancel: @escaping () -> } else { NSLocalizedString("Enable automatic message deletion?", comment: "alert title") } - + let message = if ttl.neverExpires { NSLocalizedString("Messages in this chat will never be deleted.", comment: "alert message") } else { NSLocalizedString("This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.", comment: "alert message") } - + showAlert(title, message: message) { [ UIAlertAction( diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 2f36e5fa93..0fd4a86ece 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -75,12 +75,12 @@ struct GroupChatInfoView: View { List { groupInfoHeader() .listRowBackground(Color.clear) - + localAliasTextEdit() .listRowBackground(Color.clear) .listRowSeparator(.hidden) .padding(.bottom, 18) - + infoActionButtons() .padding(.horizontal) .frame(maxWidth: .infinity) @@ -124,7 +124,7 @@ struct GroupChatInfoView: View { Text(label) .foregroundColor(theme.colors.secondary) } - + Section { if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT { sendReceiptsOption() @@ -181,7 +181,7 @@ struct GroupChatInfoView: View { leaveGroupButton() } } - + if developerTools { Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { infoRow("Local name", chat.chatInfo.localDisplayName) @@ -193,7 +193,7 @@ struct GroupChatInfoView: View { .navigationBarHidden(true) .disabled(progressIndicator) .opacity(progressIndicator ? 0.6 : 1) - + if progressIndicator { ProgressView().scaleEffect(2) } @@ -234,19 +234,29 @@ struct GroupChatInfoView: View { private func groupInfoHeader() -> some View { VStack { let cInfo = chat.chatInfo + // show actual display name, alias can be edited in this view + let displayName = (cInfo.groupInfo?.groupProfile.displayName ?? cInfo.displayName).trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = cInfo.fullName.trimmingCharacters(in: .whitespacesAndNewlines) ChatInfoImage(chat: chat, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.top, 12) .padding() - Text(cInfo.groupInfo?.groupProfile.displayName ?? cInfo.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(4) .padding(.bottom, 2) - if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName { + if fullName != "" && fullName != displayName && fullName != cInfo.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(cInfo.fullName) .font(.title2) .multilineTextAlignment(.center) - .lineLimit(8) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = cInfo.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + Text(descr) + .font(.subheadline) + .multilineTextAlignment(.center) + .lineLimit(4) } } .frame(maxWidth: .infinity, alignment: .center) @@ -268,7 +278,7 @@ struct GroupChatInfoView: View { .multilineTextAlignment(.center) .foregroundColor(theme.colors.secondary) } - + private func setGroupAlias() { Task { do { @@ -282,7 +292,7 @@ struct GroupChatInfoView: View { } } } - + func infoActionButtons() -> some View { GeometryReader { g in let buttonWidth = g.size.width / 4 @@ -829,11 +839,11 @@ struct GroupPreferencesButton: View { @State var preferences: FullGroupPreferences @State var currentPreferences: FullGroupPreferences var creatingGroup: Bool = false - + private var label: LocalizedStringKey { groupInfo.businessChat == nil ? "Group preferences" : "Chat preferences" } - + var body: some View { NavigationLink { GroupPreferencesView( @@ -851,7 +861,7 @@ struct GroupPreferencesButton: View { creatingGroup ? "Save" : "Save and notify group members", comment: "alert button" ) - + if groupInfo.fullGroupPreferences != preferences { showAlert( title: NSLocalizedString("Save preferences?", comment: "alert title"), @@ -869,7 +879,7 @@ struct GroupPreferencesButton: View { } } } - + private func savePreferences() { Task { do { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 275b8dd357..725b5a61fa 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -444,29 +444,39 @@ struct GroupMemberInfoView: View { MemberProfileImage(mem, size: 192, color: Color(uiColor: .tertiarySystemFill)) .padding(.top, 12) .padding() + // show alias if set, alias cannot be edited in this view + let displayName = mem.displayName.trimmingCharacters(in: .whitespacesAndNewlines) + let fullName = mem.fullName.trimmingCharacters(in: .whitespacesAndNewlines) if mem.verified { ( Text(Image(systemName: "checkmark.shield")) .foregroundColor(theme.colors.secondary) .font(.title2) + textSpace - + Text(mem.displayName) + + Text(displayName) .font(.largeTitle) ) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } else { - Text(mem.displayName) + Text(displayName) .font(.largeTitle) .multilineTextAlignment(.center) .lineLimit(2) .padding(.bottom, 2) } - if mem.fullName != "" && mem.fullName != mem.displayName { + if fullName != "" && fullName != displayName && fullName != mem.memberProfile.displayName.trimmingCharacters(in: .whitespacesAndNewlines) { Text(mem.fullName) .font(.title2) .multilineTextAlignment(.center) + .lineLimit(3) + .padding(.bottom, 2) + } + if let descr = mem.memberProfile.shortDescr?.trimmingCharacters(in: .whitespacesAndNewlines), descr != "" { + Text(descr) + .font(.subheadline) + .multilineTextAlignment(.center) .lineLimit(4) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift index 4de3608735..db1c755682 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupProfileView.swift @@ -55,6 +55,8 @@ struct GroupProfileView: View { if fullName != "" && fullName != groupProfile.displayName { TextField("Group full name (optional)", text: $groupProfile.fullName) } + // TODO enable in v6.4.1, limit to 160 characters + // TextField("Short description", text: Binding(get: {groupProfile.shortDescr ?? ""}, set: {groupProfile.shortDescr = $0})) } footer: { Text("Group profile is stored on members' devices, not on the servers.") } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index ae72cb1be5..dfc8be738e 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -29,6 +29,7 @@ struct CreateProfile: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var theme: AppTheme @State private var displayName: String = "" + @State private var profileBio: String = "" @FocusState private var focusDisplayName @State private var alert: UserProfileAlert? @@ -37,6 +38,8 @@ struct CreateProfile: View { Section { TextField("Enter your name…", text: $displayName) .focused($focusDisplayName) + // TODO enable in v6.4.1, limit to 160 characters + // TextField("Bio", text: $profileBio) Button { createProfile() } label: { @@ -80,9 +83,11 @@ struct CreateProfile: View { private func createProfile() { hideKeyboard() + let shortDescr: String? = if profileBio.isEmpty { nil } else { profileBio } let profile = Profile( displayName: displayName.trimmingCharacters(in: .whitespaces), - fullName: "" + fullName: "", + shortDescr: shortDescr ) let m = ChatModel.shared do { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfile.swift b/apps/ios/Shared/Views/UserSettings/UserProfile.swift index aa2c04ccaa..444975d236 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfile.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfile.swift @@ -43,6 +43,8 @@ struct UserProfile: View { if let user = chatModel.currentUser, showFullName(user) { TextField("Full name (optional)", text: $profile.fullName) } + // TODO enable in v6.4.1, limit to 160 characters + // TextField("Bio", text: Binding(get: {profile.shortDescr ?? ""}, set: {profile.shortDescr = $0})) } footer: { Text("Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.") } diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 2582eb43bc..fc78a3532d 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -178,8 +178,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -543,8 +543,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -704,8 +704,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -790,8 +790,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-Hjmcl0BGDg19a5be1DeLnp.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.5.1-H9b1PUqJfdC45BY0VzYHX.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index cab0e3bda6..cd687f619c 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -32,6 +32,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable { public var displayName: String { get { profile.displayName } } public var fullName: String { get { profile.fullName } } + public var shortDescr: String? { profile.shortDescr } public var image: String? { get { profile.image } } public var localAlias: String { get { "" } } @@ -109,12 +110,14 @@ public struct Profile: Codable, NamedChat, Hashable { public init( displayName: String, fullName: String, + shortDescr: String? = nil, image: String? = nil, contactLink: String? = nil, preferences: Preferences? = nil ) { self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.image = image self.contactLink = contactLink self.preferences = preferences @@ -122,6 +125,7 @@ public struct Profile: Codable, NamedChat, Hashable { public var displayName: String public var fullName: String + public var shortDescr: String? public var image: String? public var contactLink: String? public var preferences: Preferences? @@ -142,6 +146,7 @@ public struct LocalProfile: Codable, NamedChat, Hashable { profileId: Int64, displayName: String, fullName: String, + shortDescr: String? = nil, image: String? = nil, contactLink: String? = nil, preferences: Preferences? = nil, @@ -150,6 +155,7 @@ public struct LocalProfile: Codable, NamedChat, Hashable { self.profileId = profileId self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.image = image self.contactLink = contactLink self.preferences = preferences @@ -159,6 +165,7 @@ public struct LocalProfile: Codable, NamedChat, Hashable { public var profileId: Int64 public var displayName: String public var fullName: String + public var shortDescr: String? public var image: String? public var contactLink: String? public var preferences: Preferences? @@ -180,11 +187,27 @@ public struct LocalProfile: Codable, NamedChat, Hashable { } public func toLocalProfile (_ profileId: Int64, _ profile: Profile, _ localAlias: String) -> LocalProfile { - LocalProfile(profileId: profileId, displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences, localAlias: localAlias) + LocalProfile( + profileId: profileId, + displayName: profile.displayName, + fullName: profile.fullName, + shortDescr: profile.shortDescr, + image: profile.image, + contactLink: profile.contactLink, + preferences: profile.preferences, + localAlias: localAlias + ) } public func fromLocalProfile (_ profile: LocalProfile) -> Profile { - Profile(displayName: profile.displayName, fullName: profile.fullName, image: profile.image, contactLink: profile.contactLink, preferences: profile.preferences) + Profile( + displayName: profile.displayName, + fullName: profile.fullName, + shortDescr: profile.shortDescr, + image: profile.image, + contactLink: profile.contactLink, + preferences: profile.preferences + ) } public struct UserProfileUpdateSummary: Decodable, Hashable { @@ -204,6 +227,7 @@ public enum ChatType: String, Hashable { public protocol NamedChat { var displayName: String { get } var fullName: String { get } + var shortDescr: String? { get } var image: String? { get } var localAlias: String { get } } @@ -1249,6 +1273,17 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var shortDescr: String? { + switch self { + case let .direct(contact): contact.profile.shortDescr + case let .group(groupInfo, _): groupInfo.groupProfile.shortDescr + case .local: nil + case let .contactRequest(contactRequest): contactRequest.profile.shortDescr + case let .contactConnection(contactConnection): nil + case .invalidJSON: nil + } + } + public var image: String? { get { switch self { @@ -1739,6 +1774,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var sendMsgToConnect: Bool { nextSendGrpInv || nextConnectPrepared } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } + public var shortDescr: String? { profile.shortDescr } public var image: String? { get { profile.image } } public var contactLink: String? { get { profile.contactLink } } public var localAlias: String { profile.localAlias } @@ -1925,6 +1961,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable { public var apiId: Int64 { get { contactRequestId } } var ready: Bool { get { true } } public var displayName: String { get { profile.displayName } } + public var shortDescr: String? { profile.shortDescr } public var fullName: String { get { profile.fullName } } public var image: String? { get { profile.image } } public var localAlias: String { "" } @@ -1977,6 +2014,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable { } } public var fullName: String { get { "" } } + public var shortDescr: String? { nil } public var image: String? { get { nil } } public var initiated: Bool { get { (pccConnStatus.initiated ?? false) && !viaContactUri } } @@ -2093,6 +2131,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable { public var profileChangeProhibited: Bool { preparedGroup?.connLinkPreparedConnection ?? false } public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias } public var fullName: String { get { groupProfile.fullName } } + public var shortDescr: String? { groupProfile.shortDescr } public var image: String? { get { groupProfile.image } } public var chatTags: [Int64] public var chatItemTTL: Int64? @@ -2152,6 +2191,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable { public init( displayName: String, fullName: String, + shortDescr: String? = nil, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil, @@ -2159,6 +2199,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable { ) { self.displayName = displayName self.fullName = fullName + self.shortDescr = shortDescr self.description = description self.image = image self.groupPreferences = groupPreferences @@ -2167,6 +2208,7 @@ public struct GroupProfile: Codable, NamedChat, Hashable { public var displayName: String public var fullName: String + public var shortDescr: String? public var description: String? public var image: String? public var groupPreferences: GroupPreferences? @@ -2555,6 +2597,7 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable { public var ready: Bool { get { true } } public var displayName: String { get { ChatInfo.privateNotesChatName } } public var fullName: String { get { "" } } + public var shortDescr: String? { nil } public var image: String? { get { nil } } public var localAlias: String { get { "" } } 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 87f6069cfe..7c2a359107 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 @@ -1233,6 +1233,7 @@ data class User( ): NamedChat, UserLike { override val displayName: String get() = profile.displayName override val fullName: String get() = profile.fullName + override val shortDescr: String? get() = profile.shortDescr override val image: String? get() = profile.image override val localAlias: String = "" @@ -1302,6 +1303,7 @@ typealias ChatId = String interface NamedChat { val displayName: String val fullName: String + val shortDescr: String? val image: String? val localAlias: String val chatViewName: String @@ -1412,6 +1414,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contact.updatedAt override val displayName get() = contact.displayName override val fullName get() = contact.fullName + override val shortDescr get() = contact.profile.shortDescr override val image get() = contact.image override val localAlias: String get() = contact.localAlias override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase) @@ -1439,6 +1442,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = groupInfo.updatedAt override val displayName get() = groupInfo.displayName override val fullName get() = groupInfo.fullName + override val shortDescr get() = groupInfo.groupProfile.shortDescr override val image get() = groupInfo.image override val localAlias get() = groupInfo.localAlias @@ -1465,6 +1469,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = noteFolder.updatedAt override val displayName get() = noteFolder.displayName override val fullName get() = noteFolder.fullName + override val shortDescr get() = null override val image get() = noteFolder.image override val localAlias get() = noteFolder.localAlias @@ -1491,6 +1496,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contactRequest.updatedAt override val displayName get() = contactRequest.displayName override val fullName get() = contactRequest.fullName + override val shortDescr get() = contactRequest.profile.shortDescr override val image get() = contactRequest.image override val localAlias get() = contactRequest.localAlias @@ -1517,6 +1523,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val updatedAt get() = contactConnection.updatedAt override val displayName get() = contactConnection.displayName override val fullName get() = contactConnection.fullName + override val shortDescr get() = null override val image get() = contactConnection.image override val localAlias get() = contactConnection.localAlias @@ -1546,6 +1553,7 @@ sealed class ChatInfo: SomeChat, NamedChat { override val timedMessagesTTL: Int? get() = null override val displayName get() = invalidChatName override val fullName get() = invalidChatName + override val shortDescr get() = null override val image get() = null override val localAlias get() = "" @@ -1750,6 +1758,7 @@ data class Contact( 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 + override val shortDescr get() = profile.shortDescr override val image get() = profile.image val contactLink: String? = profile.contactLink override val localAlias get() = profile.localAlias @@ -1900,6 +1909,7 @@ data class SecurityCode(val securityCode: String, val verifiedAt: Instant) data class Profile( override val displayName: String, override val fullName: String, + override val shortDescr: String?, override val image: String? = null, override val localAlias : String = "", val contactLink: String? = null, @@ -1910,12 +1920,13 @@ data class Profile( return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, image, localAlias, contactLink, preferences) + fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, image, localAlias, contactLink, preferences) companion object { val sampleData = Profile( displayName = "alice", - fullName = "Alice" + fullName = "Alice", + shortDescr = null, ) } } @@ -1925,6 +1936,7 @@ data class LocalProfile( val profileId: Long, override val displayName: String, override val fullName: String, + override val shortDescr: String?, override val image: String? = null, override val localAlias: String, val contactLink: String? = null, @@ -1932,13 +1944,14 @@ data class LocalProfile( ): NamedChat { val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" } - fun toProfile(): Profile = Profile(displayName, fullName, image, localAlias, contactLink, preferences) + fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, image, localAlias, contactLink, preferences) companion object { val sampleData = LocalProfile( profileId = 1L, displayName = "alice", fullName = "Alice", + shortDescr = null, preferences = ChatPreferences.sampleData, localAlias = "" ) @@ -2004,6 +2017,7 @@ data class GroupInfo ( override val timedMessagesTTL: Int? get() = with(fullGroupPreferences.timedMessages) { if (on) ttl else null } override val displayName get() = localAlias.ifEmpty { groupProfile.displayName } override val fullName get() = groupProfile.fullName + override val shortDescr get() = groupProfile.shortDescr override val image get() = groupProfile.image val isOwner: Boolean @@ -2075,6 +2089,7 @@ data class GroupRef(val groupId: Long, val localDisplayName: String) data class GroupProfile ( override val displayName: String, override val fullName: String, + override val shortDescr: String?, val description: String? = null, override val image: String? = null, override val localAlias: String = "", @@ -2084,7 +2099,8 @@ data class GroupProfile ( companion object { val sampleData = GroupProfile( displayName = "team", - fullName = "My Team" + fullName = "My Team", + shortDescr = null, ) } } @@ -2169,6 +2185,7 @@ data class GroupMember ( return pastMember(name) } override val fullName: String get() = memberProfile.fullName + override val shortDescr: String? get() = memberProfile.shortDescr override val image: String? get() = memberProfile.image val contactLink: String? = memberProfile.contactLink val verified get() = activeConn?.connectionCode != null @@ -2453,6 +2470,7 @@ class NoteFolder( override val timedMessagesTTL: Int? get() = null override val displayName get() = generalGetString(MR.strings.note_folder_local_display_name) override val fullName get() = "" + override val shortDescr get() = null override val image get() = null override val localAlias get() = "" override val localDisplayName: String get() = "" @@ -2491,6 +2509,7 @@ class UserContactRequest ( override val timedMessagesTTL: Int? get() = null override val displayName get() = profile.displayName override val fullName get() = profile.fullName + override val shortDescr get() = profile.shortDescr override val image get() = profile.image override val localAlias get() = "" @@ -2548,6 +2567,7 @@ class PendingContactConnection( } } override val fullName get() = "" + override val shortDescr get() = null override val image get() = null val initiated get() = (pccConnStatus.initiated ?: false) && !viaContactUri 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 136d38e215..7438f85fd8 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 @@ -573,7 +573,7 @@ object ChatController { suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { Log.d(TAG, "startChatWithTemporaryDatabase") - val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) + val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = "", shortDescr = null), ctrl = ctrl) if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) { Log.e(TAG, "Error setting network config, stopping migration") return null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 8317c6cf6c..9d9b4c8f5e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -46,6 +46,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { .padding(top = 20.dp) ) { val displayName = rememberSaveable { mutableStateOf("") } + val shortDescr = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } ColumnWithScrollBar { @@ -66,6 +67,14 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { } } ProfileNameField(displayName, "", { it.trim() == mkValidName(it) }, focusRequester) + +// TODO enable in v6.4.1, limit to 160 characters + +// Text( +// stringResource(MR.strings.short_descr), +// fontSize = 16.sp +// ) +// ProfileNameField(shortDescr, "") } SettingsActionItem( painterResource(MR.images.ic_check), @@ -75,7 +84,7 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { iconColor = MaterialTheme.colors.primary, click = { if (chatModel.localUserCreated.value == true) { - createProfileInProfiles(chatModel, displayName.value, close) + createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close) } else { createProfileInNoProfileSetup(displayName.value, close) } @@ -162,7 +171,7 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { withBGApi { - val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withBGApi + val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi if (!chatModel.connectedToRemote()) { chatModel.localUserCreated.value = true } @@ -173,11 +182,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { } } -fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) { +fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) { withBGApi { val rhId = chatModel.remoteHostId() val user = chatModel.controller.apiCreateActiveUser( - rhId, Profile(displayName.trim(), "", null) + rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null) ) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { @@ -196,7 +205,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) { withBGApi { chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser( - null, Profile(displayName.trim(), "", null) + null, Profile(displayName.trim(), "", null, null) ) ?: return@withBGApi chatModel.localUserCreated.value = true val onboardingStage = chatModel.controller.appPrefs.onboardingStage diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 115d5b3c78..05d9e6564c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -706,15 +706,16 @@ fun ChatInfoLayout( @Composable fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { Column( - Modifier.padding(horizontal = 8.dp), + Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) + val displayName = contact.profile.displayName.trim() val text = buildAnnotatedString { if (contact.verified) { appendInlineContent(id = "shieldIcon") } - append(contact.profile.displayName) + append(displayName) } val inlineContent: Map = mapOf( "shieldIcon" to InlineTextContent( @@ -724,10 +725,11 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { } ) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(contact.profile.displayName)) + val copyNameToClipboard = fun (name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val copyDisplayName = { copyNameToClipboard(displayName) } Text( text, inlineContent = inlineContent, @@ -735,18 +737,39 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) { textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) + ) + ChatInfoDescription(cInfo, displayName, copyNameToClipboard) + } +} + +@Composable +fun ChatInfoDescription(c: NamedChat, displayName: String, copyNameToClipboard: (String) -> Unit) { + val fullName = c.fullName.trim() + if (fullName != "" && fullName != displayName && fullName != c.displayName.trim()) { + val copyFullName = { copyNameToClipboard(fullName) } + Text( + fullName, style = MaterialTheme.typography.h2, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF).combinedClickable(onClick = copyFullName, onLongClick = copyFullName).onRightClick(copyFullName) + ) + } + val descr = c.shortDescr?.trim() + if (descr != null && descr != "") { + val copyDescr = { copyNameToClipboard(descr) } + Text( + descr, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + lineHeight = 21.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF).combinedClickable(onClick = copyDescr, onLongClick = copyDescr).onRightClick(copyDescr) ) - if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName) { - Text( - cInfo.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 4, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) - ) - } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 74069f25f1..5a749766c4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -724,33 +724,27 @@ fun ChatTTLOption(chatItemTTL: State, setChatItemTTL: (ChatItemTTL @Composable private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) { Column( - Modifier.padding(horizontal = 8.dp), + Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { ChatInfoImage(cInfo, size = 192.dp, iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(groupInfo.groupProfile.displayName)) + val copyNameToClipboard = fun(name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val displayName = groupInfo.groupProfile.displayName.trim() + val copyDisplayName = { copyNameToClipboard(displayName) } Text( - groupInfo.groupProfile.displayName, style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), + displayName, + style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal), color = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, - maxLines = 4, + maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) ) - if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != groupInfo.groupProfile.displayName) { - Text( - cInfo.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 8, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) - ) - } + ChatInfoDescription(cInfo, displayName, copyNameToClipboard) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index ac06b88222..d3e7f60f62 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -568,11 +568,12 @@ fun GroupMemberInfoHeader(member: GroupMember) { horizontalAlignment = Alignment.CenterHorizontally ) { MemberProfileImage(size = 192.dp, member, color = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight) + val displayName = member.displayName.trim() // alias if set val text = buildAnnotatedString { if (member.verified) { appendInlineContent(id = "shieldIcon") } - append(member.displayName) + append(displayName) } val inlineContent: Map = mapOf( "shieldIcon" to InlineTextContent( @@ -582,10 +583,11 @@ fun GroupMemberInfoHeader(member: GroupMember) { } ) val clipboard = LocalClipboardManager.current - val copyNameToClipboard = { - clipboard.setText(AnnotatedString(member.displayName)) + val copyNameToClipboard = fun(name: String) { + clipboard.setText(AnnotatedString(name)) showToast(generalGetString(MR.strings.copied)) } + val copyDisplayName = { copyNameToClipboard(displayName) } Text( text, inlineContent = inlineContent, @@ -593,18 +595,10 @@ fun GroupMemberInfoHeader(member: GroupMember) { textAlign = TextAlign.Center, maxLines = 3, overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) + modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName) ) - if (member.fullName != "" && member.fullName != member.displayName) { - Text( - member.fullName, style = MaterialTheme.typography.h2, - color = MaterialTheme.colors.onBackground, - textAlign = TextAlign.Center, - maxLines = 4, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.combinedClickable(onClick = copyNameToClipboard, onLongClick = copyNameToClipboard).onRightClick(copyNameToClipboard) - ) - } + // passing actual display name here, as alias is used above + ChatInfoDescription(member, member.memberProfile.displayName.trim(), copyNameToClipboard) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index fb24c028b2..05b4be1c47 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -56,14 +56,16 @@ fun GroupProfileLayout( val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) } val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) } + val shortDescr = rememberSaveable { mutableStateOf(groupProfile.shortDescr ?: "") } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) } val scope = rememberCoroutineScope() val scrollState = rememberScrollState() val focusRequester = remember { FocusRequester() } val dataUnchanged = - displayName.value == groupProfile.displayName && - fullName.value == groupProfile.fullName && + displayName.value.trim() == groupProfile.displayName && + fullName.value.trim() == groupProfile.fullName && + shortDescr.value.trim() == (groupProfile.shortDescr ?: "") && groupProfile.image == profileImage.value val closeWithAlert = { if (dataUnchanged || !canUpdateProfile(displayName.value, groupProfile)) { @@ -73,7 +75,8 @@ fun GroupProfileLayout( saveProfile( groupProfile.copy( displayName = displayName.value.trim(), - fullName = fullName.value, + fullName = fullName.value.trim(), + shortDescr = shortDescr.value.trim().ifEmpty { null }, image = profileImage.value ) ) @@ -130,7 +133,7 @@ fun GroupProfileLayout( } } ProfileNameField(displayName, "", { isValidNewProfileName(it, groupProfile) }, focusRequester) - if (groupProfile.fullName.isNotEmpty() && groupProfile.fullName != groupProfile.displayName) { + if (groupProfile.fullName.trim().isNotEmpty() && groupProfile.fullName.trim() != groupProfile.displayName.trim()) { Spacer(Modifier.height(DEFAULT_PADDING)) Text( stringResource(MR.strings.group_full_name_field), @@ -139,6 +142,17 @@ fun GroupProfileLayout( ) ProfileNameField(fullName) } + +// TODO enable in v6.4.1, limit to 160 characters + +// Spacer(Modifier.height(DEFAULT_PADDING)) +// Text( +// stringResource(MR.strings.group_short_descr_field), +// fontSize = 16.sp, +// modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) +// ) +// ProfileNameField(shortDescr) + Spacer(Modifier.height(DEFAULT_PADDING)) val enabled = !dataUnchanged && canUpdateProfile(displayName.value, groupProfile) if (enabled) { @@ -148,7 +162,8 @@ fun GroupProfileLayout( saveProfile( groupProfile.copy( displayName = displayName.value.trim(), - fullName = fullName.value, + fullName = fullName.value.trim(), + shortDescr = shortDescr.value.trim().ifEmpty { null }, image = profileImage.value ) ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index 2bcbbe29e0..39bb9545e1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -194,7 +194,7 @@ fun CIGroupInvitationViewLongNamePreview() { CIGroupInvitationView( ci = ChatItem.getGroupInvitationSample(), groupInvitation = CIGroupInvitation.getSample( - groupProfile = GroupProfile("group_with_a_really_really_really_long_name", "Group With A Really Really Really Long Name"), + groupProfile = GroupProfile("group_with_a_really_really_really_long_name", "Group With A Really Really Really Long Name", shortDescr = null), status = CIGroupInvitationStatus.Accepted ), memberRole = GroupMemberRole.Admin, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 65e1864935..8021a605db 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -82,7 +82,7 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: ( } var profile: Profile? = null if (!displayName.isNullOrEmpty()) { - profile = Profile(displayName = displayName, fullName = "") + profile = Profile(displayName = displayName, fullName = "", shortDescr = null) } val createdUser = m.controller.apiCreateActiveUser(null, profile, pastTimestamp = true) m.currentUser.value = createdUser diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index fd0d41478a..d4161ee3e5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -140,6 +140,7 @@ fun AddGroupLayout( createGroup(incognito.value, GroupProfile( displayName = displayName.value.trim(), fullName = "", + shortDescr = null, image = profileImage.value, groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON)) )) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 90122bd29d..2149649755 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -40,9 +40,10 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { UserProfileLayout( profile = profile, close, - saveProfile = { displayName, fullName, image -> + saveProfile = { displayName, fullName, shortDescr, image -> withBGApi { - val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image)) + val updatedProfile = profile.copy(displayName = displayName.trim(), fullName = fullName.trim(), shortDescr = shortDescr.trim().ifEmpty { null }, image = image) + val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, updatedProfile) if (updated != null) { val (newProfile, _) = updated chatModel.updateCurrentUser(user.remoteHostId, newProfile) @@ -59,11 +60,12 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { fun UserProfileLayout( profile: Profile, close: () -> Unit, - saveProfile: (String, String, String?) -> Unit, + saveProfile: (String, String, String, String?) -> Unit, ) { val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val displayName = remember { mutableStateOf(profile.displayName) } val fullName = remember { mutableStateOf(profile.fullName) } + val shortDescr = remember { mutableStateOf(profile.shortDescr ?: "") } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(profile.image) } val scope = rememberCoroutineScope() @@ -85,14 +87,15 @@ fun UserProfileLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val dataUnchanged = - displayName.value == profile.displayName && - fullName.value == profile.fullName && + displayName.value.trim() == profile.displayName && + fullName.value.trim() == profile.fullName && + shortDescr.value.trim() == (profile.shortDescr ?: "") && profile.image == profileImage.value val closeWithAlert = { if (dataUnchanged || !canSaveProfile(displayName.value, profile)) { close() } else { - showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, profileImage.value) }, close) + showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }, close) } } ModalView(close = closeWithAlert) { @@ -144,9 +147,20 @@ fun UserProfileLayout( ) ProfileNameField(fullName) } + +// TODO enable in v6.4.1, limit to 160 characters + +// Spacer(Modifier.height(DEFAULT_PADDING)) +// Text( +// stringResource(MR.strings.short_descr__field), +// fontSize = 16.sp, +// modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) +// ) +// ProfileNameField(shortDescr) + Spacer(Modifier.height(DEFAULT_PADDING)) val enabled = !dataUnchanged && canSaveProfile(displayName.value, profile) - val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, profileImage.value) } + val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) } val saveColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary Text( stringResource(MR.strings.save_and_notify_contacts), @@ -209,7 +223,7 @@ private fun isValidNewProfileName(displayName: String, profile: Profile): Boolea displayName == profile.displayName || isValidDisplayName(displayName.trim()) private fun showFullName(profile: Profile): Boolean = - profile.fullName.isNotEmpty() && profile.fullName != profile.displayName + profile.fullName.trim().isNotEmpty() && profile.fullName.trim() != profile.displayName.trim() private fun canSaveProfile(displayName: String, profile: Profile): Boolean = displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile) @@ -225,7 +239,7 @@ fun PreviewUserProfileLayoutEditOff() { UserProfileLayout( profile = Profile.sampleData, close = {}, - saveProfile = { _, _, _ -> } + saveProfile = { _, _, _, _ -> } ) } } @@ -241,7 +255,7 @@ fun PreviewUserProfileLayoutEditOn() { UserProfileLayout( profile = Profile.sampleData, close = {}, - saveProfile = { _, _, _ -> } + saveProfile = { _, _, _, _ -> } ) } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 3b9d57090a..cfe9dd3722 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1108,6 +1108,7 @@ Profile name: Full name: + Bio: Your current profile Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. Edit image @@ -1137,6 +1138,7 @@ The profile is only shared with your contacts. Display name cannot contain whitespace. Enter your name: + Your bio: Create Create profile Create @@ -1914,6 +1916,7 @@ Fully decentralized – visible only to members. Enter group name: Group full name: + Short description: Your chat profile will be sent to group members Your chat profile will be sent to chat members Create group diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 9db9e4a019..93aebbf47b 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -234,14 +234,19 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName getGroupReg st groupId >>= \case Just gr -> action gr Nothing -> logError $ "Error: " <> err <> ", group: " <> localDisplayName <> ", can't find group registration ID " <> tshow groupId - groupInfoText GroupProfile {displayName = n, fullName = fn, description = d} = - n <> (if n == fn || T.null fn then "" else " (" <> fn <> ")") <> maybe "" ("\nWelcome message:\n" <>) d + groupInfoText p@GroupProfile {description = d} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d + groupNameDescr GroupProfile {displayName = n, fullName = fn, shortDescr = sd_} = + n <> maybe "" (\d' -> " (" <> d' <> ")") descr + where + descr + | n == fn || T.null fn = if sd_ == Just "" then Nothing else sd_ + | otherwise = Just fn userGroupReference gr GroupInfo {groupProfile = GroupProfile {displayName}} = userGroupReference' gr displayName userGroupReference' GroupReg {userGroupRegId} displayName = groupReference' userGroupRegId displayName groupReference GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = groupReference' groupId displayName groupReference' groupId displayName = "ID " <> tshow groupId <> " (" <> displayName <> ")" - groupAlreadyListed GroupInfo {groupProfile = GroupProfile {displayName, fullName}} = - "The group " <> displayName <> " (" <> fullName <> ") is already listed in the directory, please choose another name." + groupAlreadyListed GroupInfo {groupProfile = p} = + "The group " <> groupNameDescr p <> " is already listed in the directory, please choose another name." getGroups :: Text -> IO (Maybe [(GroupInfo, GroupSummary)]) getGroups = getGroups_ . Just @@ -295,7 +300,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName \Content and privacy policy: https://simplex.chat/docs/directory.html" deGroupInvitation :: Contact -> GroupInfo -> GroupMemberRole -> GroupMemberRole -> IO () - deGroupInvitation ct g@GroupInfo {groupProfile = GroupProfile {displayName, fullName}} fromMemberRole memberRole = do + deGroupInvitation ct g@GroupInfo {groupProfile = p@GroupProfile {displayName}} fromMemberRole memberRole = do logInfo $ "invited to group " <> viewGroupName g <> " by " <> viewContactName ct case badRolesMsg $ groupRolesStatus fromMemberRole memberRole of Just msg -> sendMessage cc ct msg @@ -308,7 +313,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where askConfirmation = do ugrId <- addGroupReg st ct g GRSPendingConfirmation - sendMessage cc ct $ "The group " <> displayName <> " (" <> fullName <> ") is already submitted to the directory.\nTo confirm the registration, please send:" + sendMessage cc ct $ "The group " <> groupNameDescr p <> " is already submitted to the directory.\nTo confirm the registration, please send:" sendMessage cc ct $ "/confirm " <> tshow ugrId <> ":" <> viewName displayName badRolesMsg :: GroupRolesStatus -> Maybe Text @@ -400,9 +405,9 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName GroupInfo {groupId, groupProfile = p} = fromGroup GroupInfo {groupProfile = p'} = toGroup sameProfile - GroupProfile {displayName = n, fullName = fn, image = i, description = d} - GroupProfile {displayName = n', fullName = fn', image = i', description = d'} = - n == n' && fn == fn' && i == i' && d == d' + GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d} + GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d'} = + n == n' && fn == fn' && i == i' && sd == sd' && d == d' groupLinkAdded gr byMember = do getDuplicateGroup toGroup >>= \case Nothing -> notifyOwner gr "Error: getDuplicateGroup. Please notify the developers." diff --git a/simplex-chat.cabal b/simplex-chat.cabal index f48f46a1f7..75e819a727 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -110,6 +110,7 @@ library Simplex.Chat.Store.Postgres.Migrations.M20250526_short_links Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepared_connection + Simplex.Chat.Store.Postgres.Migrations.M20250709_profile_short_descr else exposed-modules: Simplex.Chat.Archive @@ -245,6 +246,7 @@ library Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_connection + Simplex.Chat.Store.SQLite.Migrations.M20250709_profile_short_descr other-modules: Paths_simplex_chat hs-source-dirs: diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 0061b38022..de38af692d 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -531,7 +531,7 @@ data ChatCommand | CancelFile FileTransferId | FileStatus FileTransferId | ShowProfile -- UserId (not used in UI) - | UpdateProfile ContactName Text -- UserId (not used in UI) + | UpdateProfile ContactName (Maybe Text) -- UserId (not used in UI) | UpdateProfileImage (Maybe ImageData) -- UserId (not used in UI) | ShowProfileImage | SetUserFeature AChatFeature FeatureAllowed -- UserId (not used in UI) diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index 08c0a30c6d..e70aeb8775 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -107,7 +107,7 @@ createActiveUser cc = do where loop = do displayName <- T.pack <$> getWithPrompt "display name" - let profile = Just Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} + let profile = Just Profile {displayName, fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = Nothing} execChatCommand' (CreateActiveUser NewUser {profile, pastTimestamp = False}) 0 `runReaderT` cc >>= \case Right (CRActiveUser user) -> pure user r -> printResponseEvent (Nothing, Nothing) (config cc) r >> loop diff --git a/src/Simplex/Chat/Help.hs b/src/Simplex/Chat/Help.hs index adb77b9557..59e7a2c941 100644 --- a/src/Simplex/Chat/Help.hs +++ b/src/Simplex/Chat/Help.hs @@ -122,7 +122,7 @@ groupsHelpInfo = map styleMarkdown [ green "Group commands:", - indent <> highlight "/group [] " <> " - create group", + indent <> highlight "/group [] " <> " - create group", indent <> highlight "/add [] " <> " - add contact to group, roles: " <> highlight "owner" <> ", " <> highlight "admin" <> " (default), " <> highlight "member", indent <> highlight "/join " <> " - accept group invitation", indent <> highlight "/members " <> " - list group members", @@ -131,8 +131,10 @@ groupsHelpInfo = indent <> highlight "/clear # " <> " - clear all messages in the group locally", indent <> highlight "/delete # " <> " - delete group and all messages", indent <> highlight "/gp " <> " - view group profile", - indent <> highlight "/gp [] " <> " - update group profile names", - indent <> highlight "/group_descr [] " <> " - update/remove group description", + indent <> highlight "/gp [] " <> " - update group profile names", + indent <> highlight "/set welcome " <> " - set group welcome message", + indent <> highlight "/delete welcome " <> " - delete group welcome message", + indent <> highlight "/show welcome " <> " - show group welcome message", indent <> highlight "/groups " <> " - list groups", indent <> highlight "# " <> " - send message to group", "", diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 42499bd6a6..3065b48893 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2507,8 +2507,8 @@ processChatCommand vr nm = \case APIUpdateGroupProfile groupId p' -> withUser $ \user -> do g <- withFastStore $ \db -> getGroup db vr user groupId runUpdateGroupProfile user g p' - UpdateGroupNames gName GroupProfile {displayName, fullName} -> - updateGroupProfileByName gName $ \p -> p {displayName, fullName} + UpdateGroupNames gName p'@GroupProfile {displayName, fullName, shortDescr} -> + updateGroupProfileByName gName $ \p -> p {displayName, fullName, shortDescr} ShowGroupProfile gName -> withUser $ \user -> CRGroupProfile user <$> withFastStore (\db -> getGroupInfoByName db vr user gName) UpdateGroupDescription gName description -> @@ -2729,8 +2729,8 @@ processChatCommand vr nm = \case fileStatus <- withFastStore $ \db -> getFileTransferProgress db user fileId pure $ CRFileTransferStatus user fileStatus ShowProfile -> withUser $ \user@User {profile} -> pure $ CRUserProfile user (fromLocalProfile profile) - UpdateProfile displayName fullName -> withUser $ \user@User {profile} -> do - let p = (fromLocalProfile profile :: Profile) {displayName = displayName, fullName = fullName} + UpdateProfile displayName shortDescr -> withUser $ \user@User {profile} -> do + let p = (fromLocalProfile profile :: Profile) {displayName, shortDescr, fullName = ""} updateProfile user p UpdateProfileImage image -> withUser $ \user@User {profile} -> do let p = (fromLocalProfile profile :: Profile) {image} @@ -3151,9 +3151,9 @@ processChatCommand vr nm = \case let (newMs, oldMs) = partition (\m -> maxVersion (memberChatVRange m) >= businessChatPrefsVersion) ms -- this is a fallback to send the members with the old version correct profile of the business when preferences change unless (null oldMs) $ do - GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} <- + GroupMember {memberProfile = LocalProfile {displayName, fullName, shortDescr, image}} <- withStore $ \db -> getGroupMemberByMemberId db vr user g businessId - let p'' = p' {displayName, fullName, image} :: GroupProfile + let p'' = p' {displayName, fullName, shortDescr, image} :: GroupProfile recipients = filter memberCurrentOrPending oldMs void $ sendGroupMessage user g' Nothing recipients (XGrpInfo p'') let ps' = fromMaybe defaultBusinessGroupPrefs $ groupPreferences p' @@ -3162,6 +3162,7 @@ processChatCommand vr nm = \case Nothing -> do setGroupLinkData' let recipients = filter memberCurrentOrPending ms + liftIO $ putStrLn $ "about to sendGroupMessage to " <> show (length recipients) sendGroupMessage user g' Nothing recipients (XGrpInfo p') where setGroupLinkData' :: CM () @@ -3553,14 +3554,14 @@ processChatCommand vr nm = \case business = maybe False businessAddress settings contactData | large = ContactShortLinkData p msg business - | otherwise = ContactShortLinkData p {fullName = "", image = Nothing, contactLink = Nothing} Nothing business + | otherwise = ContactShortLinkData p {fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing} Nothing business pure $ encodeShortLinkData large contactData groupShortLinkData :: GroupProfile -> CM UserLinkData groupShortLinkData gp = do large <- chatReadVar useLargeLinkData let gp' | large = gp - | otherwise = gp {fullName = "", description = Nothing, image = Nothing, memberAdmission = Nothing} + | otherwise = gp {fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, memberAdmission = Nothing} pure $ encodeShortLinkData large $ GroupShortLinkData gp' encodeShortLinkData :: J.ToJSON a => Bool -> a -> UserLinkData encodeShortLinkData large d = @@ -4609,7 +4610,7 @@ chatCommandP = "/set profile image " *> (UpdateProfileImage . Just . ImageData <$> imageP), "/delete profile image" $> UpdateProfileImage Nothing, "/show profile image" $> ShowProfileImage, - ("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> profileNames), + ("/profile " <|> "/p ") *> (uncurry UpdateProfile <$> profileNameDescr), ("/profile" <|> "/p") $> ShowProfile, "/set voice #" *> (SetGroupFeatureRole (AGFR SGFVoice) <$> displayNameP <*> _strP <*> optional memberRole), "/set voice @" *> (SetContactFeature (ACF SCFVoice) <$> displayNameP <*> optional (A.space *> strP)), @@ -4721,24 +4722,26 @@ chatCommandP = clearOverrides <- (" clear_overrides=" *> onOffP) <|> pure False pure UserMsgReceiptSettings {enable, clearOverrides} onOffP = ("on" $> True) <|> ("off" $> False) - profileNames = (,) <$> displayNameP <*> fullNameP + profileNameDescr = (,) <$> displayNameP <*> shortDescrP newUserP = do - (cName, fullName) <- profileNames - let profile = Just Profile {displayName = cName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} + (cName, shortDescr) <- profileNameDescr + let profile = Just Profile {displayName = cName, fullName = "", shortDescr, image = Nothing, contactLink = Nothing, preferences = Nothing} pure NewUser {profile, pastTimestamp = False} jsonP :: J.FromJSON a => Parser a jsonP = J.eitherDecodeStrict' <$?> A.takeByteString groupProfile = do - (gName, fullName) <- profileNames + (gName, shortDescr) <- profileNameDescr let groupPreferences = Just (emptyGroupPrefs :: GroupPreferences) { directMessages = Just DirectMessagesGroupPreference {enable = FEOn, role = Nothing}, history = Just HistoryGroupPreference {enable = FEOn} } - pure GroupProfile {displayName = gName, fullName, description = Nothing, image = Nothing, groupPreferences, memberAdmission = Nothing} + pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, groupPreferences, memberAdmission = Nothing} memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing) - fullNameP = A.space *> textP <|> pure "" + shortDescrP = do + descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure "" + pure $ if T.null descr then Nothing else Just $ T.take 160 descr textP = safeDecodeUtf8 <$> A.takeByteString pwdP = jsonP <|> (UserPwd . safeDecodeUtf8 <$> A.takeTill (== ' ')) verifyCodeP = safeDecodeUtf8 <$> A.takeWhile (\c -> isDigit c || c == ' ') diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 81c4f080f4..625f3f0685 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1041,8 +1041,8 @@ acceptBusinessJoinRequestAsync pure (gInfo, clientMember) businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile -businessGroupProfile Profile {displayName, fullName, image} groupPreferences = - GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing} +businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences = + GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing} introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM () introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do @@ -2504,6 +2504,7 @@ simplexTeamContactProfile = Profile { displayName = "SimpleX Chat team", fullName = "", + shortDescr = Just "Help and support for SimpleX network users", image = Just (ImageData "data:image/jpg;base64,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"), contactLink = Just $ CLFull adminContactReq, preferences = Nothing @@ -2514,6 +2515,7 @@ simplexStatusContactProfile = Profile { displayName = "SimpleX-Status", fullName = "", + shortDescr = Just "SimpleX bot for server status and release updates", image = Just (ImageData "data:image/jpg;base64,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"), contactLink = Just (either error CLFull $ strDecode "simplex:/contact/#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FShQuD-rPokbDvkyotKx5NwM8P3oUXHxA%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEA6fSx1k9zrOmF0BJpCaTarZvnZpMTAVQhd3RkDQ35KT0%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion"), preferences = Nothing diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 31bf0b743f..5a73430191 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2232,9 +2232,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = createInternalChatItem user (CDDirectRcv c') ciContent Nothing where visibleProfileUpdated = - n' /= n || fn' /= fn || i' /= i || cl' /= cl - Profile {displayName = n, fullName = fn, image = i, contactLink = cl} = p - Profile {displayName = n', fullName = fn', image = i', contactLink = cl'} = p' + n' /= n || fn' /= fn || sd /= sd' || i' /= i || cl' /= cl + Profile {displayName = n, fullName = fn, shortDescr = sd, image = i, contactLink = cl} = p + Profile {displayName = n', fullName = fn', shortDescr = sd', image = i', contactLink = cl'} = p' xInfoMember :: GroupInfo -> GroupMember -> Profile -> UTCTime -> CM () xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs) diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index 09d1e9a281..e0bff73e0c 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -318,7 +318,7 @@ e2eInfoPQText = ciGroupInvitationToText :: CIGroupInvitation -> GroupMemberRole -> Text ciGroupInvitationToText CIGroupInvitation {groupProfile = GroupProfile {displayName, fullName}} role = - "invitation to join group " <> displayName <> optionalFullName displayName fullName <> " as " <> (decodeLatin1 . strEncode $ role) + "invitation to join group " <> displayName <> optionalFullName displayName fullName Nothing <> " as " <> (decodeLatin1 . strEncode $ role) rcvDirectEventToText :: RcvDirectEvent -> Text rcvDirectEventToText = \case @@ -402,7 +402,7 @@ sndConnEventToText = \case maybe "" (\GroupMemberRef {profile = Profile {displayName}} -> " for " <> displayName) member_ profileToText :: Profile -> Text -profileToText Profile {displayName, fullName} = displayName <> optionalFullName displayName fullName +profileToText Profile {displayName, fullName} = displayName <> optionalFullName displayName fullName Nothing msgIntegrityError :: MsgErrorType -> Text msgIntegrityError = \case diff --git a/src/Simplex/Chat/ProfileGenerator.hs b/src/Simplex/Chat/ProfileGenerator.hs index 5b6c9222cc..176320f282 100644 --- a/src/Simplex/Chat/ProfileGenerator.hs +++ b/src/Simplex/Chat/ProfileGenerator.hs @@ -10,7 +10,7 @@ generateRandomProfile :: IO Profile generateRandomProfile = do adjective <- pick adjectives noun <- pickNoun adjective 2 - pure $ Profile {displayName = adjective <> noun, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} + pure $ Profile {displayName = adjective <> noun, fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = Nothing} where pick :: [a] -> IO a pick xs = (xs !!) <$> randomRIO (0, length xs - 1) diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 3334653a2c..4aacc7e677 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -111,7 +111,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do db [sql| SELECT - c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, + c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl FROM contacts c @@ -120,8 +120,8 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do |] (userId, contactId) toContact' :: Int64 -> Connection -> [ChatTagId] -> ContactRow' -> Contact - toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) = - let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} + toContact' contactId conn chatTags ((profileId, localDisplayName, viaGroup, displayName, fullName, shortDescr, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) = + let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences, localAlias} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn activeConn = Just conn @@ -137,7 +137,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -147,12 +147,12 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts FROM group_members m diff --git a/src/Simplex/Chat/Store/ContactRequest.hs b/src/Simplex/Chat/Store/ContactRequest.hs index ee68de5af2..4836e30116 100644 --- a/src/Simplex/Chat/Store/ContactRequest.hs +++ b/src/Simplex/Chat/Store/ContactRequest.hs @@ -72,7 +72,7 @@ createOrUpdateContactRequest isSimplexTeam invId cReqChatVRange@(VersionRange minV maxV) - profile@Profile {displayName, fullName, image, contactLink, preferences} + profile@Profile {displayName, fullName, shortDescr, image, contactLink, preferences} xContactId_ welcomeMsgId_ requestMsg_ @@ -109,7 +109,7 @@ createOrUpdateContactRequest [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection @@ -143,7 +143,7 @@ createOrUpdateContactRequest SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -161,8 +161,8 @@ createOrUpdateContactRequest liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) profileId <- liftIO $ insertedRowId db liftIO $ DB.execute @@ -229,6 +229,7 @@ createOrUpdateContactRequest UPDATE contact_profiles SET display_name = ?, full_name = ?, + short_descr = ?, image = ?, contact_link = ?, updated_at = ? @@ -239,7 +240,7 @@ createOrUpdateContactRequest AND contact_request_id = ? ) |] - (displayName, fullName, image, contactLink, currentTs, userId, contactRequestId) + (displayName, fullName, shortDescr, image, contactLink, currentTs, userId, contactRequestId) updateRequest currentTs = if displayName == oldDisplayName then diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index f142da4553..e6db6bf90e 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -262,7 +262,7 @@ getContactByConnReqHash db vr user@User {userId} cReqHash1 cReqHash2 = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection @@ -692,15 +692,15 @@ updateContactProfile_ db userId profileId profile = do updateContactProfile_' db userId profileId profile currentTs updateContactProfile_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO () -updateContactProfile_' db userId profileId Profile {displayName, fullName, image, contactLink, preferences} updatedAt = do +updateContactProfile_' db userId profileId Profile {displayName, fullName, shortDescr, image, contactLink, preferences} updatedAt = do DB.execute db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (displayName, fullName, image, contactLink, preferences, updatedAt, userId, profileId) + (displayName, fullName, shortDescr, image, contactLink, preferences, updatedAt, userId, profileId) -- update only member profile fields (when member doesn't have associated contact - we can reset contactLink and prefs) updateMemberContactProfileReset_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO () @@ -709,15 +709,15 @@ updateMemberContactProfileReset_ db userId profileId profile = do updateMemberContactProfileReset_' db userId profileId profile currentTs updateMemberContactProfileReset_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO () -updateMemberContactProfileReset_' db userId profileId Profile {displayName, fullName, image} updatedAt = do +updateMemberContactProfileReset_' db userId profileId Profile {displayName, fullName, shortDescr, image} updatedAt = do DB.execute db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (displayName, fullName, image, updatedAt, userId, profileId) + (displayName, fullName, shortDescr, image, updatedAt, userId, profileId) -- update only member profile fields (when member has associated contact - we keep contactLink and prefs) updateMemberContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO () @@ -726,15 +726,15 @@ updateMemberContactProfile_ db userId profileId profile = do updateMemberContactProfile_' db userId profileId profile currentTs updateMemberContactProfile_' :: DB.Connection -> UserId -> ProfileId -> Profile -> UTCTime -> IO () -updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, image} updatedAt = do +updateMemberContactProfile_' db userId profileId Profile {displayName, fullName, shortDescr, image} updatedAt = do DB.execute db [sql| UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? |] - (displayName, fullName, image, updatedAt, userId, profileId) + (displayName, fullName, shortDescr, image, updatedAt, userId, profileId) updateContactLDN_ :: DB.Connection -> User -> Int64 -> ContactName -> ContactName -> UTCTime -> IO () updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt = do @@ -785,7 +785,7 @@ contactRequestQuery = SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -903,7 +903,7 @@ getContact_ db vr user@User {userId} contactId deleted = do [sql| SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 51cbcd6b61..c2c6d9798d 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -157,6 +157,7 @@ import Crypto.Random (ChaChaDRG) import qualified Data.Aeson.TH as J import Data.Bifunctor (second) import Data.Bitraversable (bitraverse) +import Data.Char (toLower) import Data.Either (rights) import Data.Int (Int64) import Data.List (partition, sortOn) @@ -192,11 +193,11 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..)) import Database.SQLite.Simple.QQ (sql) #endif -type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime) +type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId) :. (Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime) toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember -toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs)) = - Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs)) +toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId) :. (Just profileId, Just displayName, Just fullName, shortDescr, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs)) = + Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. (profileId, displayName, fullName, shortDescr, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs)) toMaybeGroupMember _ _ = Nothing createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO GroupLink @@ -317,7 +318,7 @@ setGroupLinkShortLink db gLnk@GroupLink {userContactLinkId, connLinkContact = CC -- | creates completely new group with a single member - the current user createNewGroup :: DB.Connection -> VersionRangeChat -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do - let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile + let GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} = groupProfile fullGroupPreferences = mergeGroupPreferences groupPreferences currentTs <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile @@ -325,8 +326,8 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc groupId <- liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -388,7 +389,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ DB.query db "SELECT group_id FROM groups WHERE inv_queue_info = ? AND user_id = ? LIMIT 1" (connRequest, userId) createGroupInvitation_ :: ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation_ = do - let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile + let GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} = groupProfile fullGroupPreferences = mergeGroupPreferences groupPreferences ExceptT $ withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do @@ -396,8 +397,8 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ groupId <- liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -753,13 +754,13 @@ createGroupViaLink' createGroup_ :: DB.Connection -> UserId -> GroupProfile -> Maybe (CreatedLinkContact, Maybe SharedMsgId) -> Maybe BusinessChatInfo -> UTCTime -> ExceptT StoreError IO (GroupId, Text) createGroup_ db userId groupProfile prepared business currentTs = ExceptT $ do - let GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} = groupProfile + let GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} = groupProfile withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do liftIO $ do DB.execute db - "INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, description, image, userId, groupPreferences, memberAdmission, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -936,14 +937,14 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do db [sql| SELECT - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, - mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts FROM groups g @@ -951,15 +952,16 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do JOIN group_members mu USING (group_id) JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) WHERE g.user_id = ? AND mu.contact_id = ? - AND (LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' + AND (LOWER(gp.display_name) LIKE '%' || ? || '%' + OR LOWER(gp.full_name) LIKE '%' || ? || '%' + OR LOWER(gp.short_descr) LIKE '%' || ? || '%' + OR LOWER(gp.description) LIKE '%' || ? || '%' ) |] - (userId, userContactId, search, search, search) + (userId, userContactId, search, search, search, search) mapM (addGroupChatTags db) g_ where - search = fromMaybe "" search_ + search = maybe "" (map toLower) search_ getUserGroupsWithSummary :: DB.Connection -> VersionRangeChat -> User -> Maybe ContactId -> Maybe String -> IO [(GroupInfo, GroupSummary)] getUserGroupsWithSummary db vr user _contactId_ search_ = @@ -1008,7 +1010,7 @@ groupMemberQuery = [sql| SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -1239,7 +1241,7 @@ createJoiningMember User {userId, userContactId} GroupInfo {groupId, membership} cReqChatVRange - Profile {displayName, fullName, image, contactLink, preferences} + Profile {displayName, fullName, shortDescr, image, contactLink, preferences} cReqXContactId_ welcomeMsgId_ memberRole @@ -1249,8 +1251,8 @@ createJoiningMember liftIO $ DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, image, contactLink, userId, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, contactLink, userId, preferences, currentTs, currentTs) profileId <- liftIO $ insertedRowId db createWithRandomId gVar $ \memId -> do insertMember_ ldn profileId (MemberId memId) currentTs @@ -1299,7 +1301,7 @@ createBusinessRequestGroup gVar user@User {userId, userContactId} cReqChatVRange - Profile {displayName, fullName, image} + Profile {displayName, fullName, shortDescr, image} profileId -- contact request profile id, to be used for member profile ldn -- contact request local display name, to be used for group local display name groupPreferences = do @@ -1315,8 +1317,8 @@ createBusinessRequestGroup liftIO $ DB.execute db - "INSERT INTO group_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, groupPreferences, currentTs, currentTs) + "INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, userId, groupPreferences, currentTs, currentTs) groupProfileId <- liftIO $ insertedRowId db liftIO $ DB.execute @@ -1503,12 +1505,12 @@ createNewGroupMember db user gInfo invitingMember memInfo@MemberInfo {profile} m liftIO $ createNewMember_ db user gInfo newMember currentTs createNewMemberProfile_ :: DB.Connection -> User -> Profile -> UTCTime -> ExceptT StoreError IO (Text, ProfileId) -createNewMemberProfile_ db User {userId} Profile {displayName, fullName, image, contactLink, preferences} createdAt = +createNewMemberProfile_ db User {userId} Profile {displayName, fullName, shortDescr, image, contactLink, preferences} createdAt = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" - (displayName, fullName, image, contactLink, userId, preferences, createdAt, createdAt) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, contactLink, userId, preferences, createdAt, createdAt) profileId <- insertedRowId db pure $ Right (ldn, profileId) @@ -1818,7 +1820,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -1828,12 +1830,12 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, -- via GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -1881,7 +1883,7 @@ getViaGroupContact db vr user@User {userId} GroupMember {groupMemberId} = do maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) contactId_ updateGroupProfile :: DB.Connection -> User -> GroupInfo -> GroupProfile -> ExceptT StoreError IO GroupInfo -updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences, memberAdmission} +updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} | displayName == newName = liftIO $ do currentTs <- getCurrentTime updateGroupProfile_ currentTs @@ -1899,14 +1901,14 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, db [sql| UPDATE group_profiles - SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups WHERE user_id = ? AND group_id = ? ) |] - (newName, fullName, description, image, groupPreferences, memberAdmission, currentTs, userId, groupId) + (newName, fullName, shortDescr, description, image, groupPreferences, memberAdmission, currentTs, userId, groupId) updateGroup_ ldn currentTs = do DB.execute db @@ -1932,10 +1934,10 @@ updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}, fullGroupPreferences = mergeGroupPreferences $ Just ps} updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo -updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do +updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, shortDescr = sd, image = img} = do p <- getGroupProfile -- to avoid any race conditions with UI let g' = g {groupProfile = p} :: GroupInfo - p' = p {displayName = n, fullName = fn, image = img} :: GroupProfile + p' = p {displayName = n, fullName = fn, shortDescr = sd, image = img} :: GroupProfile updateGroupProfile db user g' p' where getGroupProfile = @@ -1944,14 +1946,14 @@ updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName DB.query db [sql| - SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences, gp.member_admission + SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.preferences, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? |] (Only groupId) - toGroupProfile (displayName, fullName, description, image, groupPreferences, memberAdmission) = - GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} + toGroupProfile (displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission) = + GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo getGroupInfo db vr User {userId, userContactId} groupId = ExceptT $ do @@ -2052,11 +2054,8 @@ getActiveMembersByName db vr user@User {userId} groupMemberName = do ts GroupInfo {chatTs, updatedAt} = fromMaybe updatedAt chatTs getMatchingContacts :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO [Contact] -getMatchingContacts db vr user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, image}} = do - contactIds <- - map fromOnly <$> case image of - Just img -> DB.query db (q <> " AND p.image = ?") (userId, contactId, CSActive, displayName, fullName, img) - Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, contactId, CSActive, displayName, fullName) +getMatchingContacts db vr user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, shortDescr, image}} = do + contactIds <- map fromOnly <$> DB.query db q (userId, contactId, CSActive, displayName, fullName, shortDescr, image) rights <$> mapM (runExceptT . getContact db vr user) contactIds where -- this query is different from one in getMatchingMemberContacts @@ -2069,14 +2068,12 @@ getMatchingContacts db vr user@User {userId} Contact {contactId, profile = Local WHERE ct.user_id = ? AND ct.contact_id != ? AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0 AND p.display_name = ? AND p.full_name = ? + AND p.short_descr IS ? AND p.image IS ? |] getMatchingMembers :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO [GroupMember] -getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {displayName, fullName, image}} = do - memberIds <- - map fromOnly <$> case image of - Just img -> DB.query db (q <> " AND p.image = ?") (userId, GCUserMember, displayName, fullName, img) - Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, GCUserMember, displayName, fullName) +getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {displayName, fullName, shortDescr, image}} = do + memberIds <- map fromOnly <$> DB.query db q (userId, GCUserMember, displayName, fullName, shortDescr, image) filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db vr user) memberIds where -- only match with members without associated contact @@ -2088,15 +2085,13 @@ getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {dis WHERE m.user_id = ? AND m.contact_id IS NULL AND m.member_category != ? AND p.display_name = ? AND p.full_name = ? + AND p.short_descr IS ? AND p.image IS ? |] getMatchingMemberContacts :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> IO [Contact] getMatchingMemberContacts _ _ _ GroupMember {memberContactId = Just _} = pure [] -getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} = do - contactIds <- - map fromOnly <$> case image of - Just img -> DB.query db (q <> " AND p.image = ?") (userId, CSActive, displayName, fullName, img) - Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, CSActive, displayName, fullName) +getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, shortDescr, image}} = do + contactIds <- map fromOnly <$> DB.query db q (userId, CSActive, displayName, fullName, shortDescr, image) rights <$> mapM (runExceptT . getContact db vr user) contactIds where q = @@ -2107,6 +2102,7 @@ getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = WHERE ct.user_id = ? AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0 AND p.display_name = ? AND p.full_name = ? + AND p.short_descr IS ? AND p.image IS ? |] createSentProbe :: DB.Connection -> TVar ChaChaDRG -> UserId -> ContactOrMember -> ExceptT StoreError IO (Probe, Int64) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 8c7c02d709..1b74255c41 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -144,6 +144,7 @@ import Control.Monad.IO.Class import Crypto.Random (ChaChaDRG) import Data.Bifunctor (first) import Data.ByteString.Char8 (ByteString) +import Data.Char (toLower) import Data.Either (fromRight, rights) import Data.Int (Int64) import Data.List (foldl', sortBy) @@ -646,7 +647,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe -- GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts FROM group_members m @@ -789,13 +790,15 @@ findDirectChatPreviews_ db User {userId} pagination clq = JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1 AND ( - LOWER(ct.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.local_alias) LIKE '%' || LOWER(?) || '%' + LOWER(ct.local_display_name) LIKE '%' || ? || '%' + OR LOWER(cp.display_name) LIKE '%' || ? || '%' + OR LOWER(cp.full_name) LIKE '%' || ? || '%' + OR LOWER(cp.short_descr) LIKE '%' || ? || '%' + OR LOWER(cp.local_alias) LIKE '%' || ? || '%' ) |] - p = baseParams :. (userId, search, search, search, search) + s = map toLower search + p = baseParams :. (userId, s, s, s, s, s) queryWithPagination q p queryWithPagination :: ToRow p => Query -> p -> IO [(ContactId, UTCTime, Maybe ChatItemId) :. ChatStatsRow] queryWithPagination query params = case pagination of @@ -895,13 +898,15 @@ findGroupChatPreviews_ db User {userId} pagination clq = JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE g.user_id = ? AND ( - LOWER(g.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' + LOWER(g.local_display_name) LIKE '%' || ? || '%' + OR LOWER(gp.display_name) LIKE '%' || ? || '%' + OR LOWER(gp.full_name) LIKE '%' || ? || '%' + OR LOWER(gp.short_descr) LIKE '%' || ? || '%' + OR LOWER(gp.description) LIKE '%' || ? || '%' ) |] - p = baseParams :. (userId, search, search, search, search) + s = map toLower search + p = baseParams :. (userId, s, s, s, s, s) queryWithPagination q p queryWithPagination :: ToRow p => Query -> p -> IO [(GroupId, UTCTime, Maybe ChatItemId) :. GroupStatsRow] queryWithPagination query params = case pagination of @@ -1051,7 +1056,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -1065,12 +1070,13 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of AND cr.contact_id IS NULL AND cr.business_group_id IS NULL AND ( - LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%' + LOWER(cr.local_display_name) LIKE '%' || ? || '%' + OR LOWER(p.display_name) LIKE '%' || ? || '%' + OR LOWER(p.full_name) LIKE '%' || ? || '%' + OR LOWER(p.short_descr) LIKE '%' || ? || '%' ) |] - params search = (userId, userId, search, search, search) + params search = let s = map toLower search in (userId, userId, s, s, s, s) getPreviews search = case pagination of PTLast count -> DB.query db (query <> " ORDER BY cr.updated_at DESC LIMIT ?") (params search :. Only count) PTAfter ts count -> DB.query db (query <> " AND cr.updated_at > ? ORDER BY cr.updated_at ASC LIMIT ?") (params search :. (ts, count)) @@ -2896,7 +2902,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, -- quoted ChatItem @@ -2904,13 +2910,13 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category, rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.local_alias, rp.preferences, rm.created_at, rm.updated_at, rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category, dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, + dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, dbm.created_at, dbm.updated_at, dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts FROM chat_items i diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index 02e2378d2c..b3ca626ca3 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -11,6 +11,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope import Simplex.Chat.Store.Postgres.Migrations.M20250526_short_links import Simplex.Chat.Store.Postgres.Migrations.M20250702_contact_requests_remove_cascade_delete import Simplex.Chat.Store.Postgres.Migrations.M20250704_groups_conn_link_prepared_connection +import Simplex.Chat.Store.Postgres.Migrations.M20250709_profile_short_descr import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -21,7 +22,8 @@ schemaMigrations = ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope), ("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links), ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete), - ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection) + ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection), + ("20250709_profile_short_descr", m20250709_profile_short_descr, Just down_m20250709_profile_short_descr) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250709_profile_short_descr.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250709_profile_short_descr.hs new file mode 100644 index 0000000000..ddf6eb4a34 --- /dev/null +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20250709_profile_short_descr.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.Postgres.Migrations.M20250709_profile_short_descr where + +import Data.Text (Text) +import qualified Data.Text as T +import Text.RawString.QQ (r) + +m20250709_profile_short_descr :: Text +m20250709_profile_short_descr = + T.pack + [r| +ALTER TABLE contact_profiles ADD COLUMN short_descr TEXT; +ALTER TABLE group_profiles ADD COLUMN short_descr TEXT; +|] + +down_m20250709_profile_short_descr :: Text +down_m20250709_profile_short_descr = + T.pack + [r| +ALTER TABLE contact_profiles DROP COLUMN short_descr; +ALTER TABLE group_profiles DROP COLUMN short_descr; +|] diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index 4115c8a491..81a2cdc505 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -331,7 +331,8 @@ CREATE TABLE test_chat_schema.contact_profiles ( incognito smallint, local_alias text DEFAULT ''::text NOT NULL, preferences text, - contact_link bytea + contact_link bytea, + short_descr text ); @@ -577,7 +578,8 @@ CREATE TABLE test_chat_schema.group_profiles ( user_id bigint, preferences text, description text, - member_admission text + member_admission text, + short_descr text ); diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index 7fca9fed3f..b1bd94edf0 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -129,7 +129,7 @@ createUserRecord :: DB.Connection -> AgentUserId -> Profile -> Bool -> ExceptT S createUserRecord db auId p activeUser = createUserRecordAt db auId p activeUser =<< liftIO getCurrentTime createUserRecordAt :: DB.Connection -> AgentUserId -> Profile -> Bool -> UTCTime -> ExceptT StoreError IO User -createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, preferences = userPreferences} activeUser currentTs = +createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, shortDescr, image, preferences = userPreferences} activeUser currentTs = checkConstraint SEDuplicateName . liftIO $ do when activeUser $ DB.execute_ db "UPDATE users SET active_user = 0" let showNtfs = True @@ -147,8 +147,8 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, (displayName, displayName, userId, currentTs, currentTs) DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" - (displayName, fullName, image, userId, userPreferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, userId, userPreferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -156,7 +156,7 @@ createUserRecordAt db (AgentUserId auId) Profile {displayName, fullName, image, (profileId, displayName, userId, BI True, currentTs, currentTs, currentTs) contactId <- insertedRowId db DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId) - pure $ toUser $ (userId, auId, contactId, profileId, BI activeUser, order, displayName, fullName, image, Nothing, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) + pure $ toUser $ (userId, auId, contactId, profileId, BI activeUser, order, displayName, fullName, shortDescr, image, Nothing, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, Nothing, Nothing, Nothing, Nothing) -- TODO [mentions] getUsersInfo :: DB.Connection -> IO [UserInfo] @@ -344,14 +344,14 @@ getUserContactProfiles db User {userId} = <$> DB.query db [sql| - SELECT display_name, full_name, image, contact_link, preferences + SELECT display_name, full_name, short_descr, image, contact_link, preferences FROM contact_profiles WHERE user_id = ? |] (Only userId) where - toContactProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) -> Profile - toContactProfile (displayName, fullName, image, contactLink, preferences) = Profile {displayName, fullName, image, contactLink, preferences} + toContactProfile :: (ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) -> Profile + toContactProfile (displayName, fullName, shortDescr, image, contactLink, preferences) = Profile {displayName, fullName, shortDescr, image, contactLink, preferences} createUserContactLink :: DB.Connection -> User -> ConnId -> CreatedLinkContact -> SubscriptionMode -> ExceptT StoreError IO () createUserContactLink db User {userId} agentConnId (CCLink cReq shortLink) subMode = diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index ed78c8092a..fb13f1dcc8 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -134,6 +134,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope import Simplex.Chat.Store.SQLite.Migrations.M20250526_short_links import Simplex.Chat.Store.SQLite.Migrations.M20250702_contact_requests_remove_cascade_delete import Simplex.Chat.Store.SQLite.Migrations.M20250704_groups_conn_link_prepared_connection +import Simplex.Chat.Store.SQLite.Migrations.M20250709_profile_short_descr import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -267,7 +268,8 @@ schemaMigrations = ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope), ("20250526_short_links", m20250526_short_links, Just down_m20250526_short_links), ("20250702_contact_requests_remove_cascade_delete", m20250702_contact_requests_remove_cascade_delete, Just down_m20250702_contact_requests_remove_cascade_delete), - ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection) + ("20250704_groups_conn_link_prepared_connection", m20250704_groups_conn_link_prepared_connection, Just down_m20250704_groups_conn_link_prepared_connection), + ("20250709_profile_short_descr", m20250709_profile_short_descr, Just down_m20250709_profile_short_descr) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250709_profile_short_descr.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250709_profile_short_descr.hs new file mode 100644 index 0000000000..59d8424d07 --- /dev/null +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20250709_profile_short_descr.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Store.SQLite.Migrations.M20250709_profile_short_descr where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20250709_profile_short_descr :: Query +m20250709_profile_short_descr = + [sql| +ALTER TABLE contact_profiles ADD COLUMN short_descr TEXT; +ALTER TABLE group_profiles ADD COLUMN short_descr TEXT; +|] + +down_m20250709_profile_short_descr :: Query +down_m20250709_profile_short_descr = + [sql| +ALTER TABLE contact_profiles DROP COLUMN short_descr; +ALTER TABLE group_profiles DROP COLUMN short_descr; +|] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 2ff3164c5d..7454285725 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -60,7 +60,7 @@ Plan: Query: SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -70,12 +70,12 @@ Query: mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, -- from GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts FROM group_members m @@ -196,7 +196,7 @@ Plan: Query: SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection @@ -287,6 +287,7 @@ Query: UPDATE contact_profiles SET display_name = ?, full_name = ?, + short_descr = ?, image = ?, contact_link = ?, updated_at = ? @@ -383,7 +384,7 @@ Plan: Query: SELECT - c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, + c.contact_profile_id, c.local_display_name, c.via_group, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, c.contact_used, c.contact_status, c.enable_ntfs, c.send_rcpts, c.favorite, p.preferences, c.user_preferences, c.created_at, c.updated_at, c.chat_ts, c.conn_full_link_to_connect, c.conn_short_link_to_connect, c.welcome_shared_msg_id, c.request_shared_msg_id, c.contact_request_id, c.contact_group_member_id, c.contact_grp_inv_sent, c.ui_themes, c.chat_deleted, c.custom_data, c.chat_item_ttl FROM contacts c @@ -398,7 +399,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -644,7 +645,7 @@ SEARCH m USING INTEGER PRIMARY KEY (rowid=?) SEARCH ct USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences, gp.member_admission + SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image, gp.preferences, gp.member_admission FROM group_profiles gp JOIN groups g ON gp.group_profile_id = g.group_profile_id WHERE g.group_id = ? @@ -673,7 +674,7 @@ Query: -- GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts FROM group_members m @@ -867,7 +868,7 @@ Query: -- GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, - p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, -- quoted ChatItem @@ -875,13 +876,13 @@ Query: -- quoted GroupMember rm.group_member_id, rm.group_id, rm.member_id, rm.peer_chat_min_version, rm.peer_chat_max_version, rm.member_role, rm.member_category, rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id, - rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences, + rp.display_name, rp.full_name, rp.short_descr, rp.image, rp.contact_link, rp.local_alias, rp.preferences, rm.created_at, rm.updated_at, rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts, -- deleted by GroupMember dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category, dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, - dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, + dbp.display_name, dbp.full_name, dbp.short_descr, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, dbm.created_at, dbm.updated_at, dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts FROM chat_items i @@ -933,7 +934,7 @@ SEARCH ri USING COVERING INDEX idx_chat_items_direct_shared_msg_id (user_id=? AN Query: SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection @@ -963,7 +964,7 @@ USE TEMP B-TREE FOR ORDER BY Query: SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -973,12 +974,12 @@ Query: mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, -- GroupInfo {membership = GroupMember {memberProfile}} - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts, -- via GroupMember m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -1013,14 +1014,14 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention, mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, - mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts FROM groups g @@ -1028,9 +1029,10 @@ Query: JOIN group_members mu USING (group_id) JOIN contact_profiles pu ON pu.contact_profile_id = COALESCE(mu.member_profile_id, mu.contact_profile_id) WHERE g.user_id = ? AND mu.contact_id = ? - AND (LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' + AND (LOWER(gp.display_name) LIKE '%' || ? || '%' + OR LOWER(gp.full_name) LIKE '%' || ? || '%' + OR LOWER(gp.short_descr) LIKE '%' || ? || '%' + OR LOWER(gp.description) LIKE '%' || ? || '%' ) Plan: @@ -1444,7 +1446,7 @@ SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE group_profiles - SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? WHERE group_profile_id IN ( SELECT group_profile_id FROM groups @@ -1524,7 +1526,7 @@ Plan: Query: SELECT -- Contact - ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, + ct.contact_id, ct.contact_profile_id, ct.local_display_name, ct.via_group, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, ct.contact_used, ct.contact_status, ct.enable_ntfs, ct.send_rcpts, ct.favorite, cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.conn_full_link_to_connect, ct.conn_short_link_to_connect, ct.welcome_shared_msg_id, ct.request_shared_msg_id, ct.contact_request_id, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, ct.chat_item_ttl, -- Connection @@ -1627,7 +1629,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -1641,9 +1643,10 @@ Query: AND cr.contact_id IS NULL AND cr.business_group_id IS NULL AND ( - LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%' + LOWER(cr.local_display_name) LIKE '%' || ? || '%' + OR LOWER(p.display_name) LIKE '%' || ? || '%' + OR LOWER(p.full_name) LIKE '%' || ? || '%' + OR LOWER(p.short_descr) LIKE '%' || ? || '%' ) AND cr.updated_at < ? ORDER BY cr.updated_at DESC LIMIT ? Plan: @@ -1655,7 +1658,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -1669,9 +1672,10 @@ Query: AND cr.contact_id IS NULL AND cr.business_group_id IS NULL AND ( - LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%' + LOWER(cr.local_display_name) LIKE '%' || ? || '%' + OR LOWER(p.display_name) LIKE '%' || ? || '%' + OR LOWER(p.full_name) LIKE '%' || ? || '%' + OR LOWER(p.short_descr) LIKE '%' || ? || '%' ) AND cr.updated_at > ? ORDER BY cr.updated_at ASC LIMIT ? Plan: @@ -1683,7 +1687,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -1697,9 +1701,10 @@ Query: AND cr.contact_id IS NULL AND cr.business_group_id IS NULL AND ( - LOWER(cr.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(p.full_name) LIKE '%' || LOWER(?) || '%' + LOWER(cr.local_display_name) LIKE '%' || ? || '%' + OR LOWER(p.display_name) LIKE '%' || ? || '%' + OR LOWER(p.full_name) LIKE '%' || ? || '%' + OR LOWER(p.short_descr) LIKE '%' || ? || '%' ) ORDER BY cr.updated_at DESC LIMIT ? Plan: @@ -1742,10 +1747,11 @@ Query: JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id WHERE ct.user_id = ? AND ct.is_user = 0 AND ct.deleted = 0 AND ct.contact_used = 1 AND ( - LOWER(ct.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(cp.local_alias) LIKE '%' || LOWER(?) || '%' + LOWER(ct.local_display_name) LIKE '%' || ? || '%' + OR LOWER(cp.display_name) LIKE '%' || ? || '%' + OR LOWER(cp.full_name) LIKE '%' || ? || '%' + OR LOWER(cp.short_descr) LIKE '%' || ? || '%' + OR LOWER(cp.local_alias) LIKE '%' || ? || '%' ) ORDER BY ct.chat_ts DESC LIMIT ? Plan: @@ -2113,10 +2119,11 @@ Query: JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id WHERE g.user_id = ? AND ( - LOWER(g.local_display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.display_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.full_name) LIKE '%' || LOWER(?) || '%' - OR LOWER(gp.description) LIKE '%' || LOWER(?) || '%' + LOWER(g.local_display_name) LIKE '%' || ? || '%' + OR LOWER(gp.display_name) LIKE '%' || ? || '%' + OR LOWER(gp.full_name) LIKE '%' || ? || '%' + OR LOWER(gp.short_descr) LIKE '%' || ? || '%' + OR LOWER(gp.description) LIKE '%' || ? || '%' ) ORDER BY g.chat_ts DESC LIMIT ? Plan: @@ -3169,7 +3176,7 @@ Plan: SEARCH connections USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences + SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences FROM contact_profiles cp WHERE cp.user_id = ? AND cp.contact_profile_id = ? @@ -3183,19 +3190,8 @@ Query: WHERE ct.user_id = ? AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0 AND p.display_name = ? AND p.full_name = ? - AND p.image = ? -Plan: -SEARCH ct USING INDEX idx_contacts_chat_ts (user_id=?) -SEARCH p USING INTEGER PRIMARY KEY (rowid=?) - -Query: - SELECT ct.contact_id - FROM contacts ct - JOIN contact_profiles p ON ct.contact_profile_id = p.contact_profile_id - WHERE ct.user_id = ? - AND ct.contact_status = ? AND ct.deleted = 0 AND ct.is_user = 0 - AND p.display_name = ? AND p.full_name = ? - AND p.image is NULL + AND p.short_descr IS ? AND p.image IS ? + Plan: SEARCH ct USING INDEX idx_contacts_chat_ts (user_id=?) SEARCH p USING INTEGER PRIMARY KEY (rowid=?) @@ -3223,7 +3219,7 @@ SEARCH f USING PRIMARY KEY (file_id=?) SEARCH d USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT display_name, full_name, image, contact_link, preferences + SELECT display_name, full_name, short_descr, image, contact_link, preferences FROM contact_profiles WHERE user_id = ? @@ -3324,19 +3320,8 @@ Query: WHERE m.user_id = ? AND m.contact_id IS NULL AND m.member_category != ? AND p.display_name = ? AND p.full_name = ? - AND p.image = ? -Plan: -SEARCH m USING INDEX idx_group_members_user_id (user_id=?) -SEARCH p USING INTEGER PRIMARY KEY (rowid=?) - -Query: - SELECT m.group_member_id - FROM group_members m - JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) - WHERE m.user_id = ? AND m.contact_id IS NULL - AND m.member_category != ? - AND p.display_name = ? AND p.full_name = ? - AND p.image is NULL + AND p.short_descr IS ? AND p.image IS ? + Plan: SEARCH m USING INDEX idx_group_members_user_id (user_id=?) SEARCH p USING INTEGER PRIMARY KEY (rowid=?) @@ -4133,8 +4118,8 @@ Query: Plan: Query: - INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at) - VALUES (?,?,?,?,?,?,?) + INSERT INTO contact_profiles (display_name, full_name, short_descr, image, user_id, incognito, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) @@ -4389,7 +4374,7 @@ SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = ?, preferences = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? Plan: @@ -4397,7 +4382,7 @@ SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, contact_link = NULL, preferences = NULL, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? Plan: @@ -4405,7 +4390,7 @@ SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?) Query: UPDATE contact_profiles - SET display_name = ?, full_name = ?, image = ?, updated_at = ? + SET display_name = ?, full_name = ?, short_descr = ?, image = ?, updated_at = ? WHERE user_id = ? AND contact_profile_id = ? Plan: @@ -4679,7 +4664,7 @@ SEARCH pending_group_messages USING COVERING INDEX idx_pending_group_messages_me Query: SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -4688,7 +4673,7 @@ Query: -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts FROM groups g @@ -4705,7 +4690,7 @@ SEARCH pu USING INTEGER PRIMARY KEY (rowid=?) Query: SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -4714,7 +4699,7 @@ Query: -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts FROM groups g @@ -4732,7 +4717,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -4747,7 +4732,7 @@ Query: SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_id, cr.contact_id, cr.business_group_id, cr.user_contact_link_id, - cr.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, cr.xcontact_id, + cr.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, cr.xcontact_id, cr.pq_support, cr.welcome_shared_msg_id, cr.request_shared_msg_id, p.preferences, cr.created_at, cr.updated_at, cr.peer_chat_min_version, cr.peer_chat_max_version @@ -4761,7 +4746,7 @@ SEARCH p USING INTEGER PRIMARY KEY (rowid=?) Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4794,7 +4779,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4819,7 +4804,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4844,7 +4829,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4869,7 +4854,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4894,7 +4879,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -4919,7 +4904,7 @@ SEARCH cc USING COVERING INDEX idx_connections_group_member (user_id=? AND group Query: SELECT m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, - m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, + m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.local_alias, p.preferences, m.created_at, m.updated_at, m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id, @@ -5065,7 +5050,7 @@ Plan: SEARCH server_operators USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5077,7 +5062,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5090,7 +5075,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5103,7 +5088,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5117,7 +5102,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5130,7 +5115,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5143,7 +5128,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5156,7 +5141,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5169,7 +5154,7 @@ SEARCH uct USING INTEGER PRIMARY KEY (rowid=?) SEARCH ucp USING INTEGER PRIMARY KEY (rowid=?) Query: - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id @@ -5633,15 +5618,15 @@ Plan: Query: INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?) Plan: -Query: INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) -Query: INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) -Query: INSERT INTO contact_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?) +Query: INSERT INTO contact_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) Plan: SEARCH contact_requests USING COVERING INDEX idx_contact_requests_contact_profile_id (contact_profile_id=?) @@ -5677,10 +5662,10 @@ Plan: Query: INSERT INTO files (user_id, group_id, file_name, file_size, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) Plan: -Query: INSERT INTO group_profiles (display_name, full_name, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?) +Query: INSERT INTO group_profiles (display_name, full_name, short_descr, description, image, user_id, preferences, member_admission, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?) Plan: -Query: INSERT INTO group_profiles (display_name, full_name, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?) +Query: INSERT INTO group_profiles (display_name, full_name, short_descr, image, user_id, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?) Plan: Query: INSERT INTO group_snd_item_statuses (chat_item_id, group_member_id, group_snd_item_status) VALUES (?,?,?) diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 913a99b6ec..8352b4f952 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -18,7 +18,8 @@ CREATE TABLE contact_profiles( incognito INTEGER, local_alias TEXT DEFAULT '' CHECK(local_alias NOT NULL), preferences TEXT, - contact_link BLOB + contact_link BLOB, + short_descr TEXT ); CREATE TABLE users( user_id INTEGER PRIMARY KEY, @@ -114,7 +115,8 @@ CREATE TABLE group_profiles( user_id INTEGER DEFAULT NULL REFERENCES users ON DELETE CASCADE, preferences TEXT, description TEXT NULL, - member_admission TEXT + member_admission TEXT, + short_descr TEXT ); CREATE TABLE groups( group_id INTEGER PRIMARY KEY, -- local group ID diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 90246f0710..96ae329b5b 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -293,14 +293,14 @@ createConnection_ db userId connType entityId acId connStatus connChatVersion pe ent ct = if connType == ct then entityId else Nothing createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64 -createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do +createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, shortDescr, image} = do DB.execute db [sql| - INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at) - VALUES (?,?,?,?,?,?,?) + INSERT INTO contact_profiles (display_name, full_name, short_descr, image, user_id, incognito, created_at, updated_at) + VALUES (?,?,?,?,?,?,?,?) |] - (displayName, fullName, image, userId, Just (BI True), createdAt, createdAt) + (displayName, fullName, shortDescr, image, userId, Just (BI True), createdAt, createdAt) insertedRowId db updateConnSupportPQ :: DB.Connection -> Int64 -> PQSupport -> PQEncryption -> IO () @@ -387,12 +387,12 @@ createContact db User {userId} profile = do void $ createContact_ db userId profile Nothing "" Nothing currentTs createContact_ :: DB.Connection -> UserId -> Profile -> Maybe (ACreatedConnLink, Maybe SharedMsgId) -> LocalAlias -> Maybe Int64 -> UTCTime -> ExceptT StoreError IO (Text, ContactId, ProfileId) -createContact_ db userId Profile {displayName, fullName, image, contactLink, preferences} prepared localAlias viaGroup currentTs = +createContact_ db userId Profile {displayName, fullName, shortDescr, image, contactLink, preferences} prepared localAlias viaGroup currentTs = ExceptT . withLocalDisplayName db userId displayName $ \ldn -> do DB.execute db - "INSERT INTO contact_profiles (display_name, full_name, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" - (displayName, fullName, image, contactLink, userId, localAlias, preferences, currentTs, currentTs) + "INSERT INTO contact_profiles (display_name, full_name, short_descr, image, contact_link, user_id, local_alias, preferences, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)" + (displayName, fullName, shortDescr, image, contactLink, userId, localAlias, preferences, currentTs, currentTs) profileId <- insertedRowId db DB.execute db @@ -436,13 +436,13 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = type PreparedContactRow = (Maybe AConnectionRequestUri, Maybe AConnShortLink, Maybe SharedMsgId, Maybe SharedMsgId) -type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) +type ContactRow' = (ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, BoolInt, ContactStatus) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime) :. PreparedContactRow :. (Maybe Int64, Maybe GroupMemberId, BoolInt, Maybe UIThemeEntityOverrides, BoolInt, Maybe CustomData, Maybe Int64) type ContactRow = Only ContactId :. ContactRow' toContact :: VersionRangeChat -> User -> [ChatTagId] -> ContactRow :. MaybeConnectionRow -> Contact -toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) = - let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} +toContact vr user chatTags ((Only contactId :. (profileId, localDisplayName, viaGroup, displayName, fullName, shortDescr, image, contactLink, localAlias, BI contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, BI favorite, preferences, userPreferences, createdAt, updatedAt, chatTs) :. preparedContactRow :. (contactRequestId, contactGroupMemberId, BI contactGrpInvSent, uiThemes, BI chatDeleted, customData, chatItemTTL)) :. connRow) = + let profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences, localAlias} activeConn = toMaybeConnection vr connRow chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} incognito = maybe False connIncognito activeConn @@ -463,42 +463,39 @@ toACreatedConnLink_ (Just (ACR m cr)) csl = case csl of getProfileById :: DB.Connection -> UserId -> Int64 -> ExceptT StoreError IO LocalProfile getProfileById db userId profileId = - ExceptT . firstRow toProfile (SEProfileNotFound profileId) $ + ExceptT . firstRow rowToLocalProfile (SEProfileNotFound profileId) $ DB.query db [sql| - SELECT cp.display_name, cp.full_name, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences + SELECT cp.contact_profile_id, cp.display_name, cp.full_name, cp.short_descr, cp.image, cp.contact_link, cp.local_alias, cp.preferences -- , ct.user_preferences FROM contact_profiles cp WHERE cp.user_id = ? AND cp.contact_profile_id = ? |] (userId, profileId) - where - toProfile :: (ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) -> LocalProfile - toProfile (displayName, fullName, image, contactLink, localAlias, preferences) = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} -type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64) :. (Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) +type ContactRequestRow = (Int64, ContactName, AgentInvId, Maybe ContactId, Maybe GroupId, Maybe Int64) :. (Int64, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact) :. (Maybe XContactId, PQSupport, Maybe SharedMsgId, Maybe SharedMsgId, Maybe Preferences, UTCTime, UTCTime, VersionChat, VersionChat) toContactRequest :: ContactRequestRow -> UserContactRequest -toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_) :. (profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer)) = do - let profile = Profile {displayName, fullName, image, contactLink, preferences} +toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_) :. (profileId, displayName, fullName, shortDescr, image, contactLink) :. (xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, preferences, createdAt, updatedAt, minVer, maxVer)) = do + let profile = Profile {displayName, fullName, shortDescr, image, contactLink, preferences} cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer in UserContactRequest {contactRequestId, agentInvitationId, contactId_, businessGroupId_, userContactLinkId_, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, welcomeSharedMsgId, requestSharedMsgId, createdAt, updatedAt} userQuery :: Query userQuery = [sql| - SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.image, ucp.contact_link, ucp.preferences, + SELECT u.user_id, u.agent_user_id, u.contact_id, ucp.contact_profile_id, u.active_user, u.active_order, u.local_display_name, ucp.full_name, ucp.short_descr, ucp.image, ucp.contact_link, ucp.preferences, u.show_ntfs, u.send_rcpts_contacts, u.send_rcpts_small_groups, u.view_pwd_hash, u.view_pwd_salt, u.user_member_profile_updated_at, u.ui_themes FROM users u JOIN contacts uct ON uct.contact_id = u.contact_id JOIN contact_profiles ucp ON ucp.contact_profile_id = uct.contact_profile_id |] -toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User -toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, displayName, fullName, image, contactLink, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = +toUser :: (UserId, UserId, ContactId, ProfileId, BoolInt, Int64, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe Preferences) :. (BoolInt, BoolInt, BoolInt, Maybe B64UrlByteString, Maybe B64UrlByteString, Maybe UTCTime, Maybe UIThemeEntityOverrides) -> User +toUser ((userId, auId, userContactId, profileId, BI activeUser, activeOrder, displayName, fullName, shortDescr, image, contactLink, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, viewPwdHash_, viewPwdSalt_, userMemberProfileUpdatedAt, uiThemes)) = User {userId, agentUserId = AgentUserId auId, userContactId, localDisplayName = displayName, profile, activeUser, activeOrder, fullPreferences, showNtfs, sendRcptsContacts, sendRcptsSmallGroups, viewPwdHash, userMemberProfileUpdatedAt, uiThemes} where - profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences = userPreferences, localAlias = ""} + profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences = userPreferences, localAlias = ""} fullPreferences = mergePreferences Nothing userPreferences viewPwdHash = UserPwdHash <$> viewPwdHash_ <*> viewPwdSalt_ @@ -612,16 +609,18 @@ type PreparedGroupRow = (Maybe ConnReqContact, Maybe ShortLinkContact, BoolInt, type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId) -type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int) :. GroupMemberRow +type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Text, Maybe Text, Maybe ImageData) :. (Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. PreparedGroupRow :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int) :. GroupMemberRow -type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime) +type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId) :. ProfileRow :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime) + +type ProfileRow = (ProfileId, ContactName, Text, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo -toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention) :. userMemberRow) = +toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, shortDescr, localAlias, description, image) :. (enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. preparedGroupRow :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention) :. userMemberRow) = let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences - groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission} + groupProfile = GroupProfile {displayName, fullName, shortDescr, description, image, groupPreferences, memberAdmission} businessChat = toBusinessChatInfo businessRow preparedGroup = toPreparedGroup preparedGroupRow in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, preparedGroup, chatTags, chatItemTTL, uiThemes, customData, membersRequireAttention} @@ -633,8 +632,8 @@ toPreparedGroup = \case _ -> Nothing toGroupMember :: Int64 -> GroupMemberRow -> GroupMember -toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences) :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs)) = - let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} +toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId) :. profileRow :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs)) = + let memberProfile = rowToLocalProfile profileRow memberSettings = GroupMemberSettings {showMessages} blockedByAdmin = maybe False mrsBlocked memberRestriction_ invitedBy = toInvitedBy userContactId invitedById @@ -652,6 +651,10 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, _ -> Nothing in GroupMember {..} +rowToLocalProfile :: ProfileRow -> LocalProfile +rowToLocalProfile (profileId, displayName, fullName, shortDescr, image, contactLink, localAlias, preferences) = + LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, localAlias, preferences} + toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId} toBusinessChatInfo _ = Nothing @@ -661,7 +664,7 @@ groupInfoQuery = [sql| SELECT -- GroupInfo - g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image, + g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.short_descr, g.local_alias, gp.description, gp.image, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission, g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.conn_full_link_to_connect, g.conn_short_link_to_connect, g.conn_link_prepared_connection, g.conn_link_started_connection, g.welcome_shared_msg_id, g.request_shared_msg_id, @@ -670,7 +673,7 @@ groupInfoQuery = -- GroupMember - membership mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, - pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences, + pu.display_name, pu.full_name, pu.short_descr, pu.image, pu.contact_link, pu.local_alias, pu.preferences, mu.created_at, mu.updated_at, mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts FROM groups g diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 739f5943b9..5cedf0368e 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -422,9 +422,9 @@ type MemberName = Text type GroupName = Text -optionalFullName :: ContactName -> Text -> Text -optionalFullName displayName fullName - | T.null fullName || displayName == fullName = "" +optionalFullName :: ContactName -> Text -> Maybe Text -> Text +optionalFullName displayName fullName shortDescr + | T.null fullName || displayName == fullName = maybe "" (\sd -> " (" <> sd <> ")") shortDescr | otherwise = " (" <> fullName <> ")" data ShortGroup = ShortGroup @@ -620,6 +620,7 @@ contactUserPreferences user userPreferences contactPreferences connectedIncognit data Profile = Profile { displayName :: ContactName, fullName :: Text, + shortDescr :: Maybe Text, -- short description limited to 160 characters image :: Maybe ImageData, contactLink :: Maybe ConnLinkContact, preferences :: Maybe Preferences @@ -632,7 +633,7 @@ data Profile = Profile profileFromName :: ContactName -> Profile profileFromName displayName = - Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} + Profile {displayName, fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = Nothing} -- check if profiles match ignoring preferences profilesMatch :: LocalProfile -> LocalProfile -> Bool @@ -642,8 +643,8 @@ profilesMatch n1 == n2 && fn1 == fn2 && i1 == i2 redactedMemberProfile :: Profile -> Profile -redactedMemberProfile Profile {displayName, fullName, image} = - Profile {displayName, fullName, image, contactLink = Nothing, preferences = Nothing} +redactedMemberProfile Profile {displayName, fullName, shortDescr, image} = + Profile {displayName, fullName, shortDescr, image, contactLink = Nothing, preferences = Nothing} data IncognitoProfile = NewIncognito Profile | ExistingIncognito LocalProfile @@ -669,6 +670,7 @@ data LocalProfile = LocalProfile { profileId :: ProfileId, displayName :: ContactName, fullName :: Text, + shortDescr :: Maybe Text, image :: Maybe ImageData, contactLink :: Maybe ConnLinkContact, preferences :: Maybe Preferences, @@ -680,17 +682,18 @@ localProfileId :: LocalProfile -> ProfileId localProfileId LocalProfile {profileId} = profileId toLocalProfile :: ProfileId -> Profile -> LocalAlias -> LocalProfile -toLocalProfile profileId Profile {displayName, fullName, image, contactLink, preferences} localAlias = - LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} +toLocalProfile profileId Profile {displayName, fullName, shortDescr, image, contactLink, preferences} localAlias = + LocalProfile {profileId, displayName, fullName, shortDescr, image, contactLink, preferences, localAlias} fromLocalProfile :: LocalProfile -> Profile -fromLocalProfile LocalProfile {displayName, fullName, image, contactLink, preferences} = - Profile {displayName, fullName, image, contactLink, preferences} +fromLocalProfile LocalProfile {displayName, fullName, shortDescr, image, contactLink, preferences} = + Profile {displayName, fullName, shortDescr, image, contactLink, preferences} data GroupProfile = GroupProfile { displayName :: GroupName, fullName :: Text, - description :: Maybe Text, + shortDescr :: Maybe Text, -- short description limited to 160 characters + description :: Maybe Text, -- this has been repurposed as welcome message image :: Maybe ImageData, groupPreferences :: Maybe GroupPreferences, memberAdmission :: Maybe GroupMemberAdmission diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index fe28775c50..eb6db90d21 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -606,8 +606,8 @@ viewUsersList us = in if null ss then ["no users"] else ss where ldn (UserInfo User {localDisplayName = n} _) = T.toLower n - userInfo (UserInfo User {localDisplayName = n, profile = LocalProfile {fullName}, activeUser, showNtfs, viewPwdHash} count) - | activeUser || isNothing viewPwdHash = Just $ ttyFullName n fullName <> infoStr + userInfo (UserInfo User {localDisplayName = n, profile = LocalProfile {fullName, shortDescr}, activeUser, showNtfs, viewPwdHash} count) + | activeUser || isNothing viewPwdHash = Just $ ttyFullName n fullName shortDescr <> infoStr | otherwise = Nothing where infoStr = if null info then "" else " (" <> mconcat (intersperse ", " info) <> ")" @@ -1158,8 +1158,8 @@ viewAcceptingBusinessRequest :: GroupInfo -> [StyledString] viewAcceptingBusinessRequest g = [ttyFullGroup g <> ": accepting business address request..."] viewReceivedContactRequest :: ContactName -> Profile -> [StyledString] -viewReceivedContactRequest c Profile {fullName} = - [ ttyFullName c fullName <> " wants to connect to you!", +viewReceivedContactRequest c Profile {fullName, shortDescr} = + [ ttyFullName c fullName shortDescr <> " wants to connect to you!", "to accept: " <> highlight ("/ac " <> viewName c), "to reject: " <> highlight ("/rc " <> viewName c) <> " (the sender will NOT be notified)" ] @@ -1380,9 +1380,9 @@ viewSentGroupInvitation g c = case contactConn c of Nothing -> [] groupInvitation' :: GroupInfo -> StyledString -groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} = +groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName, shortDescr}} = highlight ("#" <> viewName ldn) - <> optFullName ldn fullName + <> optFullName ldn fullName shortDescr <> " - you are invited (" <> highlight ("/j " <> viewName ldn) <> joinText @@ -1415,9 +1415,9 @@ viewContactAndMemberAssociated ct g m ct' = ] viewUserProfile :: Profile -> [StyledString] -viewUserProfile Profile {displayName, fullName} = - [ "user profile: " <> ttyFullName displayName fullName, - "use " <> highlight' "/p " <> " to change it", +viewUserProfile Profile {displayName, fullName, shortDescr} = + [ "user profile: " <> ttyFullName displayName fullName shortDescr, + "use " <> highlight' "/p []" <> " to change it", "(the updated profile will be sent to all your contacts)" ] @@ -1703,16 +1703,17 @@ viewSwitchPhase = \case SPCompleted -> "changed address" viewUserProfileUpdated :: Profile -> Profile -> UserProfileUpdateSummary -> [StyledString] -viewUserProfileUpdated Profile {displayName = n, fullName, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', image = image', contactLink = contactLink', preferences = prefs'} summary = +viewUserProfileUpdated Profile {displayName = n, fullName, shortDescr, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', shortDescr = shortDescr', image = image', contactLink = contactLink', preferences = prefs'} summary = profileUpdated <> viewPrefsUpdated preferences prefs' where UserProfileUpdateSummary {updateSuccesses = s, updateFailures = f} = summary profileUpdated - | n == n' && fullName == fullName' && image == image' && contactLink == contactLink' = [] - | n == n' && fullName == fullName' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"] - | n == n' && fullName == fullName' = [if isNothing image' then "profile image removed" else "profile image updated"] + | n == n' && fullName == fullName' && shortDescr == shortDescr' && image == image' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' && shortDescr == shortDescr' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"] + | n == n' && fullName == fullName' && shortDescr == shortDescr' = [if isNothing image' then "profile image removed" else "profile image updated"] + | n == n' && fullName == fullName' = ["user bio " <> (if maybe True T.null shortDescr' then "removed" else "changed to " <> maybe "" plain shortDescr') <> notified] | n == n' = ["user full name " <> (if T.null fullName' || fullName' == n' then "removed" else "changed to " <> plain fullName') <> notified] - | otherwise = ["user profile is changed to " <> ttyFullName n' fullName' <> notified] + | otherwise = ["user profile is changed to " <> ttyFullName n' fullName' shortDescr' <> notified] notified = " (your " <> sShow s <> " contacts are notified" <> failures <> ")" failures | f > 0 = ", " <> sShow f <> " failures" @@ -1770,8 +1771,8 @@ countactUserPrefText cup = case cup of viewGroupUpdated :: GroupInfo -> GroupInfo -> Maybe GroupMember -> [StyledString] viewGroupUpdated - GroupInfo {localDisplayName = n, groupProfile = GroupProfile {fullName, description, image, groupPreferences = gps, memberAdmission = ma}} - g'@GroupInfo {localDisplayName = n', groupProfile = GroupProfile {fullName = fullName', description = description', image = image', groupPreferences = gps', memberAdmission = ma'}} + GroupInfo {localDisplayName = n, groupProfile = GroupProfile {fullName, shortDescr, description, image, groupPreferences = gps, memberAdmission = ma}} + g'@GroupInfo {localDisplayName = n', groupProfile = GroupProfile {fullName = fullName', shortDescr = shortDescr', description = description', image = image', groupPreferences = gps', memberAdmission = ma'}} m = do let update = groupProfileUpdated <> groupPrefsUpdated <> memberAdmissionUpdated if null update @@ -1782,8 +1783,9 @@ viewGroupUpdated groupProfileUpdated = ["changed to " <> ttyFullGroup g' | n /= n'] <> ["full name " <> if T.null fullName' || fullName' == n' then "removed" else "changed to: " <> plain fullName' | n == n' && fullName /= fullName'] + <> ["description " <> if maybe True T.null shortDescr' then "removed" else "changed to: " <> maybe "" plain shortDescr' | n == n' && fullName == fullName' && shortDescr /= shortDescr'] <> ["profile image " <> maybe "removed" (const "updated") image' | image /= image'] - <> (if description == description' then [] else maybe ["description removed"] ((bold' "description changed to:" :) . map plain . T.lines) description') + <> (if description == description' then [] else maybe ["welcome message removed"] ((bold' "welcome message changed to:" :) . map plain . T.lines) description') groupPrefsUpdated | null prefs = [] | otherwise = bold' "updated group preferences:" : prefs @@ -1799,10 +1801,11 @@ viewGroupUpdated | otherwise = ["changed member admission rules"] viewGroupProfile :: GroupInfo -> [StyledString] -viewGroupProfile g@GroupInfo {groupProfile = GroupProfile {description, image, groupPreferences = gps}} = +viewGroupProfile g@GroupInfo {groupProfile = GroupProfile {shortDescr, description, image, groupPreferences = gps}} = [ttyFullGroup g] + <> maybe [] (\sd -> ["description: " <> plain sd]) shortDescr <> maybe [] (const ["has profile image"]) image - <> maybe [] ((bold' "description:" :) . map plain . T.lines) description + <> maybe [] ((bold' "welcome message:" :) . map plain . T.lines) description <> (bold' "group preferences:" : map viewPref allGroupFeatures) where viewPref (AGF f) = plain $ groupPreferenceText (pref gps) @@ -1963,16 +1966,20 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case viewContactUpdated :: Contact -> Contact -> [StyledString] viewContactUpdated - Contact {localDisplayName = n, profile = LocalProfile {fullName, contactLink}} - Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', contactLink = contactLink'}} - | n == n' && fullName == fullName' && contactLink == contactLink' = [] - | n == n' && fullName == fullName' = + Contact {localDisplayName = n, profile = LocalProfile {fullName, shortDescr, contactLink}} + Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', shortDescr = shortDescr', contactLink = contactLink'}} + | n == n' && fullName == fullName' && shortDescr == shortDescr' && contactLink == contactLink' = [] + | n == n' && fullName == fullName' && shortDescr == shortDescr' = if isNothing contactLink' then [ttyContact n <> " removed contact address"] else [ttyContact n <> " set new contact address, use " <> highlight ("/info " <> n) <> " to view"] + | n == n' && fullName == fullName' = + if maybe True T.null shortDescr' + then ["contact " <> ttyContact n <> " removed bio"] + else ["contact " <> ttyContact n <> " updated bio: " <> maybe "" plain shortDescr'] | n == n' = ["contact " <> ttyContact n <> fullNameUpdate] | otherwise = - [ "contact " <> ttyContact n <> " changed to " <> ttyFullName n' fullName', + [ "contact " <> ttyContact n <> " changed to " <> ttyFullName n' fullName' shortDescr', "use " <> ttyToContact n' <> highlight' "" <> " to send messages" ] where @@ -2566,18 +2573,18 @@ ttyContact' :: Contact -> StyledString ttyContact' Contact {localDisplayName = c} = ttyContact c ttyFullContact :: Contact -> StyledString -ttyFullContact Contact {localDisplayName, profile = LocalProfile {fullName}} = - ttyFullName localDisplayName fullName +ttyFullContact Contact {localDisplayName, profile = LocalProfile {fullName, shortDescr}} = + ttyFullName localDisplayName fullName shortDescr ttyMember :: GroupMember -> StyledString ttyMember GroupMember {localDisplayName} = ttyContact localDisplayName ttyFullMember :: GroupMember -> StyledString -ttyFullMember GroupMember {localDisplayName, memberProfile = LocalProfile {fullName}} = - ttyFullName localDisplayName fullName +ttyFullMember GroupMember {localDisplayName, memberProfile = LocalProfile {fullName, shortDescr}} = + ttyFullName localDisplayName fullName shortDescr -ttyFullName :: ContactName -> Text -> StyledString -ttyFullName c fullName = ttyContact c <> optFullName c fullName +ttyFullName :: ContactName -> Text -> Maybe Text -> StyledString +ttyFullName c fullName shortDescr = ttyContact c <> optFullName c fullName shortDescr ttyToContact :: ContactName -> StyledString ttyToContact c = ttyTo $ "@" <> viewName c <> " " @@ -2626,8 +2633,8 @@ ttyGroups [g] = ttyGroup g ttyGroups (g : gs) = ttyGroup g <> ", " <> ttyGroups gs ttyFullGroup :: GroupInfo -> StyledString -ttyFullGroup GroupInfo {localDisplayName = g, groupProfile = GroupProfile {fullName}} = - ttyGroup g <> optFullName g fullName +ttyFullGroup GroupInfo {localDisplayName = g, groupProfile = GroupProfile {fullName, shortDescr}} = + ttyGroup g <> optFullName g fullName shortDescr ttyFromGroup :: GroupInfo -> Maybe GroupChatScopeInfo -> GroupMember -> StyledString ttyFromGroup g scopeInfo m = ttyFromGroupAttention g scopeInfo m False @@ -2672,8 +2679,8 @@ groupScopeInfoStr = \case ttyFilePath :: FilePath -> StyledString ttyFilePath = plain -optFullName :: ContactName -> Text -> StyledString -optFullName localDisplayName fullName = plain $ optionalFullName localDisplayName fullName +optFullName :: ContactName -> Text -> Maybe Text -> StyledString +optFullName localDisplayName fullName shortDescr = plain $ optionalFullName localDisplayName fullName shortDescr ctIncognito :: Contact -> StyledString ctIncognito ct = if contactConnIncognito ct then incognitoPrefix else "" diff --git a/tests/Bots/BroadcastTests.hs b/tests/Bots/BroadcastTests.hs index 71142c8b60..500e30183b 100644 --- a/tests/Bots/BroadcastTests.hs +++ b/tests/Bots/BroadcastTests.hs @@ -33,7 +33,7 @@ withBroadcastBot opts test = bot = simplexChatCore testCfg (mkChatOpts opts) $ broadcastBot opts broadcastBotProfile :: Profile -broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadcast Bot", image = Nothing, contactLink = Nothing, preferences = Nothing} +broadcastBotProfile = Profile {displayName = "broadcast_bot", fullName = "Broadcast Bot", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = Nothing} mkBotOpts :: TestParams -> [KnownContact] -> BroadcastBotOpts mkBotOpts ps publishers = diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index c8b3d33916..351943925f 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -75,7 +75,7 @@ directoryServiceTests = do it "should accept some incorrect spellings" testCaptcha directoryProfile :: Profile -directoryProfile = Profile {displayName = "SimpleX-Directory", fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} +directoryProfile = Profile {displayName = "SimpleX-Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = Nothing} mkDirectoryOpts :: TestParams -> [KnownContact] -> Maybe KnownGroup -> DirectoryOpts mkDirectoryOpts TestParams {tmpPath = ps} superUsers ownersGroup = @@ -219,7 +219,7 @@ testDirectoryService ps = u <## "2 members" updateGroupProfile u welcome = do u ##> ("/set welcome #PSA " <> welcome) - u <## "description changed to:" + u <## "welcome message changed to:" u <## welcome approvalRequested su welcome grId = do su <# "SimpleX-Directory> bob submitted the group ID 1:" @@ -738,11 +738,11 @@ testRegOwnerChangedProfile ps = registerGroup superUser bob "privacy" "Privacy" addCathAsOwner bob cath bob ##> "/gp privacy privacy Privacy and Security" - bob <## "full name changed to: Privacy and Security" + bob <## "description changed to: Privacy and Security" bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated!" bob <## "It is hidden from the directory until approved." cath <## "bob updated group #privacy:" - cath <## "full name changed to: Privacy and Security" + cath <## "description changed to: Privacy and Security" cath `connectVia` dsLink cath <## "contact and member are merged: SimpleX-Directory_1, #privacy SimpleX-Directory" cath <## "use @SimpleX-Directory to send messages" @@ -763,9 +763,9 @@ testAnotherOwnerChangedProfile ps = cath <## "contact and member are merged: SimpleX-Directory_1, #privacy SimpleX-Directory" cath <## "use @SimpleX-Directory to send messages" cath ##> "/gp privacy privacy Privacy and Security" - cath <## "full name changed to: Privacy and Security" + cath <## "description changed to: Privacy and Security" bob <## "cath updated group #privacy:" - bob <## "full name changed to: Privacy and Security" + bob <## "description changed to: Privacy and Security" bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath!" bob <## "It is hidden from the directory until approved." groupNotFound cath "privacy" @@ -784,9 +784,9 @@ testNotConnectedOwnerChangedProfile ps = registerGroup superUser bob "privacy" "Privacy" addCathAsOwner bob cath cath ##> "/gp privacy privacy Privacy and Security" - cath <## "full name changed to: Privacy and Security" + cath <## "description changed to: Privacy and Security" bob <## "cath updated group #privacy:" - bob <## "full name changed to: Privacy and Security" + bob <## "description changed to: Privacy and Security" bob <# "SimpleX-Directory> The group ID 1 (privacy) is updated by cath!" bob <## "It is hidden from the directory until approved." groupNotFound dan "privacy" @@ -806,13 +806,13 @@ testRegOwnerRemovedLink ps = bob <## "Welcome message:" welcomeWithLink <- getTermLine bob bob ##> "/set welcome #privacy Welcome!" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## "Welcome!" bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message." bob <## "" bob <## "The group is hidden from the directory until the group link is added and the group is re-approved." cath <## "bob updated group #privacy:" - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## "Welcome!" superUser <# "SimpleX-Directory> The group link is removed from ID 1 (privacy), de-listed." cath `connectVia` dsLink @@ -821,12 +821,12 @@ testRegOwnerRemovedLink ps = groupNotFound cath "privacy" let withChangedLink = T.unpack $ T.replace "contact#/?v=2-7&" "contact#/?v=3-7&" $ T.pack welcomeWithLink bob ##> ("/set welcome #privacy " <> withChangedLink) - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## withChangedLink bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message." bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." cath <## "bob updated group #privacy:" - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## withChangedLink reapproveGroup 3 superUser bob groupFoundN 3 cath "privacy" @@ -846,10 +846,10 @@ testAnotherOwnerRemovedLink ps = bob <## "Welcome message:" welcomeWithLink <- getTermLine bob cath ##> "/set welcome #privacy Welcome!" - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## "Welcome!" bob <## "cath updated group #privacy:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## "Welcome!" bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message by cath." bob <## "" @@ -857,10 +857,10 @@ testAnotherOwnerRemovedLink ps = superUser <# "SimpleX-Directory> The group link is removed from ID 1 (privacy), de-listed." groupNotFound cath "privacy" cath ##> ("/set welcome #privacy " <> welcomeWithLink) - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## welcomeWithLink bob <## "cath updated group #privacy:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## welcomeWithLink bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message by cath." bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." @@ -881,10 +881,10 @@ testNotConnectedOwnerRemovedLink ps = bob <## "Welcome message:" welcomeWithLink <- getTermLine bob cath ##> "/set welcome #privacy Welcome!" - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## "Welcome!" bob <## "cath updated group #privacy:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## "Welcome!" bob <# "SimpleX-Directory> The group link for ID 1 (privacy) is removed from the welcome message by cath." bob <## "" @@ -892,21 +892,21 @@ testNotConnectedOwnerRemovedLink ps = superUser <# "SimpleX-Directory> The group link is removed from ID 1 (privacy), de-listed." groupNotFound dan "privacy" cath ##> ("/set welcome #privacy " <> welcomeWithLink) - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## welcomeWithLink bob <## "cath updated group #privacy:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## welcomeWithLink -- bob <# "SimpleX-Directory> The group link is added by another group member, your registration will not be processed." -- bob <## "" -- bob <## "Please update the group profile yourself." -- bob ##> ("/set welcome #privacy " <> welcomeWithLink <> " - welcome!") - -- bob <## "description changed to:" + -- bob <## "welcome message changed to:" -- bob <## (welcomeWithLink <> " - welcome!") bob <# "SimpleX-Directory> Thank you! The group link for ID 1 (privacy) is added to the welcome message by cath." bob <## "You will be notified once the group is added to the directory - it may take up to 48 hours." -- cath <## "bob updated group #privacy:" - -- cath <## "description changed to:" + -- cath <## "welcome message changed to:" -- cath <## (welcomeWithLink <> " - welcome!") reapproveGroup 3 superUser bob groupFoundN 3 dan "privacy" @@ -980,7 +980,7 @@ testDuplicateProhibitWhenUpdated ps = completeRegistration superUser bob "privacy" "Privacy" welcomeWithLink 1 groupFound cath "privacy" cath ##> ("/set welcome privacy " <> welcomeWithLink') - cath <## "description changed to:" + cath <## "welcome message changed to:" cath <## welcomeWithLink' cath <# "SimpleX-Directory> The group privacy (Privacy) is already listed in the directory, please choose another name." cath ##> "/gp privacy security Security" @@ -1352,7 +1352,7 @@ completeRegistrationId su u n fn welcomeWithLink gId ugId = do updateProfileWithLink :: TestCC -> String -> String -> Int -> IO () updateProfileWithLink u n welcomeWithLink ugId = do u ##> ("/set welcome " <> viewName n <> " " <> welcomeWithLink) - u <## "description changed to:" + u <## "welcome message changed to:" u <## welcomeWithLink u <# ("SimpleX-Directory> Thank you! The group link for ID " <> show ugId <> " (" <> n <> ") is added to the welcome message.") u <## "You will be notified once the group is added to the directory - it may take up to 48 hours." diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 9d69b4fc27..56dd863b2d 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -353,7 +353,7 @@ testGroupShared alice bob cath checkMessages = do alice #$> ("/_get chat #1 around=" <> msgItem1 <> " count=2", chat, [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) alice #$> ("/_get chat #1 count=100 search=team", chat, [(0, "hey team")]) bob @@@ [("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] - bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "added cath (Catherine)"), (0, "connected"), (0, "hello"), (1, "hi there"), (0, "hey team")]) + bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "added cath"), (0, "connected"), (0, "hello"), (1, "hi there"), (0, "hey team")]) cath @@@ [("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] cath #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "connected"), (0, "hello"), (0, "hi there"), (1, "hey team")]) alice #$> ("/_read chat #1", id, "ok") @@ -586,7 +586,7 @@ testGroup2 = <> [ "#club bob> connected", "#club cath> connected", - "#club bob> added dan (Daniel)", + "#club bob> added dan", "#club dan> connected", "#club hello", "#club bob> hi there", @@ -1466,6 +1466,26 @@ testUpdateGroupProfile = concurrently_ (alice <# "#my_team bob> hi") (cath <# "#my_team bob> hi") + alice ##> "/gp my_team my_team My team" + alice <## "description changed to: My team" + concurrentlyN_ + [ do + bob <## "alice updated group #my_team:" + bob <## "description changed to: My team", + do + cath <## "alice updated group #my_team:" + cath <## "description changed to: My team" + ] + alice ##> "/gp my_team my_team My team updated" + alice <## "description changed to: My team updated" + concurrentlyN_ + [ do + bob <## "alice updated group #my_team:" + bob <## "description changed to: My team updated", + do + cath <## "alice updated group #my_team:" + cath <## "description changed to: My team updated" + ] testUpdateMemberRole :: HasCallStack => TestParams -> IO () testUpdateMemberRole = @@ -1525,15 +1545,15 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile alice ##> "/group_profile team" alice <## "#team" groupInfo' alice - alice ##> "/group_descr team Welcome to the team!" - alice <## "description changed to:" + alice ##> "/set welcome team Welcome to the team!" + alice <## "welcome message changed to:" alice <## "Welcome to the team!" bob <## "alice updated group #team:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## "Welcome to the team!" alice ##> "/group_profile team" alice <## "#team" - alice <## "description:" + alice <## "welcome message:" alice <## "Welcome to the team!" groupInfo' alice connectUsers alice cath @@ -1845,9 +1865,9 @@ testDeleteMemberWithMessages = bob <## "#team: alice removed you from the group with all messages" bob <## "use /d #team to delete the group" cath <## "#team: alice removed bob from the group with all messages" - alice #$> ("/_get chat #1 count=2", chat, [(0, "moderated [deleted by you]"), (1, "removed bob (Bob)")]) + alice #$> ("/_get chat #1 count=2", chat, [(0, "moderated [deleted by you]"), (1, "removed bob")]) bob #$> ("/_get chat #1 count=2", chat, [(1, "moderated [deleted by alice]"), (0, "removed you")]) - cath #$> ("/_get chat #1 count=2", chat, [(0, "moderated [deleted by alice]"), (0, "removed bob (Bob)")]) + cath #$> ("/_get chat #1 count=2", chat, [(0, "moderated [deleted by alice]"), (0, "removed bob")]) testDeleteMemberMarkMessagesDeleted :: HasCallStack => TestParams -> IO () testDeleteMemberMarkMessagesDeleted = @@ -1868,9 +1888,9 @@ testDeleteMemberMarkMessagesDeleted = bob <## "#team: alice removed you from the group with all messages" bob <## "use /d #team to delete the group" cath <## "#team: alice removed bob from the group with all messages" - alice #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by you]"), (1, "removed bob (Bob)")]) + alice #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by you]"), (1, "removed bob")]) bob #$> ("/_get chat #1 count=2", chat, [(1, "hello [marked deleted by alice]"), (0, "removed you")]) - cath #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by alice]"), (0, "removed bob (Bob)")]) + cath #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by alice]"), (0, "removed bob")]) testSendMulti :: HasCallStack => TestParams -> IO () testSendMulti = @@ -5564,11 +5584,11 @@ testGroupHistoryWelcomeMessage = createGroup2 "team" alice bob alice ##> "/set welcome #team welcome to team" - alice <## "description changed to:" + alice <## "welcome message changed to:" alice <## "welcome to team" bob <## "alice updated group #team:" - bob <## "description changed to:" + bob <## "welcome message changed to:" bob <## "welcome to team" threadDelay 1000000 @@ -6192,10 +6212,10 @@ testBlockForAllMarkedBlocked = #$> ( "/_get chat #1 count=6", chat, [ (0, "1"), - (1, "blocked bob (Bob)"), + (1, "blocked bob"), (0, "2 [blocked by admin]"), (0, "3 [blocked by admin]"), - (1, "unblocked bob (Bob)"), + (1, "unblocked bob"), (0, "4") ] ) @@ -6203,10 +6223,10 @@ testBlockForAllMarkedBlocked = #$> ( "/_get chat #1 count=6", chat, [ (0, "1"), - (0, "blocked bob (Bob)"), + (0, "blocked bob"), (0, "2 [blocked by admin]"), (0, "3 [blocked by admin]"), - (0, "unblocked bob (Bob)"), + (0, "unblocked bob"), (0, "4") ] ) @@ -6273,10 +6293,10 @@ testBlockForAllFullDelete = #$> ( "/_get chat #1 count=6", chat, [ (0, "1"), - (1, "blocked bob (Bob)"), + (1, "blocked bob"), (0, "blocked [blocked by admin]"), (0, "blocked [blocked by admin]"), - (1, "unblocked bob (Bob)"), + (1, "unblocked bob"), (0, "4") ] ) @@ -6284,10 +6304,10 @@ testBlockForAllFullDelete = #$> ( "/_get chat #1 count=6", chat, [ (0, "1"), - (0, "blocked bob (Bob)"), + (0, "blocked bob"), (0, "blocked [blocked by admin]"), (0, "blocked [blocked by admin]"), - (0, "unblocked bob (Bob)"), + (0, "unblocked bob"), (0, "4") ] ) @@ -6380,7 +6400,7 @@ testBlockForAllBeforeJoining = dan ##> "/_get chat #1 count=100" r <- chat <$> getTermLine dan - r `shouldContain` [(0, "3 [blocked by admin]"), (0, "4 [blocked by admin]"), (0, "unblocked bob (Bob)"), (0, "5")] + r `shouldContain` [(0, "3 [blocked by admin]"), (0, "4 [blocked by admin]"), (0, "unblocked bob"), (0, "5")] r `shouldNotContain` [(0, "1")] r `shouldNotContain` [(0, "1 [blocked by admin]")] r `shouldNotContain` [(0, "2")] diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index a5fb31ad77..38d8220a9e 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -66,7 +66,7 @@ testUserNotes ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do alice ##> "/create user secret" alice <## "user profile: secret" - alice <## "use /p to change it" + alice <## "use /p [] to change it" alice <## "(the updated profile will be sent to all your contacts)" alice ##> "/tail" diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index eaaabf5b15..e16ffc4929 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -156,19 +156,19 @@ testUpdateProfile = connectUsers bob cath alice ##> "/p" alice <## "user profile: alice (Alice)" - alice <## "use /p to change it" + alice <## "use /p [] to change it" alice <## "(the updated profile will be sent to all your contacts)" alice ##> "/p alice" concurrentlyN_ - [ alice <## "user full name removed (your 2 contacts are notified)", - bob <## "contact alice removed full name", - cath <## "contact alice removed full name" + [ alice <## "user bio removed (your 2 contacts are notified)", + bob <## "contact alice removed bio", + cath <## "contact alice removed bio" ] alice ##> "/p alice Alice Jones" concurrentlyN_ - [ alice <## "user full name changed to Alice Jones (your 2 contacts are notified)", - bob <## "contact alice updated full name: Alice Jones", - cath <## "contact alice updated full name: Alice Jones" + [ alice <## "user bio changed to Alice Jones (your 2 contacts are notified)", + bob <## "contact alice updated bio: Alice Jones", + cath <## "contact alice updated bio: Alice Jones" ] cath ##> "/p cate" concurrentlyN_ @@ -282,7 +282,7 @@ testMultiWordProfileNames = aliceProfile' = baseProfile {displayName = "Alice Jones"} bobProfile' = baseProfile {displayName = "Bob James"} cathProfile' = baseProfile {displayName = "Cath Johnson"} - baseProfile = Profile {displayName = "", fullName = "", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} + baseProfile = Profile {displayName = "", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = defaultPrefs} testUserContactLink :: HasCallStack => TestParams -> IO () testUserContactLink = @@ -577,7 +577,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile alice @@@ [("@bob", "Audio/video calls: enabled")] bob ##> "/p bob" - bob <## "user full name removed (your 0 contacts are notified)" + bob <## "user bio removed (your 0 contacts are notified)" bob ##> ("/c " <> cLink) bob <## "connection request sent!" alice <## "bob wants to connect to you!" @@ -586,7 +586,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile alice @@@ [("@bob", "Audio/video calls: enabled")] bob ##> "/p bob Bob Ross" - bob <## "user full name changed to Bob Ross (your 0 contacts are notified)" + bob <## "user bio changed to Bob Ross (your 0 contacts are notified)" bob ##> ("/c " <> cLink) alice <#? bob alice @@@ [("@bob", "Audio/video calls: enabled")] @@ -1362,8 +1362,8 @@ testConnectIncognitoInvitationLink = testChat3 aliceProfile bobProfile cathProfi -- bob is not notified on profile change alice ##> "/p alice" concurrentlyN_ - [ alice <## "user full name removed (your 1 contacts are notified)", - cath <## "contact alice removed full name" + [ alice <## "user bio removed (your 1 contacts are notified)", + cath <## "contact alice removed bio" ] alice ?#> ("@" <> bobIncognito <> " do you see that I've changed profile?") bob ?<# (aliceIncognito <> "> do you see that I've changed profile?") @@ -1852,13 +1852,13 @@ testCantSeeGlobalPrefsUpdateIncognito = testChat3 aliceProfile bobProfile cathPr ] alice <## "cath (Catherine): contact is connected" alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" - alice <## "user full name removed (your 1 contacts are notified)" + alice <## "user bio removed (your 1 contacts are notified)" alice <## "updated preferences:" alice <## "Full deletion allowed: always" (alice "/_profile 1 {\"displayName\": \"bob\", \"fullName\": \"Bob\", \"preferences\": {\"voice\": {\"allow\": \"no\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" + bob ##> "/_profile 1 {\"displayName\": \"bob\", \"fullName\": \"\", \"shortDescr\": \"Bob\", \"preferences\": {\"voice\": {\"allow\": \"no\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" bob <## "profile image removed" bob <## "updated preferences:" bob <## "Voice messages allowed: no" @@ -2270,12 +2270,12 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ bob #$> ("/_get chat @2 count=100", chat, startFeatures <> [(0, "Voice messages: enabled for you"), (1, "voice message (00:10)"), (0, "Voice messages: off")]) (bob "/_profile 1 {\"displayName\": \"bob\", \"fullName\": \"\", \"preferences\": {\"voice\": {\"allow\": \"yes\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" - bob <## "user full name removed (your 1 contacts are notified)" + bob <## "user bio removed (your 1 contacts are notified)" bob <## "updated preferences:" 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 ("/_get chat @2 count=100", chat, startFeatures <> [(1, "Voice messages: enabled for contact"), (0, "voice message (00:10)"), (1, "Voice messages: off"), (0, "updated profile"), (0, "Voice messages: enabled")]) @@ -2596,7 +2596,7 @@ testUpdateMultipleUserPrefs = testChat3 aliceProfile bobProfile cathProfile $ alice #> "@cath hi cath" cath <# "alice> hi cath" - alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"Alice\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}, \"reactions\": {\"allow\": \"no\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" + alice ##> "/_profile 1 {\"displayName\": \"alice\", \"fullName\": \"\", \"shortDescr\": \"Alice\", \"preferences\": {\"fullDelete\": {\"allow\": \"always\"}, \"reactions\": {\"allow\": \"no\"}, \"receipts\": {\"allow\": \"yes\", \"activated\": true}}}" alice <## "updated preferences:" alice <## "Full deletion allowed: always" alice <## "Message reactions allowed: no" @@ -2786,7 +2786,7 @@ testSetUITheme = theme cm = T.unpack $ encodeJSON UIThemeEntityOverrides {light = Nothing, dark = Just $ UIThemeEntityOverride cm Nothing defaultUIColors} userInfo a name = do a <## ("user profile: " <> name) - a <## "use /p to change it" + a <## "use /p [] to change it" a <## "(the updated profile will be sent to all your contacts)" contactInfo a = do a <## "contact ID: 2" @@ -2984,7 +2984,7 @@ testShortLinkInvitationPrepareContact ps@TestParams {largeLinkData} = testChatCf ] alice <# "bob> hello" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -3035,9 +3035,12 @@ testShortLinkInvitationLongName = testChatCfg2 testCfg {largeLinkData = False} a WithTime ("@" <> longName <> " hello") ] bob <# "alice> hello" - concurrently_ - (alice <## (longName <> ": contact is connected")) - (bob <## "alice (Alice): contact is connected") + concurrentlyN_ + [ do + alice <## ("contact " <> longName <> " updated bio: Bob") + alice <## (longName <> " (Bob): contact is connected"), + (bob <## "alice (Alice): contact is connected") + ] bob <##> alice where longName = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" @@ -3070,7 +3073,7 @@ testShortLinkInvitationConnectRetry ps@TestParams {largeLinkData} = testChatCfgO ] alice <# "bob> hello" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -3120,7 +3123,7 @@ testShortLinkAddressPrepareContact ps@TestParams {largeLinkData} = testChatCfg2 alice ##> "/ac bob" alice <## "bob (Bob): accepting contact request, you can send messages to contact" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -3208,7 +3211,7 @@ testShortLinkAddressConnectRetry ps@TestParams {largeLinkData} = alice ##> "/ac bob" alice <## "bob (Bob): accepting contact request, you can send messages to contact" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -3266,7 +3269,7 @@ testShortLinkAddressConnectRetryIncognito ps@TestParams {largeLinkData} = alice ##> ("/ac " <> bobIncognito) alice <## (bobIncognito <> ": accepting contact request, you can send messages to contact") unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrentlyN_ [ do _ <- getTermLine bob @@ -3492,7 +3495,7 @@ testGroupShortLinkWelcome ps@TestParams {largeLinkData} = testChatCfg2 testCfg { alice <## "group #team is created" alice <## "to add members use /a team or /create link #team" alice ##> "/set welcome #team Welcome!" - alice <## "description changed to:" + alice <## "welcome message changed to:" alice <## "Welcome!" alice ##> "/create link #team" (shortLink, fullLink) <- getGroupLinks alice "team" GRMember True @@ -3593,7 +3596,7 @@ testShortLinkInvitationConnectPreparedContactIncognito ps@TestParams {largeLinkD bobIncognito <- getTermLine bob bob <## "alice: connection started incognito" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" _ <- getTermLine bob concurrentlyN_ [ alice <## (bobIncognito <> ": contact is connected"), @@ -3626,7 +3629,7 @@ testShortLinkAddressConnectPreparedContactIncognito ps@TestParams {largeLinkData alice ##> ("/ac " <> bobIncognito) alice <## (bobIncognito <> ": accepting contact request, you can send messages to contact") unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" _ <- getTermLine bob concurrentlyN_ [ alice <## (bobIncognito <> ": contact is connected"), @@ -3670,7 +3673,7 @@ testShortLinkChangePreparedContactUser ps@TestParams {largeLinkData} = testChatC ] alice <# "robert> hello" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "robert: contact is connected") @@ -3724,7 +3727,7 @@ testShortLinkChangePreparedContactUserDuplicate ps@TestParams {largeLinkData} = ] alice <# "robert_1> hello" unless largeLinkData $ - bob <## "contact alice_1 updated full name: Alice" + bob <## "contact alice_1 updated bio: Alice" concurrently_ (bob <## "alice_1 (Alice): contact is connected") (alice <## "robert_1: contact is connected") @@ -4087,7 +4090,7 @@ testShortLinkAddressChangeAutoReply ps@TestParams {largeLinkData} = testChatCfg3 alice <## "bob (Bob): accepting contact request..." alice <## "bob (Bob): you can send messages to contact" unless largeLinkData $ - bob <## "contact alice updated full name: Alice" + bob <## "contact alice updated bio: Alice" concurrently_ (bob <### (["alice (Alice): contact is connected"] <> [WithTime "alice> welcome!" | not largeLinkData])) (alice <### (["bob (Bob): contact is connected"] <> [WithTime "@bob welcome!" | not largeLinkData])) @@ -4109,7 +4112,7 @@ testShortLinkAddressChangeAutoReply ps@TestParams {largeLinkData} = testChatCfg3 alice <## "cath (Catherine): accepting contact request..." alice <## "cath (Catherine): you can send messages to contact" unless largeLinkData $ - cath <## "contact alice updated full name: Alice" + cath <## "contact alice updated bio: Alice" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 5dffb970d7..72c0205883 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -57,25 +57,28 @@ defaultPrefs :: Maybe Preferences defaultPrefs = Just $ toChatPrefs defaultChatPrefs aliceDesktopProfile :: Profile -aliceDesktopProfile = Profile {displayName = "alice_desktop", fullName = "Alice Desktop", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +aliceDesktopProfile = mkProfile "alice_desktop" "Alice Desktop" Nothing aliceProfile :: Profile -aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +aliceProfile = mkProfile "alice" "Alice" Nothing bobProfile :: Profile -bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,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"), contactLink = Nothing, preferences = defaultPrefs} +bobProfile = mkProfile "bob" "Bob" $ Just $ ImageData "data:image/png;base64,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" cathProfile :: Profile -cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +cathProfile = mkProfile "cath" "Catherine" Nothing danProfile :: Profile -danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +danProfile = mkProfile "dan" "Daniel" Nothing eveProfile :: Profile -eveProfile = Profile {displayName = "eve", fullName = "Eve", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +eveProfile = mkProfile "eve" "Eve" Nothing businessProfile :: Profile -businessProfile = Profile {displayName = "biz", fullName = "Biz Inc", image = Nothing, contactLink = Nothing, preferences = defaultPrefs} +businessProfile = mkProfile "biz" "Biz Inc" Nothing + +mkProfile :: T.Text -> T.Text -> Maybe ImageData -> Profile +mkProfile displayName descr image = Profile {displayName, fullName = "", shortDescr = Just descr, image, contactLink = Nothing, preferences = defaultPrefs} it :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation)) it name test = @@ -685,7 +688,7 @@ lastItemId cc = do showActiveUser :: HasCallStack => TestCC -> String -> Expectation showActiveUser cc name = do cc <## ("user profile: " <> name) - cc <## "use /p to change it" + cc <## "use /p [] to change it" cc <## "(the updated profile will be sent to all your contacts)" connectUsersNoShortLink :: HasCallStack => TestCC -> TestCC -> IO () @@ -710,8 +713,8 @@ connectUsers_ cc1 cc2 noShortLink = do showName :: TestCC -> IO String showName (TestCC ChatController {currentUser} _ _ _ _ _) = do - Just User {localDisplayName, profile = LocalProfile {fullName}} <- readTVarIO currentUser - pure . T.unpack $ localDisplayName <> optionalFullName localDisplayName fullName + Just User {localDisplayName, profile = LocalProfile {fullName, shortDescr}} <- readTVarIO currentUser + pure . T.unpack $ localDisplayName <> optionalFullName localDisplayName fullName shortDescr createGroup2 :: HasCallStack => String -> TestCC -> TestCC -> IO () createGroup2 gName cc1 cc2 = createGroup2' gName cc1 (cc2, GRAdmin) True diff --git a/tests/JSONFixtures.hs b/tests/JSONFixtures.hs index bd74f44022..99157b127f 100644 --- a/tests/JSONFixtures.hs +++ b/tests/JSONFixtures.hs @@ -17,10 +17,10 @@ activeUserExistsTagged :: LB.ByteString activeUserExistsTagged = "{\"error\":{\"type\":\"error\",\"errorType\":{\"type\":\"userExists\",\"contactName\":\"alice\"}}}" activeUserSwift :: LB.ByteString -activeUserSwift = "{\"result\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" +activeUserSwift = "{\"result\":{\"_owsf\":true,\"activeUser\":{\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"\",\"shortDescr\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}}" activeUserTagged :: LB.ByteString -activeUserTagged = "{\"result\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" +activeUserTagged = "{\"result\":{\"type\":\"activeUser\",\"user\":{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"\",\"shortDescr\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}}}" chatStartedSwift :: LB.ByteString chatStartedSwift = "{\"result\":{\"_owsf\":true,\"chatStarted\":{}}}" @@ -35,7 +35,7 @@ networkStatusesTagged :: LB.ByteString networkStatusesTagged = "{\"result\":{\"type\":\"networkStatuses\",\"user_\":" <> userJSON <> ",\"networkStatuses\":[]}}" userJSON :: LB.ByteString -userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" +userJSON = "{\"userId\":1,\"agentUserId\":\"1\",\"userContactId\":1,\"localDisplayName\":\"alice\",\"profile\":{\"profileId\":1,\"displayName\":\"alice\",\"fullName\":\"\",\"shortDescr\":\"Alice\",\"localAlias\":\"\"},\"fullPreferences\":{\"timedMessages\":{\"allow\":\"yes\"},\"fullDelete\":{\"allow\":\"no\"},\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"},\"calls\":{\"allow\":\"yes\"}},\"activeUser\":true,\"activeOrder\":1,\"showNtfs\":true,\"sendRcptsContacts\":true,\"sendRcptsSmallGroups\":true}" memberSubSummarySwift :: LB.ByteString memberSubSummarySwift = "{\"result\":{\"_owsf\":true,\"memberSubSummary\":{\"user\":" <> userJSON <> ",\"memberSubscriptions\":[]}}}" diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 7cc27b3df5..8e0c937358 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -104,10 +104,10 @@ testGroupPreferences :: Maybe GroupPreferences testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing} testProfile :: Profile -testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} +testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} testGroupProfile :: GroupProfile -testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, image = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing} +testGroupProfile = GroupProfile {displayName = "team", fullName = "Team", description = Nothing, shortDescr = Nothing, image = Nothing, groupPreferences = testGroupPreferences, memberAdmission = Nothing} decodeChatMessageTest :: Spec decodeChatMessageTest = describe "Chat message encoding/decoding" $ do @@ -223,7 +223,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do #==# XInfo testProfile it "x.info with empty full name" $ "{\"v\":\"1\",\"event\":\"x.info\",\"params\":{\"profile\":{\"fullName\":\"\",\"displayName\":\"alice\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" - #==# XInfo Profile {displayName = "alice", fullName = "", image = Nothing, contactLink = Nothing, preferences = testChatPreferences} + #==# XInfo Profile {displayName = "alice", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, preferences = testChatPreferences} it "x.contact with xContactId" $ "{\"v\":\"1\",\"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 (Just $ XContactId "\1\2\3\4") Nothing Nothing diff --git a/tests/RemoteTests.hs b/tests/RemoteTests.hs index f18b097e9c..f869c68227 100644 --- a/tests/RemoteTests.hs +++ b/tests/RemoteTests.hs @@ -448,7 +448,7 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat desktop ##> "/create user desk_bottom" desktop <## "user profile: desk_bottom" - desktop <## "use /p to change it" + desktop <## "use /p [] to change it" desktop <## "(the updated profile will be sent to all your contacts)" desktop ##> "/users" desktop <## "alice_desktop (Alice Desktop)" @@ -461,7 +461,7 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat desktop ##> "/create user alt_alice" desktop <## "user profile: alt_alice" - desktop <## "use /p to change it" + desktop <## "use /p [] to change it" desktop <## "(the updated profile will be sent to all your contacts)" desktop ##> "/users" @@ -470,7 +470,7 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat desktop ##> "/user" desktop <## "user profile: alt_alice" - desktop <## "use /p to change it" + desktop <## "use /p [] to change it" desktop <## "(the updated profile will be sent to all your contacts)" bob #> "@alice hi" @@ -483,7 +483,7 @@ multipleProfilesTest = testChat4 aliceProfile aliceDesktopProfile bobProfile cat desktop <## "Using local profile" desktop ##> "/user" desktop <## "user profile: desk_bottom" - desktop <## "use /p to change it" + desktop <## "use /p [] to change it" desktop <## "(the updated profile will be sent to all your contacts)" bob #> "@alice hey"