diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 7a625bae63..aef8711f34 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -15,6 +15,12 @@ import SimpleXChat private var chatController: chat_ctrl? +// currentChatVersion in core +public let CURRENT_CHAT_VERSION: Int = 2 + +// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core) +public let CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion: 2, maxVersion: CURRENT_CHAT_VERSION) + enum TerminalItem: Identifiable { case cmd(Date, ChatCommand) case resp(Date, ChatResponse) @@ -1090,6 +1096,18 @@ func apiGetGroupLink(_ groupId: Int64) throws -> (String, GroupMemberRole)? { } } +func apiCreateMemberContact(_ groupId: Int64, _ groupMemberId: Int64) async throws -> Contact { + let r = await chatSendCmd(.apiCreateMemberContact(groupId: groupId, groupMemberId: groupMemberId)) + if case let .newMemberContact(_, contact, _, _) = r { return contact } + throw r +} + +func apiSendMemberContactInvitation(_ contactId: Int64, _ msg: MsgContent) async throws -> Contact { + let r = await chatSendCmd(.apiSendMemberContactInvitation(contactId: contactId, msg: msg), bgDelay: msgDelay) + if case let .newMemberContactSentInv(_, contact, _, _) = r { return contact } + throw r +} + func apiGetVersion() throws -> CoreVersionInfo { let r = chatSendCmdSync(.showVersion) if case let .versionInfo(info, _, _) = r { return info } @@ -1487,6 +1505,12 @@ func processReceivedMsg(_ res: ChatResponse) async { m.updateGroup(groupInfo) } } + case let .newMemberContactReceivedInv(user, contact, _, _): + if active(user) { + await MainActor.run { + m.updateContact(contact) + } + } case let .rcvFileAccepted(user, aChatItem): // usually rcvFileAccepted is a response, but it's also an event for XFTP files auto-accepted from NSE await chatItemSimpleUpdate(user, aChatItem) case let .rcvFileStart(user, aChatItem): diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 3b0861feb1..e7a5978044 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -164,6 +164,7 @@ struct ChatInfoView: View { // synchronizeConnectionButtonForce() // } } + .disabled(!contact.ready) if let contactLink = contact.contactLink { Section { @@ -180,30 +181,33 @@ struct ChatInfoView: View { } } - Section("Servers") { - networkStatusRow() - .onTapGesture { - alert = .networkStatusAlert - } - if let connStats = connectionStats { - Button("Change receiving address") { - alert = .switchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } - || connStats.ratchetSyncSendProhibited - ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert + if contact.ready { + Section("Servers") { + networkStatusRow() + .onTapGesture { + alert = .networkStatusAlert + } + if let connStats = connectionStats { + Button("Change receiving address") { + alert = .switchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + !contact.ready + || connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } || connStats.ratchetSyncSendProhibited ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + || connStats.ratchetSyncSendProhibited + ) + } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }) } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }) } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift new file mode 100644 index 0000000000..a204d83f1e --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift @@ -0,0 +1,79 @@ +// +// CIMemberCreatedContactView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 19.09.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +struct CIMemberCreatedContactView: View { + var chatItem: ChatItem + + var body: some View { + HStack(alignment: .bottom, spacing: 0) { + switch chatItem.chatDir { + case let .groupRcv(groupMember): + if let contactId = groupMember.memberContactId { + memberCreatedContactView(openText: "Open") + .onTapGesture { + dismissAllSheets(animated: true) + DispatchQueue.main.async { + ChatModel.shared.chatId = "@\(contactId)" + } + } + } else { + memberCreatedContactView() + } + default: + EmptyView() + } + } + .padding(.leading, 6) + .padding(.bottom, 6) + .textSelection(.disabled) + } + + private func memberCreatedContactView(openText: LocalizedStringKey? = nil) -> some View { + var r = eventText() + if let openText { + r = r + + Text(openText) + .fontWeight(.medium) + .foregroundColor(.accentColor) + + Text(" ") + } + r = r + chatItem.timestampText + .fontWeight(.light) + .foregroundColor(.secondary) + return r.font(.caption) + } + + private func eventText() -> Text { + if let member = chatItem.memberDisplayName { + return Text(member + " " + chatItem.content.text + " ") + .fontWeight(.light) + .foregroundColor(.secondary) + } else { + return Text(chatItem.content.text + " ") + .fontWeight(.light) + .foregroundColor(.secondary) + } + } +} + +struct CIMemberCreatedContactView_Previews: PreviewProvider { + static var previews: some View { + let content = CIContent.rcvGroupEvent(rcvGroupEvent: .memberCreatedContact) + let chatItem = ChatItem( + chatDir: .groupRcv(groupMember: GroupMember.sampleData), + meta: CIMeta.getSample(1, .now, content.text, .rcvRead), + content: content, + quotedItem: nil, + file: nil + ) + CIMemberCreatedContactView(chatItem: chatItem) + } +} diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 5d816ac64a..5ec61a8c2b 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -74,6 +74,7 @@ struct ChatItemContentView: View { case let .rcvGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole) case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole) case .rcvGroupEvent(.memberConnected): CIEventView(eventText: membersConnectedItemText) + case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem) case .rcvGroupEvent: eventItemView() case .sndGroupEvent: eventItemView() case .rcvConnEvent: eventItemView() diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 2a0cd4f2c2..81a063dcfc 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -64,6 +64,7 @@ struct ChatView: View { Spacer(minLength: 0) + connectingText() ComposeView( chat: chat, composeState: $composeState, @@ -149,6 +150,7 @@ struct ChatView: View { HStack { if contact.allowsFeature(.calls) { callButton(contact, .audio, imageName: "phone") + .disabled(!contact.ready) } Menu { if contact.allowsFeature(.calls) { @@ -157,9 +159,11 @@ struct ChatView: View { } label: { Label("Video call", systemImage: "video") } + .disabled(!contact.ready) } searchButton() toggleNtfsButton(chat) + .disabled(!contact.ready) } label: { Image(systemName: "ellipsis") } @@ -313,6 +317,19 @@ struct ChatView: View { } .scaleEffect(x: 1, y: -1, anchor: .center) } + + @ViewBuilder private func connectingText() -> some View { + if case let .direct(contact) = chat.chatInfo, + !contact.ready, + !contact.nextSendGrpInv { + Text("connecting…") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top) + } else { + EmptyView() + } + } private func floatingButtons(_ proxy: ScrollViewProxy) -> some View { let counts = chatModel.unreadChatItemCounts(itemsInView: itemsInView) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index c999c9dca0..3328da8dbe 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -257,6 +257,9 @@ struct ComposeView: View { var body: some View { VStack(spacing: 0) { + if chat.chatInfo.contact?.nextSendGrpInv ?? false { + ContextInvitingContactMemberView() + } contextItemView() switch (composeState.editing, composeState.preview) { case (true, .filePreview): EmptyView() @@ -270,7 +273,7 @@ struct ComposeView: View { Image(systemName: "paperclip") .resizable() } - .disabled(composeState.attachmentDisabled || !chat.userCanSend) + .disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false)) .frame(width: 25, height: 25) .padding(.bottom, 12) .padding(.leading, 12) @@ -298,6 +301,7 @@ struct ComposeView: View { composeState.liveMessage = nil chatModel.removeLiveDummy() }, + nextSendGrpInv: chat.chatInfo.contact?.nextSendGrpInv ?? false, voiceMessageAllowed: chat.chatInfo.featureEnabled(.voice), showEnableVoiceMessagesAlert: chat.chatInfo.showEnableVoiceMessagesAlert, startVoiceMessageRecording: { @@ -617,7 +621,9 @@ struct ComposeView: View { if liveMessage != nil { composeState = composeState.copy(liveMessage: nil) } await sending() } - if case let .editingItem(ci) = composeState.contextItem { + if chat.chatInfo.contact?.nextSendGrpInv ?? false { + await sendMemberContactInvitation() + } else if case let .editingItem(ci) = composeState.contextItem { sent = await updateMessage(ci, live: live) } else if let liveMessage = liveMessage, liveMessage.sentMsg != nil { sent = await updateMessage(liveMessage.chatItem, live: live) @@ -669,6 +675,19 @@ struct ComposeView: View { await MainActor.run { composeState.inProgress = true } } + func sendMemberContactInvitation() async { + do { + let mc = checkLinkPreview() + let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc) + await MainActor.run { + self.chatModel.updateContact(contact) + } + } catch { + logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)") + AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))") + } + } + func updateMessage(_ ei: ChatItem, live: Bool) async -> ChatItem? { if let oldMsgContent = ei.content.msgContent { do { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift new file mode 100644 index 0000000000..acb4f6d3e1 --- /dev/null +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift @@ -0,0 +1,32 @@ +// +// ContextInvitingContactMemberView.swift +// SimpleX (iOS) +// +// Created by spaced4ndy on 18.09.2023. +// Copyright © 2023 SimpleX Chat. All rights reserved. +// + +import SwiftUI + +struct ContextInvitingContactMemberView: View { + @Environment(\.colorScheme) var colorScheme + + var body: some View { + HStack { + Image(systemName: "message") + .foregroundColor(.secondary) + Text("Send direct message to connect") + } + .padding(12) + .frame(minHeight: 50) + .frame(maxWidth: .infinity, alignment: .leading) + .background(colorScheme == .light ? sentColorLight : sentColorDark) + .padding(.top, 8) + } +} + +struct ContextInvitingContactMemberView_Previews: PreviewProvider { + static var previews: some View { + ContextInvitingContactMemberView() + } +} diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index 73c7286925..6eed51788e 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -17,6 +17,7 @@ struct SendMessageView: View { var sendLiveMessage: (() async -> Void)? = nil var updateLiveMessage: (() async -> Void)? = nil var cancelLiveMessage: (() -> Void)? = nil + var nextSendGrpInv: Bool = false var showVoiceMessageButton: Bool = true var voiceMessageAllowed: Bool = true var showEnableVoiceMessagesAlert: ChatInfo.ShowEnableVoiceMessagesAlert = .other @@ -118,7 +119,9 @@ struct SendMessageView: View { @ViewBuilder private func composeActionButtons() -> some View { let vmrs = composeState.voiceMessageRecordingState - if showVoiceMessageButton + if nextSendGrpInv { + inviteMemberContactButton() + } else if showVoiceMessageButton && composeState.message.isEmpty && !composeState.editing && composeState.liveMessage == nil @@ -162,6 +165,24 @@ struct SendMessageView: View { .padding([.top, .trailing], 4) } + private func inviteMemberContactButton() -> some View { + Button { + sendMessage(nil) + } label: { + Image(systemName: "arrow.up.circle.fill") + .resizable() + .foregroundColor(sendButtonColor) + .frame(width: sendButtonSize, height: sendButtonSize) + .opacity(sendButtonOpacity) + } + .disabled( + !composeState.sendEnabled || + composeState.inProgress + ) + .frame(width: 29, height: 29) + .padding([.bottom, .trailing], 4) + } + private func sendMessageButton() -> some View { Button { sendMessage(nil) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index 6842e93f05..5ec14f5be1 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -22,6 +22,7 @@ struct GroupMemberInfoView: View { @State private var connectToMemberDialog: Bool = false @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State private var justOpened = true + @State private var progressIndicator = false enum GroupMemberInfoViewAlert: Identifiable { case removeMemberAlert(mem: GroupMember) @@ -65,146 +66,154 @@ struct GroupMemberInfoView: View { } private func groupMemberInfoView() -> some View { - VStack { - List { - groupMemberInfoHeader(member) - .listRowBackground(Color.clear) + ZStack { + VStack { + List { + groupMemberInfoHeader(member) + .listRowBackground(Color.clear) - if member.memberActive { - Section { - if let contactId = member.memberContactId { - if let chat = knownDirectChat(contactId) { + if member.memberActive { + Section { + if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) { knownDirectChatButton(chat) } else if groupInfo.fullGroupPreferences.directMessages.on { - newDirectChatButton(contactId) + if let contactId = member.memberContactId { + newDirectChatButton(contactId) + } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { + createMemberContactButton() + } } + if let code = connectionCode { verifyCodeButton(code) } + if let connStats = connectionStats, + connStats.ratchetSyncAllowed { + synchronizeConnectionButton() + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } } - if let code = connectionCode { verifyCodeButton(code) } - if let connStats = connectionStats, - connStats.ratchetSyncAllowed { - synchronizeConnectionButton() - } -// } else if developerTools { -// synchronizeConnectionButtonForce() -// } } - } - if let contactLink = member.contactLink { - Section { - QRCode(uri: contactLink) - Button { - showShareSheet(items: [contactLink]) - } label: { - Label("Share address", systemImage: "square.and.arrow.up") - } - if let contactId = member.memberContactId { - if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on { + if let contactLink = member.contactLink { + Section { + QRCode(uri: contactLink) + Button { + showShareSheet(items: [contactLink]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + if let contactId = member.memberContactId { + if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on { + connectViaAddressButton(contactLink) + } + } else { connectViaAddressButton(contactLink) } - } else { - connectViaAddressButton(contactLink) + } header: { + Text("Address") + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") } - } header: { - Text("Address") - } footer: { - Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") } - } - Section("Member") { - infoRow("Group", groupInfo.displayName) + Section("Member") { + infoRow("Group", groupInfo.displayName) - if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { - Picker("Change role", selection: $newRole) { - ForEach(roles) { role in - Text(role.text) + if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { + Picker("Change role", selection: $newRole) { + ForEach(roles) { role in + Text(role.text) + } } + .frame(height: 36) + } else { + infoRow("Role", member.memberRole.text) + } + + // TODO invited by - need to get contact by contact id + if let conn = member.activeConn { + let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) + infoRow("Connection", connLevelDesc) } - .frame(height: 36) - } else { - infoRow("Role", member.memberRole.text) } - // TODO invited by - need to get contact by contact id - if let conn = member.activeConn { - let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) - infoRow("Connection", connLevelDesc) - } - } - - if let connStats = connectionStats { - Section("Servers") { - // TODO network connection status - Button("Change receiving address") { - alert = .switchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } - || connStats.ratchetSyncSendProhibited - ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert + if let connStats = connectionStats { + Section("Servers") { + // TODO network connection status + Button("Change receiving address") { + alert = .switchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } || connStats.ratchetSyncSendProhibited ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } + || connStats.ratchetSyncSendProhibited + ) + } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }) } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }) } - } - if member.canBeRemoved(groupInfo: groupInfo) { - Section { - removeMemberButton(member) + if member.canBeRemoved(groupInfo: groupInfo) { + Section { + removeMemberButton(member) + } } - } - if developerTools { - Section("For console") { - infoRow("Local name", member.localDisplayName) - infoRow("Database ID", "\(member.groupMemberId)") + if developerTools { + Section("For console") { + infoRow("Local name", member.localDisplayName) + infoRow("Database ID", "\(member.groupMemberId)") + } + } + } + .navigationBarHidden(true) + .onAppear { + if #unavailable(iOS 16) { + // this condition prevents re-setting picker + if !justOpened { return } + } + newRole = member.memberRole + do { + let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) + let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + member = mem + connectionStats = stats + connectionCode = code + } catch let error { + logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") + } + justOpened = false + } + .onChange(of: newRole) { _ in + if newRole != member.memberRole { + alert = .changeMemberRoleAlert(mem: member, role: newRole) } } } - .navigationBarHidden(true) - .onAppear { - if #unavailable(iOS 16) { - // this condition prevents re-setting picker - if !justOpened { return } - } - newRole = member.memberRole - do { - let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) - let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) - member = mem - connectionStats = stats - connectionCode = code - } catch let error { - logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") - } - justOpened = false - } - .onChange(of: newRole) { _ in - if newRole != member.memberRole { - alert = .changeMemberRoleAlert(mem: member, role: newRole) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .alert(item: $alert) { alertItem in + switch(alertItem) { + case let .removeMemberAlert(mem): return removeMemberAlert(mem) + case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem) + case .switchAddressAlert: return switchAddressAlert(switchMemberAddress) + case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress) + case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) }) + case let .connRequestSentAlert(type): return connReqSentAlert(type) + case let .error(title, error): return Alert(title: Text(title), message: Text(error)) + case let .other(alert): return alert } } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .alert(item: $alert) { alertItem in - switch(alertItem) { - case let .removeMemberAlert(mem): return removeMemberAlert(mem) - case let .changeMemberRoleAlert(mem, _): return changeMemberRoleAlert(mem) - case .switchAddressAlert: return switchAddressAlert(switchMemberAddress) - case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress) - case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) }) - case let .connRequestSentAlert(type): return connReqSentAlert(type) - case let .error(title, error): return Alert(title: Text(title), message: Text(error)) - case let .other(alert): return alert + + if progressIndicator { + ProgressView().scaleEffect(2) } } } @@ -260,6 +269,33 @@ struct GroupMemberInfoView: View { } } + func createMemberContactButton() -> some View { + Button { + progressIndicator = true + Task { + do { + let memberContact = try await apiCreateMemberContact(groupInfo.apiId, member.groupMemberId) + await MainActor.run { + progressIndicator = false + chatModel.addChat(Chat(chatInfo: .direct(contact: memberContact))) + dismissAllSheets(animated: true) + chatModel.chatId = memberContact.id + chatModel.setContactNetworkStatus(memberContact, .connected) + } + } catch let error { + logger.error("createMemberContactButton apiCreateMemberContact error: \(responseError(error))") + let a = getErrorAlert(error, "Error creating member contact") + await MainActor.run { + progressIndicator = false + alert = .error(title: a.title, error: a.message) + } + } + } + } label: { + Label("Send direct message", systemImage: "message") + } + } + private func groupMemberInfoHeader(_ mem: GroupMember) -> some View { VStack { ProfileImage(imageStr: mem.image, color: Color(uiColor: .tertiarySystemFill)) diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 025c765a72..e7580530b6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -49,11 +49,10 @@ struct ChatListNavLink: View { } @ViewBuilder private func contactNavLink(_ contact: Contact) -> some View { - let v = NavLinkPlain( + NavLinkPlain( tag: chat.chatInfo.id, selection: $chatModel.chatId, - label: { ChatPreviewView(chat: chat) }, - disabled: !contact.ready + label: { ChatPreviewView(chat: chat) } ) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() @@ -76,14 +75,6 @@ struct ChatListNavLink: View { .tint(.red) } .frame(height: rowHeights[dynamicTypeSize]) - - if contact.ready { - v - } else { - v.onTapGesture { - AlertManager.shared.showAlert(pendingContactAlert(chat, contact)) - } - } } @ViewBuilder private func groupNavLink(_ groupInfo: GroupInfo) -> some View { diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index eccf0b5b42..3ac8fada74 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -181,7 +181,11 @@ struct ChatPreviewView: View { switch (chat.chatInfo) { case let .direct(contact): if !contact.ready { - chatPreviewInfoText("connecting…") + if contact.nextSendGrpInv { + chatPreviewInfoText("send direct message") + } else { + chatPreviewInfoText("connecting…") + } } case let .group(groupInfo): switch (groupInfo.membership.memberStatus) { diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 97bc90919f..bbfd95e6bf 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1978,6 +1978,10 @@ Chyba při vytváření odkazu skupiny No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Chyba při vytváření profilu! @@ -2107,6 +2111,10 @@ Chyba odesílání e-mailu No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Chyba při odesílání zprávy @@ -3337,6 +3345,10 @@ Hlasové zprávy může odesílat pouze váš kontakt. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Otevřít nastavení @@ -4071,6 +4083,10 @@ Odeslat přímou zprávu No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Poslat mizící zprávu @@ -5613,6 +5629,10 @@ Servery SimpleX nevidí váš profil. připojeno No comment provided by engineer. + + connected directly + rcv group event chat item + connecting připojování @@ -6072,6 +6092,10 @@ Servery SimpleX nevidí váš profil. bezpečnostní kód změněn chat item text + + send direct message + No comment provided by engineer. + starting… začíná… diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index ba23f48543..114b7f3e73 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1979,6 +1979,10 @@ Fehler beim Erzeugen des Gruppen-Links No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Fehler beim Erstellen des Profils! @@ -2109,6 +2113,10 @@ Fehler beim Senden der eMail No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Fehler beim Senden der Nachricht @@ -3340,6 +3348,10 @@ Nur Ihr Kontakt kann Sprachnachrichten versenden. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Geräte-Einstellungen öffnen @@ -4075,6 +4087,10 @@ Direktnachricht senden No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Verschwindende Nachricht senden @@ -5625,6 +5641,10 @@ SimpleX-Server können Ihr Profil nicht einsehen. Verbunden No comment provided by engineer. + + connected directly + rcv group event chat item + connecting verbinde @@ -6086,6 +6106,10 @@ SimpleX-Server können Ihr Profil nicht einsehen. Sicherheitscode wurde geändert chat item text + + send direct message + No comment provided by engineer. + starting… Verbindung wird gestartet… diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 9fbc9ebd78..0aeeecfbe6 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1988,6 +1988,11 @@ Error creating group link No comment provided by engineer. + + Error creating member contact + Error creating member contact + No comment provided by engineer. + Error creating profile! Error creating profile! @@ -2118,6 +2123,11 @@ Error sending email No comment provided by engineer. + + Error sending member contact invitation + Error sending member contact invitation + No comment provided by engineer. + Error sending message Error sending message @@ -3350,6 +3360,11 @@ Only your contact can send voice messages. No comment provided by engineer. + + Open + Open + No comment provided by engineer. + Open Settings Open Settings @@ -4085,6 +4100,11 @@ Send direct message No comment provided by engineer. + + Send direct message to connect + Send direct message to connect + No comment provided by engineer. + Send disappearing message Send disappearing message @@ -5637,6 +5657,11 @@ SimpleX servers cannot see your profile. connected No comment provided by engineer. + + connected directly + connected directly + rcv group event chat item + connecting connecting @@ -6098,6 +6123,11 @@ SimpleX servers cannot see your profile. security code changed chat item text + + send direct message + send direct message + No comment provided by engineer. + starting… starting… diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 4657a938e5..85f02bba1a 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1978,6 +1978,10 @@ Error al crear enlace de grupo No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! ¡Error al crear perfil! @@ -2107,6 +2111,10 @@ Error al enviar email No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Error al enviar mensaje @@ -3338,6 +3346,10 @@ Sólo tu contacto puede enviar mensajes de voz. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Abrir Configuración @@ -4073,6 +4085,10 @@ Enviar mensaje directo No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Enviar mensaje temporal @@ -5624,6 +5640,10 @@ Los servidores de SimpleX no pueden ver tu perfil. conectado No comment provided by engineer. + + connected directly + rcv group event chat item + connecting conectando @@ -6085,6 +6105,10 @@ Los servidores de SimpleX no pueden ver tu perfil. código de seguridad cambiado chat item text + + send direct message + No comment provided by engineer. + starting… inicializando… diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index ebd1ed7746..c7e970f6ff 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1979,6 +1979,10 @@ Virhe ryhmälinkin luomisessa No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Virhe profiilin luomisessa! @@ -2109,6 +2113,10 @@ Virhe sähköpostin lähettämisessä No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Virhe viestin lähettämisessä @@ -3340,6 +3348,10 @@ Vain kontaktisi voi lähettää ääniviestejä. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Avaa Asetukset @@ -4075,6 +4087,10 @@ Lähetä yksityisviesti No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Lähetä katoava viesti @@ -5625,6 +5641,10 @@ SimpleX-palvelimet eivät näe profiiliasi. yhdistetty No comment provided by engineer. + + connected directly + rcv group event chat item + connecting yhdistää @@ -6086,6 +6106,10 @@ SimpleX-palvelimet eivät näe profiiliasi. turvakoodi on muuttunut chat item text + + send direct message + No comment provided by engineer. + starting… alkaa… diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 27d9b103d3..3960c54b26 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1979,6 +1979,10 @@ Erreur lors de la création du lien du groupe No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Erreur lors de la création du profil ! @@ -2109,6 +2113,10 @@ Erreur lors de l'envoi de l'e-mail No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Erreur lors de l'envoi du message @@ -3340,6 +3348,10 @@ Seul votre contact peut envoyer des messages vocaux. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Ouvrir les Paramètres @@ -4075,6 +4087,10 @@ Envoi de message direct No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Envoyer un message éphémère @@ -5625,6 +5641,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. connecté No comment provided by engineer. + + connected directly + rcv group event chat item + connecting connexion @@ -6086,6 +6106,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. code de sécurité modifié chat item text + + send direct message + No comment provided by engineer. + starting… lancement… diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 758e66f93c..0a05bdedda 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1979,6 +1979,10 @@ Errore nella creazione del link del gruppo No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Errore nella creazione del profilo! @@ -2109,6 +2113,10 @@ Errore nell'invio dell'email No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Errore nell'invio del messaggio @@ -3340,6 +3348,10 @@ Solo il tuo contatto può inviare messaggi vocali. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Apri le impostazioni @@ -4075,6 +4087,10 @@ Invia messaggio diretto No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Invia messaggio a tempo @@ -5625,6 +5641,10 @@ I server di SimpleX non possono vedere il tuo profilo. connesso/a No comment provided by engineer. + + connected directly + rcv group event chat item + connecting in connessione @@ -6086,6 +6106,10 @@ I server di SimpleX non possono vedere il tuo profilo. codice di sicurezza modificato chat item text + + send direct message + No comment provided by engineer. + starting… avvio… diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 29acf9ddfa..27d7cb54f6 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1978,6 +1978,10 @@ グループリンク生成にエラー発生 No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! プロフィール作成にエラー発生! @@ -2107,6 +2111,10 @@ メールの送信にエラー発生 No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message メッセージ送信にエラー発生 @@ -3336,6 +3344,10 @@ 音声メッセージを送れるのはあなたの連絡相手だけです。 No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings 設定を開く @@ -4069,6 +4081,10 @@ ダイレクトメッセージを送信 No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message 消えるメッセージを送信 @@ -5611,6 +5627,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 接続中 No comment provided by engineer. + + connected directly + rcv group event chat item + connecting 接続待ち @@ -6072,6 +6092,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 セキュリティコードが変更されました chat item text + + send direct message + No comment provided by engineer. + starting… 接続中… diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index c1504a25ce..afa779f664 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1979,6 +1979,10 @@ Fout bij maken van groep link No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Fout bij aanmaken van profiel! @@ -2109,6 +2113,10 @@ Fout bij het verzenden van e-mail No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Fout bij verzenden van bericht @@ -3340,6 +3348,10 @@ Alleen uw contact kan spraak berichten verzenden. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Open instellingen @@ -4075,6 +4087,10 @@ Direct bericht sturen No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Stuur een verdwijnend bericht @@ -5625,6 +5641,10 @@ SimpleX servers kunnen uw profiel niet zien. verbonden No comment provided by engineer. + + connected directly + rcv group event chat item + connecting Verbinden @@ -6086,6 +6106,10 @@ SimpleX servers kunnen uw profiel niet zien. beveiligingscode gewijzigd chat item text + + send direct message + No comment provided by engineer. + starting… beginnen… diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index c17e89c916..d35d149336 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1979,6 +1979,10 @@ Błąd tworzenia linku grupy No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Błąd tworzenia profilu! @@ -2109,6 +2113,10 @@ Błąd wysyłania e-mail No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Błąd wysyłania wiadomości @@ -3340,6 +3348,10 @@ Tylko Twój kontakt może wysyłać wiadomości głosowe. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Otwórz Ustawienia @@ -4075,6 +4087,10 @@ Wyślij wiadomość bezpośrednią No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Wyślij znikającą wiadomość @@ -5625,6 +5641,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. połączony No comment provided by engineer. + + connected directly + rcv group event chat item + connecting łączenie @@ -6086,6 +6106,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. kod bezpieczeństwa zmieniony chat item text + + send direct message + No comment provided by engineer. + starting… uruchamianie… diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 1d0cf4e8ef..4a4431d60f 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1978,6 +1978,10 @@ Ошибка при создании ссылки группы No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Ошибка создания профиля! @@ -2107,6 +2111,10 @@ Ошибка отправки email No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Ошибка при отправке сообщения @@ -3338,6 +3346,10 @@ Только Ваш контакт может отправлять голосовые сообщения. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Открыть Настройки @@ -4073,6 +4085,10 @@ Отправить сообщение No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Отправить исчезающее сообщение @@ -5623,6 +5639,10 @@ SimpleX серверы не могут получить доступ к Ваше соединение установлено No comment provided by engineer. + + connected directly + rcv group event chat item + connecting соединяется @@ -6084,6 +6104,10 @@ SimpleX серверы не могут получить доступ к Ваше код безопасности изменился chat item text + + send direct message + No comment provided by engineer. + starting… инициализация… diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 439161a7f1..19681b3150 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1966,6 +1966,10 @@ เกิดข้อผิดพลาดในการสร้างลิงก์กลุ่ม No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! เกิดข้อผิดพลาดในการสร้างโปรไฟล์! @@ -2095,6 +2099,10 @@ เกิดข้อผิดพลาดในการส่งอีเมล No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message เกิดข้อผิดพลาดในการส่งข้อความ @@ -3322,6 +3330,10 @@ ผู้ติดต่อของคุณเท่านั้นที่สามารถส่งข้อความเสียงได้ No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings เปิดการตั้งค่า @@ -4054,6 +4066,10 @@ ส่งข้อความโดยตรง No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message ส่งข้อความแบบที่หายไป @@ -5595,6 +5611,10 @@ SimpleX servers cannot see your profile. เชื่อมต่อสำเร็จ No comment provided by engineer. + + connected directly + rcv group event chat item + connecting กำลังเชื่อมต่อ @@ -6054,6 +6074,10 @@ SimpleX servers cannot see your profile. เปลี่ยนรหัสความปลอดภัยแล้ว chat item text + + send direct message + No comment provided by engineer. + starting… กำลังเริ่มต้น… diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 9d289854a7..4947bf80c5 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1978,6 +1978,10 @@ Помилка створення посилання на групу No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! Помилка створення профілю! @@ -2107,6 +2111,10 @@ Помилка надсилання електронного листа No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message Помилка надсилання повідомлення @@ -3338,6 +3346,10 @@ Тільки ваш контакт може надсилати голосові повідомлення. No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings Відкрийте Налаштування @@ -4073,6 +4085,10 @@ Надішліть пряме повідомлення No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message Надіслати зникаюче повідомлення @@ -5623,6 +5639,10 @@ SimpleX servers cannot see your profile. з'єднаний No comment provided by engineer. + + connected directly + rcv group event chat item + connecting з'єднання @@ -6084,6 +6104,10 @@ SimpleX servers cannot see your profile. змінено код безпеки chat item text + + send direct message + No comment provided by engineer. + starting… починаючи… diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 08a446d2d1..304dac1d2c 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1979,6 +1979,10 @@ 创建群组链接错误 No comment provided by engineer. + + Error creating member contact + No comment provided by engineer. + Error creating profile! 创建资料错误! @@ -2109,6 +2113,10 @@ 发送电邮错误 No comment provided by engineer. + + Error sending member contact invitation + No comment provided by engineer. + Error sending message 发送消息错误 @@ -3340,6 +3348,10 @@ 只有您的联系人可以发送语音消息。 No comment provided by engineer. + + Open + No comment provided by engineer. + Open Settings 打开设置 @@ -4075,6 +4087,10 @@ 发送私信 No comment provided by engineer. + + Send direct message to connect + No comment provided by engineer. + Send disappearing message 发送限时消息中 @@ -5625,6 +5641,10 @@ SimpleX 服务器无法看到您的资料。 已连接 No comment provided by engineer. + + connected directly + rcv group event chat item + connecting 连接中 @@ -6086,6 +6106,10 @@ SimpleX 服务器无法看到您的资料。 安全密码已更改 chat item text + + send direct message + No comment provided by engineer. + starting… 启动中…… diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index cae3722e81..55b271ed09 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -153,6 +153,8 @@ 5CFE0921282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */; }; 6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; }; + 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; }; + 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; }; 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; }; 6440CA00288857A10062C672 /* CIEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440C9FF288857A10062C672 /* CIEventView.swift */; }; 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */; }; @@ -429,6 +431,8 @@ 5CFA59CF286477B400863A68 /* ChatArchiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatArchiveView.swift; sourceTree = ""; }; 5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; }; 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = ""; }; + 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = ""; }; + 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = ""; }; 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = ""; }; 6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = ""; }; 6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = ""; }; @@ -822,6 +826,7 @@ 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */, 18415FD2E36F13F596A45BB4 /* CIVideoView.swift */, 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */, + 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */, ); path = ChatItem; sourceTree = ""; @@ -837,6 +842,7 @@ 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */, 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */, D72A9087294BD7A70047C86D /* NativeTextEditor.swift */, + 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */, ); path = ComposeMessage; sourceTree = ""; @@ -1094,6 +1100,7 @@ 5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */, 5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */, 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */, + 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */, 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */, 5C029EAA283942EA004A9677 /* CallController.swift in Sources */, @@ -1155,6 +1162,7 @@ 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */, + 6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */, 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */, 64466DCC29FFE3E800E3D48D /* MailView.swift in Sources */, 5C971E2127AEBF8300C8A3CE /* ChatInfoImage.swift in Sources */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 635534fea0..e67a245382 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -61,6 +61,8 @@ public enum ChatCommand { case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole) case apiDeleteGroupLink(groupId: Int64) case apiGetGroupLink(groupId: Int64) + case apiCreateMemberContact(groupId: Int64, groupMemberId: Int64) + case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent) case apiGetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol) case apiSetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol, servers: [ServerCfg]) case apiTestProtoServer(userId: Int64, server: String) @@ -181,6 +183,8 @@ public enum ChatCommand { case let .apiGroupLinkMemberRole(groupId, memberRole): return "/_set link role #\(groupId) \(memberRole)" case let .apiDeleteGroupLink(groupId): return "/_delete link #\(groupId)" case let .apiGetGroupLink(groupId): return "/_get link #\(groupId)" + case let .apiCreateMemberContact(groupId, groupMemberId): return "/_create member contact #\(groupId) \(groupMemberId)" + case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)" case let .apiGetUserProtoServers(userId, serverProtocol): return "/_servers \(userId) \(serverProtocol)" case let .apiSetUserProtoServers(userId, serverProtocol, servers): return "/_servers \(userId) \(serverProtocol) \(protoServersStr(servers))" case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)" @@ -304,6 +308,8 @@ public enum ChatCommand { case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole" case .apiDeleteGroupLink: return "apiDeleteGroupLink" case .apiGetGroupLink: return "apiGetGroupLink" + case .apiCreateMemberContact: return "apiCreateMemberContact" + case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation" case .apiGetUserProtoServers: return "apiGetUserProtoServers" case .apiSetUserProtoServers: return "apiSetUserProtoServers" case .apiTestProtoServer: return "apiTestProtoServer" @@ -514,6 +520,9 @@ public enum ChatResponse: Decodable, Error { case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) case groupLink(user: UserRef, groupInfo: GroupInfo, connReqContact: String, memberRole: GroupMemberRole) case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo) + case newMemberContact(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case newMemberContactSentInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) + case newMemberContactReceivedInv(user: UserRef, contact: Contact, groupInfo: GroupInfo, member: GroupMember) // receiving file events case rcvFileAccepted(user: UserRef, chatItem: AChatItem) case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) @@ -647,6 +656,9 @@ public enum ChatResponse: Decodable, Error { case .groupLinkCreated: return "groupLinkCreated" case .groupLink: return "groupLink" case .groupLinkDeleted: return "groupLinkDeleted" + case .newMemberContact: return "newMemberContact" + case .newMemberContactSentInv: return "newMemberContactSentInv" + case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" case .rcvFileAccepted: return "rcvFileAccepted" case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" case .rcvFileStart: return "rcvFileStart" @@ -780,6 +792,9 @@ public enum ChatResponse: Decodable, Error { case let .groupLinkCreated(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") case let .groupLink(u, groupInfo, connReqContact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)\nmemberRole: \(memberRole)") case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo)) + case let .newMemberContact(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactSentInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") + case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) case .rcvFileAcceptedSndCancelled: return noDetails case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) @@ -1454,6 +1469,7 @@ public enum ChatErrorType: Decodable { case agentCommandError(message: String) case invalidFileDescription(message: String) case connectionIncognitoChangeProhibited + case peerChatVRangeIncompatible case internalError(message: String) case exception(message: String) } @@ -1479,6 +1495,7 @@ public enum StoreError: Decodable { case groupMemberNameNotFound(groupId: Int64, groupMemberName: ContactName) case groupMemberNotFound(groupMemberId: Int64) case groupMemberNotFoundByMemberId(memberId: String) + case memberContactGroupMemberNotFound(contactId: Int64) case groupWithoutUser case duplicateGroupMember case groupAlreadyJoined diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index ce8bd426cc..a24c82110c 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1378,11 +1378,17 @@ public struct Contact: Identifiable, Decodable, NamedChat { public var mergedPreferences: ContactUserPreferences var createdAt: Date var updatedAt: Date + var contactGroupMemberId: Int64? + var contactGrpInvSent: Bool public var id: ChatId { get { "@\(contactId)" } } public var apiId: Int64 { get { contactId } } public var ready: Bool { get { activeConn.connStatus == .ready } } - public var sendMsgEnabled: Bool { get { !(activeConn.connectionStats?.ratchetSyncSendProhibited ?? false) } } + public var sendMsgEnabled: Bool { get { + (ready && !(activeConn.connectionStats?.ratchetSyncSendProhibited ?? false)) + || nextSendGrpInv + } } + public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } } public var displayName: String { localAlias == "" ? profile.displayName : localAlias } public var fullName: String { get { profile.fullName } } public var image: String? { get { profile.image } } @@ -1428,7 +1434,8 @@ public struct Contact: Identifiable, Decodable, NamedChat { userPreferences: Preferences.sampleData, mergedPreferences: ContactUserPreferences.sampleData, createdAt: .now, - updatedAt: .now + updatedAt: .now, + contactGrpInvSent: false ) } @@ -1449,6 +1456,7 @@ public struct ContactSubStatus: Decodable { public struct Connection: Decodable { public var connId: Int64 public var agentConnId: String + public var peerChatVRange: VersionRange var connStatus: ConnStatus public var connLevel: Int public var viaGroupLink: Bool @@ -1458,7 +1466,7 @@ public struct Connection: Decodable { public var connectionStats: ConnectionStats? = nil private enum CodingKeys: String, CodingKey { - case connId, agentConnId, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode + case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode } public var id: ChatId { get { ":\(connId)" } } @@ -1466,12 +1474,27 @@ public struct Connection: Decodable { static let sampleData = Connection( connId: 1, agentConnId: "abc", + peerChatVRange: VersionRange(minVersion: 1, maxVersion: 1), connStatus: .ready, connLevel: 0, viaGroupLink: false ) } +public struct VersionRange: Decodable { + public init(minVersion: Int, maxVersion: Int) { + self.minVersion = minVersion + self.maxVersion = maxVersion + } + + public var minVersion: Int + public var maxVersion: Int + + public func isCompatibleRange(_ vRange: VersionRange) -> Bool { + self.minVersion <= vRange.maxVersion && vRange.minVersion <= self.maxVersion + } +} + public struct SecurityCode: Decodable, Equatable { public init(securityCode: String, verifiedAt: Date) { self.securityCode = securityCode @@ -1503,6 +1526,7 @@ public struct UserContact: Decodable { public struct UserContactRequest: Decodable, NamedChat { var contactRequestId: Int64 public var userContactLinkId: Int64 + public var cReqChatVRange: VersionRange var localDisplayName: ContactName var profile: Profile var createdAt: Date @@ -1520,6 +1544,7 @@ public struct UserContactRequest: Decodable, NamedChat { public static let sampleData = UserContactRequest( contactRequestId: 1, userContactLinkId: 1, + cReqChatVRange: VersionRange(minVersion: 1, maxVersion: 1), localDisplayName: "alice", profile: Profile.sampleData, createdAt: .now, @@ -2078,6 +2103,7 @@ public struct ChatItem: Identifiable, Decodable { case .memberLeft: return false case .memberDeleted: return false case .invitedViaGroupLink: return false + case .memberCreatedContact: return false } case .sndGroupEvent: return showNtfDir case .rcvConnEvent: return false @@ -3181,6 +3207,7 @@ public enum RcvGroupEvent: Decodable { case groupDeleted case groupUpdated(groupProfile: GroupProfile) case invitedViaGroupLink + case memberCreatedContact var text: String { switch self { @@ -3198,6 +3225,7 @@ public enum RcvGroupEvent: Decodable { case .groupDeleted: return NSLocalizedString("deleted group", comment: "rcv group event chat item") case .groupUpdated: return NSLocalizedString("updated group profile", comment: "rcv group event chat item") case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item") + case .memberCreatedContact: return NSLocalizedString("connected directly", comment: "rcv group event chat item") } } }