mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-30 20:45:49 +00:00
ios: Multiusers feature continue (#1793)
* ios: Multiusers feature continue * Logging of user in responses * UserId Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * Undo ugly user inclusion into functions. Now it's in backend * Do not set active user if it's unchanged * Blank line * if * Change active user function * refactor * refactor Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * Alert Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ba29d0242e
commit
ad6aa10cd2
@@ -50,7 +50,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
try await apiVerifyToken(token: token, nonce: nonce, code: verification)
|
||||
m.tokenStatus = .active
|
||||
} catch {
|
||||
if let cr = error as? ChatResponse, case .chatCmdError(.errorAgent(.NTF(.AUTH))) = cr {
|
||||
if let cr = error as? ChatResponse, case .chatCmdError(_, .errorAgent(.NTF(.AUTH))) = cr {
|
||||
m.tokenStatus = .expired
|
||||
}
|
||||
logger.error("AppDelegate: didReceiveRemoteNotification: apiVerifyToken or apiIntervalNofication error: \(responseError(error))")
|
||||
|
||||
@@ -16,7 +16,7 @@ final class ChatModel: ObservableObject {
|
||||
@Published var onboardingStage: OnboardingStage?
|
||||
@Published var v3DBMigration: V3DBMigrationState = v3DBMigrationDefault.get()
|
||||
@Published var currentUser: User?
|
||||
@Published var users: [UserInfo] = []
|
||||
@Published private(set) var users: [UserInfo] = []
|
||||
@Published var chatInitialized = false
|
||||
@Published var chatRunning: Bool?
|
||||
@Published var chatDbChanged = false
|
||||
@@ -177,6 +177,7 @@ final class ChatModel: ObservableObject {
|
||||
chats[i].chatItems = [cItem]
|
||||
if case .rcvNew = cItem.meta.itemStatus {
|
||||
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
|
||||
increaseUnreadCounter(user: currentUser!)
|
||||
NtfManager.shared.incNtfBadgeCount()
|
||||
}
|
||||
if i > 0 {
|
||||
@@ -344,6 +345,7 @@ final class ChatModel: ObservableObject {
|
||||
if markedCount > 0 {
|
||||
NtfManager.shared.decNtfBadgeCount(by: markedCount)
|
||||
chat.chatStats.unreadCount -= markedCount
|
||||
self.decreaseUnreadCounter(user: self.currentUser!, by: markedCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,6 +397,21 @@ final class ChatModel: ObservableObject {
|
||||
func decreaseUnreadCounter(_ cInfo: ChatInfo) {
|
||||
if let i = getChatIndex(cInfo.id) {
|
||||
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount - 1
|
||||
decreaseUnreadCounter(user: currentUser!)
|
||||
}
|
||||
}
|
||||
|
||||
func increaseUnreadCounter(user: User) {
|
||||
changeUnreadCounter(user: user, by: 1)
|
||||
}
|
||||
|
||||
func decreaseUnreadCounter(user: User, by: Int = 1) {
|
||||
changeUnreadCounter(user: user, by: -by)
|
||||
}
|
||||
|
||||
private func changeUnreadCounter(user: User, by: Int) {
|
||||
if let i = users.firstIndex(where: { $0.user.id == user.id }) {
|
||||
users[i].unreadCount += Int64(by)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,6 +497,31 @@ final class ChatModel: ObservableObject {
|
||||
while i < maxIx && inView(i) { i += 1 }
|
||||
return reversedChatItems[min(i - 1, maxIx)]
|
||||
}
|
||||
|
||||
func updateUsers(_ new: [UserInfo]) {
|
||||
users = new
|
||||
.sorted { $0.user.chatViewName.compare($1.user.chatViewName) == .orderedAscending }
|
||||
.sorted { first, _ in first.user.activeUser }
|
||||
}
|
||||
|
||||
func changeActiveUser(_ toUserId: Int64) {
|
||||
do {
|
||||
let activeUser = try apiSetActiveUser(toUserId)
|
||||
var users = users
|
||||
let oldActiveIndex = users.firstIndex(where: { $0.user.userId == currentUser?.userId })!
|
||||
var oldActive = users[oldActiveIndex]
|
||||
oldActive.user.activeUser = false
|
||||
users[oldActiveIndex] = oldActive
|
||||
|
||||
currentUser = activeUser
|
||||
let currentActiveIndex = users.firstIndex(where: { $0.user.userId == activeUser.userId })!
|
||||
users[currentActiveIndex] = UserInfo(user: activeUser, unreadCount: users[currentActiveIndex].unreadCount)
|
||||
updateUsers(users)
|
||||
try getUserChatData(self)
|
||||
} catch {
|
||||
logger.error("Unable to set active user: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UnreadChatItemCounts {
|
||||
|
||||
@@ -28,7 +28,6 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
private var granted = false
|
||||
private var prevNtfTime: Dictionary<ChatId, Date> = [:]
|
||||
|
||||
|
||||
// Handle notification when app is in background
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
didReceive response: UNNotificationResponse,
|
||||
@@ -38,6 +37,10 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
let chatModel = ChatModel.shared
|
||||
let action = response.actionIdentifier
|
||||
logger.debug("NtfManager.userNotificationCenter: didReceive: action \(action), categoryIdentifier \(content.categoryIdentifier)")
|
||||
if let userId = content.userInfo["userId"] as? Int64,
|
||||
userId != chatModel.currentUser?.userId {
|
||||
chatModel.changeActiveUser(userId)
|
||||
}
|
||||
if content.categoryIdentifier == ntfCategoryContactRequest && action == ntfActionAcceptContact,
|
||||
let chatId = content.userInfo["chatId"] as? String {
|
||||
if case let .contactRequest(contactRequest) = chatModel.getChat(chatId)?.chatInfo {
|
||||
@@ -189,20 +192,20 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
center.delegate = self
|
||||
}
|
||||
|
||||
func notifyContactRequest(_ contactRequest: UserContactRequest) {
|
||||
func notifyContactRequest(_ user: User, _ contactRequest: UserContactRequest) {
|
||||
logger.debug("NtfManager.notifyContactRequest")
|
||||
addNotification(createContactRequestNtf(contactRequest))
|
||||
addNotification(createContactRequestNtf(user, contactRequest))
|
||||
}
|
||||
|
||||
func notifyContactConnected(_ contact: Contact) {
|
||||
func notifyContactConnected(_ user: User, _ contact: Contact) {
|
||||
logger.debug("NtfManager.notifyContactConnected")
|
||||
addNotification(createContactConnectedNtf(contact))
|
||||
addNotification(createContactConnectedNtf(user, contact))
|
||||
}
|
||||
|
||||
func notifyMessageReceived(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
func notifyMessageReceived(_ user: User, _ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
logger.debug("NtfManager.notifyMessageReceived")
|
||||
if cInfo.ntfsEnabled {
|
||||
addNotification(createMessageReceivedNtf(cInfo, cItem))
|
||||
addNotification(createMessageReceivedNtf(user, cInfo, cItem))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ func apiGetActiveUser() throws -> User? {
|
||||
let r = chatSendCmdSync(.showActiveUser)
|
||||
switch r {
|
||||
case let .activeUser(user): return user
|
||||
case .chatCmdError(.error(.noActiveUser)): return nil
|
||||
case .chatCmdError(_, .error(.noActiveUser)): return nil
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
@@ -209,19 +209,19 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th
|
||||
func apiGetChats() throws -> [ChatData] {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetChats: no current user") }
|
||||
let r = chatSendCmdSync(.apiGetChats(userId: userId))
|
||||
if case let .apiChats(chats) = r { return chats }
|
||||
if case let .apiChats(_, chats) = r { return chats }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetChat(type: ChatType, id: Int64, search: String = "") throws -> Chat {
|
||||
let r = chatSendCmdSync(.apiGetChat(type: type, id: id, pagination: .last(count: 50), search: search))
|
||||
if case let .apiChat(chat) = r { return Chat.init(chat) }
|
||||
if case let .apiChat(_, chat) = r { return Chat.init(chat) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetChatItems(type: ChatType, id: Int64, pagination: ChatPagination, search: String = "") async throws -> [ChatItem] {
|
||||
let r = await chatSendCmd(.apiGetChat(type: type, id: id, pagination: pagination, search: search))
|
||||
if case let .apiChat(chat) = r { return chat.chatItems }
|
||||
if case let .apiChat(_, chat) = r { return chat.chatItems }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ func apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int6
|
||||
var cItem: ChatItem!
|
||||
let endTask = beginBGTask({ if cItem != nil { chatModel.messageDelivery.removeValue(forKey: cItem.id) } })
|
||||
r = await chatSendCmd(cmd, bgTask: false)
|
||||
if case let .newChatItem(aChatItem) = r {
|
||||
if case let .newChatItem(_, aChatItem) = r {
|
||||
cItem = aChatItem.chatItem
|
||||
chatModel.messageDelivery[cItem.id] = endTask
|
||||
return cItem
|
||||
@@ -258,7 +258,7 @@ func apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int6
|
||||
return nil
|
||||
} else {
|
||||
r = await chatSendCmd(cmd, bgDelay: msgDelay)
|
||||
if case let .newChatItem(aChatItem) = r {
|
||||
if case let .newChatItem(_, aChatItem) = r {
|
||||
return aChatItem.chatItem
|
||||
}
|
||||
sendMessageErrorAlert(r)
|
||||
@@ -276,13 +276,13 @@ private func sendMessageErrorAlert(_ r: ChatResponse) {
|
||||
|
||||
func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool = false) async throws -> ChatItem {
|
||||
let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, msg: msg, live: live), bgDelay: msgDelay)
|
||||
if case let .chatItemUpdated(aChatItem) = r { return aChatItem.chatItem }
|
||||
if case let .chatItemUpdated(_, aChatItem) = r { return aChatItem.chatItem }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> (ChatItem, ChatItem?) {
|
||||
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
|
||||
if case let .chatItemDeleted(deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
|
||||
if case let .chatItemDeleted(_, deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) {
|
||||
let r = chatSendCmdSync(.apiGetNtfToken)
|
||||
switch r {
|
||||
case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode)
|
||||
case .chatCmdError(.errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off)
|
||||
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off)
|
||||
default:
|
||||
logger.debug("apiGetNtfToken response: \(String(describing: r), privacy: .public)")
|
||||
return (nil, nil, .off)
|
||||
@@ -331,7 +331,7 @@ func apiDeleteToken(token: DeviceToken) async throws {
|
||||
func getUserSMPServers() throws -> ([ServerCfg], [String]) {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getUserSMPServers: no current user") }
|
||||
let r = chatSendCmdSync(.apiGetUserSMPServers(userId: userId))
|
||||
if case let .userSMPServers(smpServers, presetServers) = r { return (smpServers, presetServers) }
|
||||
if case let .userSMPServers(_, smpServers, presetServers) = r { return (smpServers, presetServers) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ func setUserSMPServers(smpServers: [ServerCfg]) async throws {
|
||||
func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure> {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("testSMPServer: no current user") }
|
||||
let r = await chatSendCmd(.testSMPServer(userId: userId, smpServer: smpServer))
|
||||
if case let .smpTestResult(testFailure) = r {
|
||||
if case let .smpTestResult(_, testFailure) = r {
|
||||
if let t = testFailure {
|
||||
return .failure(t)
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func testSMPServer(smpServer: String) async throws -> Result<(), SMPTestFailure>
|
||||
func getChatItemTTL() throws -> ChatItemTTL {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("getChatItemTTL: no current user") }
|
||||
let r = chatSendCmdSync(.apiGetChatItemTTL(userId: userId))
|
||||
if case let .chatItemTTL(chatItemTTL) = r { return ChatItemTTL(chatItemTTL) }
|
||||
if case let .chatItemTTL(_, chatItemTTL) = r { return ChatItemTTL(chatItemTTL) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -382,13 +382,13 @@ func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) a
|
||||
|
||||
func apiContactInfo(_ contactId: Int64) async throws -> (ConnectionStats?, Profile?) {
|
||||
let r = await chatSendCmd(.apiContactInfo(contactId: contactId))
|
||||
if case let .contactInfo(_, connStats, customUserProfile) = r { return (connStats, customUserProfile) }
|
||||
if case let .contactInfo(_, _, connStats, customUserProfile) = r { return (connStats, customUserProfile) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (ConnectionStats?) {
|
||||
let r = chatSendCmdSync(.apiGroupMemberInfo(groupId: groupId, groupMemberId: groupMemberId))
|
||||
if case let .groupMemberInfo(_, _, connStats_) = r { return (connStats_) }
|
||||
if case let .groupMemberInfo(_, _, _, connStats_) = r { return (connStats_) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -402,26 +402,26 @@ func apiSwitchGroupMember(_ groupId: Int64, _ groupMemberId: Int64) async throws
|
||||
|
||||
func apiGetContactCode(_ contactId: Int64) async throws -> (Contact, String) {
|
||||
let r = await chatSendCmd(.apiGetContactCode(contactId: contactId))
|
||||
if case let .contactCode(contact, connectionCode) = r { return (contact, connectionCode) }
|
||||
if case let .contactCode(_, contact, connectionCode) = r { return (contact, connectionCode) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetGroupMemberCode(_ groupId: Int64, _ groupMemberId: Int64) throws -> (GroupMember, String) {
|
||||
let r = chatSendCmdSync(.apiGetGroupMemberCode(groupId: groupId, groupMemberId: groupMemberId))
|
||||
if case let .groupMemberCode(_, member, connectionCode) = r { return (member, connectionCode) }
|
||||
if case let .groupMemberCode(_, _, member, connectionCode) = r { return (member, connectionCode) }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiVerifyContact(_ contactId: Int64, connectionCode: String?) -> (Bool, String)? {
|
||||
let r = chatSendCmdSync(.apiVerifyContact(contactId: contactId, connectionCode: connectionCode))
|
||||
if case let .connectionVerified(verified, expectedCode) = r { return (verified, expectedCode) }
|
||||
if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) }
|
||||
logger.error("apiVerifyContact error: \(String(describing: r))")
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCode: String?) -> (Bool, String)? {
|
||||
let r = chatSendCmdSync(.apiVerifyGroupMember(groupId: groupId, groupMemberId: groupMemberId, connectionCode: connectionCode))
|
||||
if case let .connectionVerified(verified, expectedCode) = r { return (verified, expectedCode) }
|
||||
if case let .connectionVerified(_, verified, expectedCode) = r { return (verified, expectedCode) }
|
||||
logger.error("apiVerifyGroupMember error: \(String(describing: r))")
|
||||
return nil
|
||||
}
|
||||
@@ -432,7 +432,7 @@ func apiAddContact() async -> String? {
|
||||
return nil
|
||||
}
|
||||
let r = await chatSendCmd(.apiAddContact(userId: userId), bgTask: false)
|
||||
if case let .invitation(connReqInvitation) = r { return connReqInvitation }
|
||||
if case let .invitation(_, connReqInvitation) = r { return connReqInvitation }
|
||||
connectionErrorAlert(r)
|
||||
return nil
|
||||
}
|
||||
@@ -447,7 +447,7 @@ func apiConnect(connReq: String) async -> ConnReqType? {
|
||||
switch r {
|
||||
case .sentConfirmation: return .invitation
|
||||
case .sentInvitation: return .contact
|
||||
case let .contactAlreadyExists(contact):
|
||||
case let .contactAlreadyExists(_, contact):
|
||||
let m = ChatModel.shared
|
||||
if let c = m.getContactChat(contact.contactId) {
|
||||
await MainActor.run { m.chatId = c.id }
|
||||
@@ -457,19 +457,19 @@ func apiConnect(connReq: String) async -> ConnReqType? {
|
||||
message: "You are already connected to \(contact.displayName)."
|
||||
)
|
||||
return nil
|
||||
case .chatCmdError(.error(.invalidConnReq)):
|
||||
case .chatCmdError(_, .error(.invalidConnReq)):
|
||||
am.showAlertMsg(
|
||||
title: "Invalid connection link",
|
||||
message: "Please check that you used the correct link or ask your contact to send you another one."
|
||||
)
|
||||
return nil
|
||||
case .chatCmdError(.errorAgent(.SMP(.AUTH))):
|
||||
case .chatCmdError(_, .errorAgent(.SMP(.AUTH))):
|
||||
am.showAlertMsg(
|
||||
title: "Connection error (AUTH)",
|
||||
message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection."
|
||||
)
|
||||
return nil
|
||||
case let .chatCmdError(.errorAgent(.INTERNAL(internalErr))):
|
||||
case let .chatCmdError(_, .errorAgent(.INTERNAL(internalErr))):
|
||||
if internalErr == "SEUniqueID" {
|
||||
am.showAlertMsg(
|
||||
title: "Already connected?",
|
||||
@@ -516,7 +516,7 @@ func deleteChat(_ chat: Chat) async {
|
||||
|
||||
func apiClearChat(type: ChatType, id: Int64) async throws -> ChatInfo {
|
||||
let r = await chatSendCmd(.apiClearChat(type: type, id: id), bgTask: false)
|
||||
if case let .chatCleared(updatedChatInfo) = r { return updatedChatInfo }
|
||||
if case let .chatCleared(_, updatedChatInfo) = r { return updatedChatInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@ func clearChat(_ chat: Chat) async {
|
||||
func apiListContacts() throws -> [Contact] {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiListContacts: no current user") }
|
||||
let r = chatSendCmdSync(.apiListContacts(userId: userId))
|
||||
if case let .contactsList(contacts) = r { return contacts }
|
||||
if case let .contactsList(_, contacts) = r { return contacts }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -542,33 +542,33 @@ func apiUpdateProfile(profile: Profile) async throws -> Profile? {
|
||||
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
|
||||
switch r {
|
||||
case .userProfileNoChange: return nil
|
||||
case let .userProfileUpdated(_, toProfile): return toProfile
|
||||
case let .userProfileUpdated(_, _, toProfile): return toProfile
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
|
||||
func apiSetContactPrefs(contactId: Int64, preferences: Preferences) async throws -> Contact? {
|
||||
let r = await chatSendCmd(.apiSetContactPrefs(contactId: contactId, preferences: preferences))
|
||||
if case let .contactPrefsUpdated(_, toContact) = r { return toContact }
|
||||
if case let .contactPrefsUpdated(_, _, toContact) = r { return toContact }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetContactAlias(contactId: Int64, localAlias: String) async throws -> Contact? {
|
||||
let r = await chatSendCmd(.apiSetContactAlias(contactId: contactId, localAlias: localAlias))
|
||||
if case let .contactAliasUpdated(toContact) = r { return toContact }
|
||||
if case let .contactAliasUpdated(_, toContact) = r { return toContact }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSetConnectionAlias(connId: Int64, localAlias: String) async throws -> PendingContactConnection? {
|
||||
let r = await chatSendCmd(.apiSetConnectionAlias(connId: connId, localAlias: localAlias))
|
||||
if case let .connectionAliasUpdated(toConnection) = r { return toConnection }
|
||||
if case let .connectionAliasUpdated(_, toConnection) = r { return toConnection }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiCreateUserAddress() async throws -> String {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiCreateUserAddress: no current user") }
|
||||
let r = await chatSendCmd(.apiCreateMyAddress(userId: userId))
|
||||
if case let .userContactLinkCreated(connReq) = r { return connReq }
|
||||
if case let .userContactLinkCreated(_, connReq) = r { return connReq }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -583,8 +583,8 @@ func apiGetUserAddress() throws -> UserContactLink? {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiGetUserAddress: no current user") }
|
||||
let r = chatSendCmdSync(.apiShowMyAddress(userId: userId))
|
||||
switch r {
|
||||
case let .userContactLink(contactLink): return contactLink
|
||||
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
|
||||
case let .userContactLink(_, contactLink): return contactLink
|
||||
case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
@@ -593,8 +593,8 @@ func userAddressAutoAccept(_ autoAccept: AutoAccept?) async throws -> UserContac
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("userAddressAutoAccept: no current user") }
|
||||
let r = await chatSendCmd(.apiAddressAutoAccept(userId: userId, autoAccept: autoAccept))
|
||||
switch r {
|
||||
case let .userContactLinkUpdated(contactLink): return contactLink
|
||||
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
|
||||
case let .userContactLinkUpdated(_, contactLink): return contactLink
|
||||
case .chatCmdError(_, chatError: .errorStore(storeError: .userContactLinkNotFound)): return nil
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
@@ -603,8 +603,8 @@ func apiAcceptContactRequest(contactReqId: Int64) async -> Contact? {
|
||||
let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
|
||||
let am = AlertManager.shared
|
||||
|
||||
if case let .acceptingContactRequest(contact) = r { return contact }
|
||||
if case .chatCmdError(.errorAgent(.SMP(.AUTH))) = r {
|
||||
if case let .acceptingContactRequest(_, contact) = r { return contact }
|
||||
if case .chatCmdError(_, .errorAgent(.SMP(.AUTH))) = r {
|
||||
am.showAlertMsg(
|
||||
title: "Connection error (AUTH)",
|
||||
message: "Sender may have deleted the connection request."
|
||||
@@ -643,7 +643,7 @@ func receiveFile(fileId: Int64) async {
|
||||
func apiReceiveFile(fileId: Int64, inline: Bool) async -> AChatItem? {
|
||||
let r = await chatSendCmd(.receiveFile(fileId: fileId, inline: inline))
|
||||
let am = AlertManager.shared
|
||||
if case let .rcvFileAccepted(chatItem) = r { return chatItem }
|
||||
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
|
||||
if case .rcvFileAcceptedSndCancelled = r {
|
||||
am.showAlertMsg(
|
||||
title: "Cannot receive file",
|
||||
@@ -652,7 +652,7 @@ func apiReceiveFile(fileId: Int64, inline: Bool) async -> AChatItem? {
|
||||
} else if !networkErrorAlert(r) {
|
||||
logger.error("apiReceiveFile error: \(String(describing: r))")
|
||||
switch r {
|
||||
case .chatCmdError(.error(.fileAlreadyReceiving)):
|
||||
case .chatCmdError(_, .error(.fileAlreadyReceiving)):
|
||||
logger.debug("apiReceiveFile ignoring fileAlreadyReceiving error")
|
||||
default:
|
||||
am.showAlertMsg(
|
||||
@@ -667,13 +667,13 @@ func apiReceiveFile(fileId: Int64, inline: Bool) async -> AChatItem? {
|
||||
func networkErrorAlert(_ r: ChatResponse) -> Bool {
|
||||
let am = AlertManager.shared
|
||||
switch r {
|
||||
case let .chatCmdError(.errorAgent(.BROKER(addr, .TIMEOUT))):
|
||||
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):
|
||||
am.showAlertMsg(
|
||||
title: "Connection timeout",
|
||||
message: "Please check your network connection with \(serverHostname(addr)) and try again."
|
||||
)
|
||||
return true
|
||||
case let .chatCmdError(.errorAgent(.BROKER(addr, .NETWORK))):
|
||||
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))):
|
||||
am.showAlertMsg(
|
||||
title: "Connection error",
|
||||
message: "Please check your network connection with \(serverHostname(addr)) and try again."
|
||||
@@ -788,13 +788,13 @@ private func sendCommandOkResp(_ cmd: ChatCommand) async throws {
|
||||
func apiNewGroup(_ p: GroupProfile) throws -> GroupInfo {
|
||||
guard let userId = ChatModel.shared.currentUser?.userId else { throw RuntimeError("apiNewGroup: no current user") }
|
||||
let r = chatSendCmdSync(.apiNewGroup(userId: userId, groupProfile: p))
|
||||
if case let .groupCreated(groupInfo) = r { return groupInfo }
|
||||
if case let .groupCreated(_, groupInfo) = r { return groupInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiAddMember(_ groupId: Int64, _ contactId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember {
|
||||
let r = await chatSendCmd(.apiAddMember(groupId: groupId, contactId: contactId, memberRole: memberRole))
|
||||
if case let .sentGroupInvitation(_, _, member) = r { return member }
|
||||
if case let .sentGroupInvitation(_, _, _, member) = r { return member }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -807,22 +807,22 @@ enum JoinGroupResult {
|
||||
func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult {
|
||||
let r = await chatSendCmd(.apiJoinGroup(groupId: groupId))
|
||||
switch r {
|
||||
case let .userAcceptedGroupSent(groupInfo, _): return .joined(groupInfo: groupInfo)
|
||||
case .chatCmdError(.errorAgent(.SMP(.AUTH))): return .invitationRemoved
|
||||
case .chatCmdError(.errorStore(.groupNotFound)): return .groupNotFound
|
||||
case let .userAcceptedGroupSent(_, groupInfo, _): return .joined(groupInfo: groupInfo)
|
||||
case .chatCmdError(_, .errorAgent(.SMP(.AUTH))): return .invitationRemoved
|
||||
case .chatCmdError(_, .errorStore(.groupNotFound)): return .groupNotFound
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
|
||||
func apiRemoveMember(_ groupId: Int64, _ memberId: Int64) async throws -> GroupMember {
|
||||
let r = await chatSendCmd(.apiRemoveMember(groupId: groupId, memberId: memberId), bgTask: false)
|
||||
if case let .userDeletedMember(_, member) = r { return member }
|
||||
if case let .userDeletedMember(_, _, member) = r { return member }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiMemberRole(_ groupId: Int64, _ memberId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember {
|
||||
let r = await chatSendCmd(.apiMemberRole(groupId: groupId, memberId: memberId, memberRole: memberRole), bgTask: false)
|
||||
if case let .memberRoleUser(_, member, _, _) = r { return member }
|
||||
if case let .memberRoleUser(_, _, member, _, _) = r { return member }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -837,19 +837,19 @@ func leaveGroup(_ groupId: Int64) async {
|
||||
|
||||
func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo {
|
||||
let r = await chatSendCmd(.apiLeaveGroup(groupId: groupId), bgTask: false)
|
||||
if case let .leftMemberUser(groupInfo) = r { return groupInfo }
|
||||
if case let .leftMemberUser(_, groupInfo) = r { return groupInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
|
||||
let r = await chatSendCmd(.apiListMembers(groupId: groupId))
|
||||
if case let .groupMembers(group) = r { return group.members }
|
||||
if case let .groupMembers(_, group) = r { return group.members }
|
||||
return []
|
||||
}
|
||||
|
||||
func apiListMembersSync(_ groupId: Int64) -> [GroupMember] {
|
||||
let r = chatSendCmdSync(.apiListMembers(groupId: groupId))
|
||||
if case let .groupMembers(group) = r { return group.members }
|
||||
if case let .groupMembers(_, group) = r { return group.members }
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -863,13 +863,13 @@ func filterMembersToAdd(_ ms: [GroupMember]) -> [Contact] {
|
||||
|
||||
func apiUpdateGroup(_ groupId: Int64, _ groupProfile: GroupProfile) async throws -> GroupInfo {
|
||||
let r = await chatSendCmd(.apiUpdateGroupProfile(groupId: groupId, groupProfile: groupProfile))
|
||||
if case let .groupUpdated(toGroup) = r { return toGroup }
|
||||
if case let .groupUpdated(_, toGroup) = r { return toGroup }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiCreateGroupLink(_ groupId: Int64) async throws -> String {
|
||||
let r = await chatSendCmd(.apiCreateGroupLink(groupId: groupId))
|
||||
if case let .groupLinkCreated(_, connReq) = r { return connReq }
|
||||
if case let .groupLinkCreated(_, _, connReq) = r { return connReq }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -882,9 +882,9 @@ func apiDeleteGroupLink(_ groupId: Int64) async throws {
|
||||
func apiGetGroupLink(_ groupId: Int64) throws -> String? {
|
||||
let r = chatSendCmdSync(.apiGetGroupLink(groupId: groupId))
|
||||
switch r {
|
||||
case let .groupLink(_, connReq):
|
||||
case let .groupLink(_, _, connReq):
|
||||
return connReq
|
||||
case .chatCmdError(chatError: .errorStore(storeError: .groupLinkNotFound)):
|
||||
case .chatCmdError(_, chatError: .errorStore(storeError: .groupLinkNotFound)):
|
||||
return nil
|
||||
default: throw r
|
||||
}
|
||||
@@ -917,9 +917,9 @@ func startChat() throws {
|
||||
let m = ChatModel.shared
|
||||
try setNetworkConfig(getNetCfg())
|
||||
let justStarted = try apiStartChat()
|
||||
m.updateUsers(listUsers())
|
||||
if justStarted {
|
||||
try getUserChatData(m)
|
||||
m.users = listUsers()
|
||||
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCount())
|
||||
try refreshCallInvitations()
|
||||
(m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken()
|
||||
@@ -927,7 +927,7 @@ func startChat() throws {
|
||||
registerToken(token: token)
|
||||
}
|
||||
withAnimation {
|
||||
m.onboardingStage = m.onboardingStage == .step2_CreateProfile
|
||||
m.onboardingStage = m.onboardingStage == .step2_CreateProfile && m.users.count == 1
|
||||
? .step3_SetNotificationsMode
|
||||
: .onboardingComplete
|
||||
}
|
||||
@@ -988,25 +988,31 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.terminalItems.append(.resp(.now, res))
|
||||
logger.debug("processReceivedMsg: \(res.responseType)")
|
||||
switch res {
|
||||
case let .newContactConnection(connection):
|
||||
m.updateContactConnection(connection)
|
||||
case let .contactConnectionDeleted(connection):
|
||||
m.removeChat(connection.id)
|
||||
case let .contactConnected(contact, _):
|
||||
if contact.directOrUsed {
|
||||
case let .newContactConnection(user, connection):
|
||||
if active(user) {
|
||||
m.updateContactConnection(connection)
|
||||
}
|
||||
case let .contactConnectionDeleted(user, connection):
|
||||
if active(user) {
|
||||
m.removeChat(connection.id)
|
||||
}
|
||||
case let .contactConnected(user, contact, _):
|
||||
if active(user) && contact.directOrUsed {
|
||||
m.updateContact(contact)
|
||||
m.dismissConnReqView(contact.activeConn.id)
|
||||
m.removeChat(contact.activeConn.id)
|
||||
m.updateNetworkStatus(contact.id, .connected)
|
||||
NtfManager.shared.notifyContactConnected(contact)
|
||||
NtfManager.shared.notifyContactConnected(user, contact)
|
||||
}
|
||||
case let .contactConnecting(contact):
|
||||
if contact.directOrUsed {
|
||||
case let .contactConnecting(user, contact):
|
||||
if active(user) && contact.directOrUsed {
|
||||
m.updateContact(contact)
|
||||
m.dismissConnReqView(contact.activeConn.id)
|
||||
m.removeChat(contact.activeConn.id)
|
||||
}
|
||||
case let .receivedContactRequest(contactRequest):
|
||||
case let .receivedContactRequest(user, contactRequest):
|
||||
if !active(user) { return }
|
||||
|
||||
let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest)
|
||||
if m.hasChat(contactRequest.id) {
|
||||
m.updateChatInfo(cInfo)
|
||||
@@ -1015,27 +1021,34 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
chatInfo: cInfo,
|
||||
chatItems: []
|
||||
))
|
||||
NtfManager.shared.notifyContactRequest(contactRequest)
|
||||
NtfManager.shared.notifyContactRequest(user, contactRequest)
|
||||
}
|
||||
case let .contactUpdated(toContact):
|
||||
let cInfo = ChatInfo.direct(contact: toContact)
|
||||
if m.hasChat(toContact.id) {
|
||||
case let .contactUpdated(user, toContact):
|
||||
if active(user) && m.hasChat(toContact.id) {
|
||||
let cInfo = ChatInfo.direct(contact: toContact)
|
||||
m.updateChatInfo(cInfo)
|
||||
}
|
||||
case let .contactsMerged(intoContact, mergedContact):
|
||||
if m.hasChat(mergedContact.id) {
|
||||
case let .contactsMerged(user, intoContact, mergedContact):
|
||||
if active(user) && m.hasChat(mergedContact.id) {
|
||||
if m.chatId == mergedContact.id {
|
||||
m.chatId = intoContact.id
|
||||
}
|
||||
m.removeChat(mergedContact.id)
|
||||
}
|
||||
case let .contactsSubscribed(_, contactRefs):
|
||||
updateContactsStatus(contactRefs, status: .connected)
|
||||
case let .contactsDisconnected(_, contactRefs):
|
||||
updateContactsStatus(contactRefs, status: .disconnected)
|
||||
case let .contactSubError(contact, chatError):
|
||||
processContactSubError(contact, chatError)
|
||||
case let .contactSubSummary(contactSubscriptions):
|
||||
case let .contactsSubscribed(user, _, contactRefs):
|
||||
if active(user) {
|
||||
updateContactsStatus(contactRefs, status: .connected)
|
||||
}
|
||||
case let .contactsDisconnected(user, _, contactRefs):
|
||||
if active(user) {
|
||||
updateContactsStatus(contactRefs, status: .disconnected)
|
||||
}
|
||||
case let .contactSubError(user, contact, chatError):
|
||||
if active(user) {
|
||||
processContactSubError(contact, chatError)
|
||||
}
|
||||
case let .contactSubSummary(user, contactSubscriptions):
|
||||
if !active(user) { return }
|
||||
for sub in contactSubscriptions {
|
||||
if let err = sub.contactError {
|
||||
processContactSubError(sub.contact, err)
|
||||
@@ -1044,7 +1057,14 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
m.updateNetworkStatus(sub.contact.id, .connected)
|
||||
}
|
||||
}
|
||||
case let .newChatItem(aChatItem):
|
||||
case let .newChatItem(user, aChatItem):
|
||||
if !active(user) {
|
||||
if case .rcvNew = aChatItem.chatItem.meta.itemStatus {
|
||||
m.increaseUnreadCounter(user: user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
m.addChatItem(cInfo, cItem)
|
||||
@@ -1060,9 +1080,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
}
|
||||
if cItem.showNotification {
|
||||
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
}
|
||||
case let .chatItemStatusUpdated(aChatItem):
|
||||
case let .chatItemStatusUpdated(user, aChatItem):
|
||||
if !active(user) { return }
|
||||
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
var res = false
|
||||
@@ -1070,7 +1092,7 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
res = m.upsertChatItem(cInfo, cItem)
|
||||
}
|
||||
if res {
|
||||
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
NtfManager.shared.notifyMessageReceived(user, cInfo, cItem)
|
||||
} else if let endTask = m.messageDelivery[cItem.id] {
|
||||
switch cItem.meta.itemStatus {
|
||||
case .sndSent: endTask()
|
||||
@@ -1079,48 +1101,87 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
default: break
|
||||
}
|
||||
}
|
||||
case let .chatItemUpdated(aChatItem):
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
case let .chatItemDeleted(deletedChatItem, toChatItem, _):
|
||||
case let .chatItemUpdated(user, aChatItem):
|
||||
if active(user) {
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
}
|
||||
case let .chatItemDeleted(user, deletedChatItem, toChatItem, _):
|
||||
if !active(user) {
|
||||
if toChatItem == nil && deletedChatItem.chatItem.isRcvNew {
|
||||
m.decreaseUnreadCounter(user: user)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let toChatItem = toChatItem {
|
||||
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
|
||||
} else {
|
||||
m.removeChatItem(deletedChatItem.chatInfo, deletedChatItem.chatItem)
|
||||
}
|
||||
case let .receivedGroupInvitation(groupInfo, _, _):
|
||||
m.updateGroup(groupInfo) // update so that repeat group invitations are not duplicated
|
||||
// NtfManager.shared.notifyContactRequest(contactRequest) // TODO notifyGroupInvitation?
|
||||
case let .userAcceptedGroupSent(groupInfo, hostContact):
|
||||
case let .receivedGroupInvitation(user, groupInfo, _, _):
|
||||
if active(user) {
|
||||
m.updateGroup(groupInfo) // update so that repeat group invitations are not duplicated
|
||||
// NtfManager.shared.notifyContactRequest(contactRequest) // TODO notifyGroupInvitation?
|
||||
}
|
||||
case let .userAcceptedGroupSent(user, groupInfo, hostContact):
|
||||
if !active(user) { return }
|
||||
|
||||
m.updateGroup(groupInfo)
|
||||
if let hostContact = hostContact {
|
||||
m.dismissConnReqView(hostContact.activeConn.id)
|
||||
m.removeChat(hostContact.activeConn.id)
|
||||
}
|
||||
case let .joinedGroupMemberConnecting(groupInfo, _, member):
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
case let .deletedMemberUser(groupInfo, _): // TODO update user member
|
||||
m.updateGroup(groupInfo)
|
||||
case let .deletedMember(groupInfo, _, deletedMember):
|
||||
_ = m.upsertGroupMember(groupInfo, deletedMember)
|
||||
case let .leftMember(groupInfo, member):
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
case let .groupDeleted(groupInfo, _): // TODO update user member
|
||||
m.updateGroup(groupInfo)
|
||||
case let .userJoinedGroup(groupInfo):
|
||||
m.updateGroup(groupInfo)
|
||||
case let .joinedGroupMember(groupInfo, member):
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
case let .connectedToGroupMember(groupInfo, member):
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
case let .groupUpdated(toGroup):
|
||||
m.updateGroup(toGroup)
|
||||
case let .rcvFileStart(aChatItem):
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
case let .rcvFileComplete(aChatItem):
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
case let .sndFileStart(aChatItem, _):
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
case let .sndFileComplete(aChatItem, _):
|
||||
case let .joinedGroupMemberConnecting(user, groupInfo, _, member):
|
||||
if active(user) {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
case let .deletedMemberUser(user, groupInfo, _): // TODO update user member
|
||||
if active(user) {
|
||||
m.updateGroup(groupInfo)
|
||||
}
|
||||
case let .deletedMember(user, groupInfo, _, deletedMember):
|
||||
if active(user) {
|
||||
_ = m.upsertGroupMember(groupInfo, deletedMember)
|
||||
}
|
||||
case let .leftMember(user, groupInfo, member):
|
||||
if active(user) {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
case let .groupDeleted(user, groupInfo, _): // TODO update user member
|
||||
if active(user) {
|
||||
m.updateGroup(groupInfo)
|
||||
}
|
||||
case let .userJoinedGroup(user, groupInfo):
|
||||
if active(user) {
|
||||
m.updateGroup(groupInfo)
|
||||
}
|
||||
case let .joinedGroupMember(user, groupInfo, member):
|
||||
if active(user) {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
case let .connectedToGroupMember(user, groupInfo, member):
|
||||
if active(user) {
|
||||
_ = m.upsertGroupMember(groupInfo, member)
|
||||
}
|
||||
case let .groupUpdated(user, toGroup):
|
||||
if active(user) {
|
||||
m.updateGroup(toGroup)
|
||||
}
|
||||
case let .rcvFileStart(user, aChatItem):
|
||||
if active(user) {
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
}
|
||||
case let .rcvFileComplete(user, aChatItem):
|
||||
if active(user) {
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
}
|
||||
case let .sndFileStart(user, aChatItem, _):
|
||||
if active(user) {
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
}
|
||||
case let .sndFileComplete(user, aChatItem, _):
|
||||
if !active(user) { return }
|
||||
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
let cItem = aChatItem.chatItem
|
||||
let mc = cItem.content.msgContent
|
||||
@@ -1145,7 +1206,7 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
// logger.debug("reportNewIncomingVoIPPushPayload success for \(contact.id)")
|
||||
// }
|
||||
// }
|
||||
case let .callOffer(contact, callType, offer, sharedKey, _):
|
||||
case let .callOffer(_, contact, callType, offer, sharedKey, _):
|
||||
withCall(contact) { call in
|
||||
call.callState = .offerReceived
|
||||
call.peerMedia = callType.media
|
||||
@@ -1163,16 +1224,16 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
relay: useRelay
|
||||
)
|
||||
}
|
||||
case let .callAnswer(contact, answer):
|
||||
case let .callAnswer(_, contact, answer):
|
||||
withCall(contact) { call in
|
||||
call.callState = .answerReceived
|
||||
m.callCommand = .answer(answer: answer.rtcSession, iceCandidates: answer.rtcIceCandidates)
|
||||
}
|
||||
case let .callExtraInfo(contact, extraInfo):
|
||||
case let .callExtraInfo(_, contact, extraInfo):
|
||||
withCall(contact) { _ in
|
||||
m.callCommand = .ice(iceCandidates: extraInfo.rtcIceCandidates)
|
||||
}
|
||||
case let .callEnded(contact):
|
||||
case let .callEnded(_, contact):
|
||||
if let invitation = m.callInvitations.removeValue(forKey: contact.id) {
|
||||
CallController.shared.reportCallRemoteEnded(invitation: invitation)
|
||||
}
|
||||
@@ -1186,6 +1247,10 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
logger.debug("unsupported event: \(res.responseType)")
|
||||
}
|
||||
|
||||
func active(_ user: User) -> Bool {
|
||||
user.id == m.currentUser?.id
|
||||
}
|
||||
|
||||
func withCall(_ contact: Contact, _ perform: (Call) -> Void) {
|
||||
if let call = m.activeCall, call.contact.apiId == contact.apiId {
|
||||
perform(call)
|
||||
@@ -1201,7 +1266,7 @@ func chatItemSimpleUpdate(_ aChatItem: AChatItem) {
|
||||
let cInfo = aChatItem.chatInfo
|
||||
let cItem = aChatItem.chatItem
|
||||
if m.upsertChatItem(cInfo, cItem) {
|
||||
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
NtfManager.shared.notifyMessageReceived(m.currentUser!, cInfo, cItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class CallManager {
|
||||
|
||||
func answerIncomingCall(invitation: RcvCallInvitation) {
|
||||
let m = ChatModel.shared
|
||||
// TODO: change active user
|
||||
m.callInvitations.removeValue(forKey: invitation.contact.id)
|
||||
m.activeCall = Call(
|
||||
direction: .incoming,
|
||||
|
||||
@@ -416,9 +416,9 @@ struct ErrorAlert {
|
||||
|
||||
func getErrorAlert(_ error: Error, _ title: LocalizedStringKey) -> ErrorAlert {
|
||||
switch error as? ChatResponse {
|
||||
case let .chatCmdError(.errorAgent(.BROKER(addr, .TIMEOUT))):
|
||||
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):
|
||||
return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
|
||||
case let .chatCmdError(.errorAgent(.BROKER(addr, .NETWORK))):
|
||||
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))):
|
||||
return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
|
||||
default:
|
||||
return ErrorAlert(title: title, message: "Error: \(responseError(error))")
|
||||
|
||||
@@ -29,45 +29,56 @@ struct UserPicker: View {
|
||||
Spacer().frame(height: 1)
|
||||
VStack(spacing: 0) {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(chatModel.users.enumerated()), id: \.0) { i, userInfo in
|
||||
Button(action: {
|
||||
if !userInfo.user.activeUser {
|
||||
changeActiveUser(toUser: userInfo)
|
||||
}
|
||||
}, label: {
|
||||
HStack(spacing: 0) {
|
||||
ProfileImage(imageStr: userInfo.user.image)
|
||||
ScrollViewReader { sp in
|
||||
VStack(spacing: 0) {
|
||||
ForEach(Array(chatModel.users.enumerated()), id: \.0) { i, userInfo in
|
||||
Button(action: {
|
||||
if !userInfo.user.activeUser {
|
||||
chatModel.changeActiveUser(userInfo.user.userId)
|
||||
userPickerVisible = false
|
||||
}
|
||||
}, label: {
|
||||
HStack(spacing: 0) {
|
||||
ProfileImage(imageStr: userInfo.user.image)
|
||||
.frame(width: 44, height: 44)
|
||||
.padding(.trailing, 12)
|
||||
Text(userInfo.user.chatViewName)
|
||||
Text(userInfo.user.chatViewName)
|
||||
.fontWeight(i == 0 ? .medium : .regular)
|
||||
.foregroundColor(.primary)
|
||||
.overlay(DetermineWidth())
|
||||
Spacer()
|
||||
if i == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
} else if userInfo.unreadCount > 0 {
|
||||
unreadCounter(userInfo.unreadCount)
|
||||
Spacer()
|
||||
if i == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
} else if userInfo.unreadCount > 0 {
|
||||
unreadCounter(userInfo.unreadCount)
|
||||
}
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding([.leading, .vertical], 12)
|
||||
})
|
||||
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
|
||||
if i < chatModel.users.count - 1 {
|
||||
Divider()
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding([.leading, .vertical], 12)
|
||||
})
|
||||
.buttonStyle(PressedButtonStyle(defaultColor: fillColor, pressedColor: Color(uiColor: .secondarySystemFill)))
|
||||
if i < chatModel.users.count - 1 {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
.overlay {
|
||||
GeometryReader { geo -> Color in
|
||||
DispatchQueue.main.async {
|
||||
scrollViewContentSize = geo.size
|
||||
let layoutFrame = UIApplication.shared.windows[0].safeAreaLayoutGuide.layoutFrame
|
||||
disableScrolling = scrollViewContentSize.height + menuButtonHeight * 2 + 10 < layoutFrame.height
|
||||
.overlay {
|
||||
GeometryReader { geo -> Color in
|
||||
DispatchQueue.main.async {
|
||||
scrollViewContentSize = geo.size
|
||||
let scenes = UIApplication.shared.connectedScenes
|
||||
if let windowScene = scenes.first as? UIWindowScene {
|
||||
let layoutFrame = windowScene.windows[0].safeAreaLayoutGuide.layoutFrame
|
||||
disableScrolling = scrollViewContentSize.height + menuButtonHeight * 2 + 10 < layoutFrame.height
|
||||
}
|
||||
}
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
.onChange(of: userPickerVisible) { visible in
|
||||
if visible {
|
||||
sp.scrollTo(0)
|
||||
}
|
||||
return Color.clear
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,36 +119,15 @@ struct UserPicker: View {
|
||||
|
||||
private func reloadCurrentUser() {
|
||||
if let updatedUser = chatModel.currentUser, let index = chatModel.users.firstIndex(where: { $0.user.userId == updatedUser.userId }) {
|
||||
let removed = chatModel.users.remove(at: index)
|
||||
chatModel.users.insert(UserInfo(user: updatedUser, unreadCount: removed.unreadCount), at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func changeActiveUser(toUser: UserInfo) {
|
||||
Task {
|
||||
do {
|
||||
let activeUser = try apiSetActiveUser(toUser.user.userId)
|
||||
let oldActiveIndex = chatModel.users.firstIndex(where: { $0.user.userId == chatModel.currentUser?.userId })!
|
||||
var oldActive = chatModel.users[oldActiveIndex]
|
||||
oldActive.user.activeUser = false
|
||||
chatModel.users[oldActiveIndex] = oldActive
|
||||
|
||||
chatModel.currentUser = activeUser
|
||||
let currentActiveIndex = chatModel.users.firstIndex(where: { $0.user.userId == activeUser.userId })!
|
||||
let removed = chatModel.users.remove(at: currentActiveIndex)
|
||||
chatModel.users.insert(UserInfo(user: activeUser, unreadCount: removed.unreadCount), at: 0)
|
||||
chatModel.users = chatModel.users.map { $0 }
|
||||
try getUserChatData(chatModel)
|
||||
userPickerVisible = false
|
||||
} catch {
|
||||
logger.error("Unable to set active user: \(error.localizedDescription)")
|
||||
}
|
||||
var users = chatModel.users
|
||||
users[index] = UserInfo(user: updatedUser, unreadCount: users[index].unreadCount)
|
||||
chatModel.updateUsers(users)
|
||||
}
|
||||
}
|
||||
|
||||
private func reloadUsers() {
|
||||
Task {
|
||||
chatModel.users = listUsers().sorted { one, two -> Bool in one.user.activeUser }
|
||||
chatModel.updateUsers(listUsers())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +161,7 @@ func unreadCounter(_ unread: Int64) -> some View {
|
||||
struct UserPicker_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let m = ChatModel()
|
||||
m.users = [UserInfo.sampleData, UserInfo.sampleData]
|
||||
m.updateUsers([UserInfo.sampleData, UserInfo.sampleData])
|
||||
return UserPicker(
|
||||
showSettings: Binding.constant(false),
|
||||
userPickerVisible: Binding.constant(true)
|
||||
|
||||
@@ -152,7 +152,7 @@ struct DatabaseEncryptionView: View {
|
||||
await operationEnded(.databaseEncrypted)
|
||||
}
|
||||
} catch let error {
|
||||
if case .chatCmdError(.errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse {
|
||||
if case .chatCmdError(_, .errorDatabase(.errorExport(.errorNotADatabase))) = error as? ChatResponse {
|
||||
await operationEnded(.currentPassphraseError)
|
||||
} else {
|
||||
await operationEnded(.error(title: "Error encrypting database", error: "\(responseError(error))"))
|
||||
|
||||
@@ -11,6 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct CreateProfile: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
@State private var displayName: String = ""
|
||||
@State private var fullName: String = ""
|
||||
@FocusState private var focusDisplayName
|
||||
@@ -97,8 +98,12 @@ struct CreateProfile: View {
|
||||
do {
|
||||
m.currentUser = try apiCreateActiveUser(profile)
|
||||
try startChat()
|
||||
withAnimation { m.onboardingStage = .step3_SetNotificationsMode }
|
||||
|
||||
if m.users.count == 1 {
|
||||
withAnimation { m.onboardingStage = .step3_SetNotificationsMode }
|
||||
} else {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
try getUserChatData(m)
|
||||
}
|
||||
} catch {
|
||||
fatalError("Failed to create user or start chat: \(responseError(error))")
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ struct TerminalView: View {
|
||||
func sendMessage() {
|
||||
let cmd = ChatCommand.string(composeState.message)
|
||||
if composeState.message.starts(with: "/sql") && (!prefPerformLA || !developerTools) {
|
||||
let resp = ChatResponse.chatCmdError(chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
|
||||
let resp = ChatResponse.chatCmdError(user: nil, chatError: ChatError.error(errorType: ChatErrorType.commandError(message: "Failed reading: empty")))
|
||||
DispatchQueue.main.async {
|
||||
ChatModel.shared.terminalItems.append(.cmd(.now, cmd))
|
||||
ChatModel.shared.terminalItems.append(.resp(.now, resp))
|
||||
|
||||
@@ -11,23 +11,24 @@ struct UserProfilesView: View {
|
||||
@Environment(\.editMode) private var editMode
|
||||
@State private var selectedUser: Int? = nil
|
||||
@State private var showAddUser: Bool? = false
|
||||
@State private var showScanSMPServer = false
|
||||
@State private var testing = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section("Your profiles") {
|
||||
ForEach(Array($m.users.enumerated()), id: \.0) { i, userInfo in
|
||||
ForEach(Array(m.users.enumerated()), id: \.0) { i, userInfo in
|
||||
userProfileView(userInfo, index: i)
|
||||
.deleteDisabled(userInfo.wrappedValue.user.activeUser)
|
||||
.deleteDisabled(userInfo.user.activeUser)
|
||||
}
|
||||
.onDelete { indexSet in
|
||||
do {
|
||||
try apiDeleteUser(m.users[indexSet.first!].user.userId)
|
||||
m.users.remove(atOffsets: indexSet)
|
||||
} catch {
|
||||
fatalError("Failed to delete user: \(responseError(error))")
|
||||
}
|
||||
AlertManager.shared.showAlert(
|
||||
Alert(
|
||||
title: Text("Delete profile?"),
|
||||
message: Text("All chats and messages will be deleted - this cannot be undone!"),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
removeUser(index: indexSet.first!)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
))
|
||||
}
|
||||
NavigationLink(destination: CreateProfile(), tag: true, selection: $showAddUser) {
|
||||
Text("Add profile…")
|
||||
@@ -37,8 +38,21 @@ struct UserProfilesView: View {
|
||||
.toolbar { EditButton() }
|
||||
}
|
||||
|
||||
private func userProfileView(_ userBinding: Binding<UserInfo>, index: Int) -> some View {
|
||||
let user = userBinding.wrappedValue.user
|
||||
private func removeUser(index: Int) {
|
||||
do {
|
||||
try apiDeleteUser(m.users[index].user.userId)
|
||||
var users = m.users
|
||||
users.remove(at: index)
|
||||
m.updateUsers(users)
|
||||
} catch {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "Failed to delete the user",
|
||||
message: "Error: \(responseError(error))"
|
||||
)
|
||||
}
|
||||
}
|
||||
private func userProfileView(_ userBinding: UserInfo, index: Int) -> some View {
|
||||
let user = userBinding.user
|
||||
return NavigationLink(tag: index, selection: $selectedUser) {
|
||||
// UserPrefs(user: userBinding, index: index)
|
||||
// .navigationBarTitle(user.chatViewName)
|
||||
|
||||
@@ -107,20 +107,22 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
let encNtfInfo = ntfData["message"] as? String,
|
||||
let dbStatus = startChat() {
|
||||
if case .ok = dbStatus,
|
||||
let ntfMsgInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
||||
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfMsgInfo), privacy: .public)")
|
||||
if let connEntity = ntfMsgInfo.connEntity {
|
||||
setBestAttemptNtf(createConnectionEventNtf(connEntity))
|
||||
if let id = connEntity.id {
|
||||
Task {
|
||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
await PendingNtfs.shared.readStream(id, for: self, msgCount: ntfMsgInfo.ntfMessages.count)
|
||||
deliverBestAttemptNtf()
|
||||
let ntfMsgInfos = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
||||
for ntfMsgInfo in ntfMsgInfos {
|
||||
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfMsgInfo), privacy: .public)")
|
||||
if let connEntity = ntfMsgInfo.connEntity {
|
||||
setBestAttemptNtf(createConnectionEventNtf(ntfMsgInfo.user, connEntity))
|
||||
if let id = connEntity.id {
|
||||
Task {
|
||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
|
||||
await PendingNtfs.shared.createStream(id)
|
||||
await PendingNtfs.shared.readStream(id, for: self, msgCount: ntfMsgInfo.ntfMessages.count)
|
||||
deliverBestAttemptNtf()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
} else {
|
||||
setBestAttemptNtf(createErrorNtf(dbStatus))
|
||||
}
|
||||
@@ -209,13 +211,13 @@ func chatRecvMsg() async -> ChatResponse? {
|
||||
func receivedMsgNtf(_ res: ChatResponse) async -> (String, UNMutableNotificationContent)? {
|
||||
logger.debug("NotificationService processReceivedMsg: \(res.responseType)")
|
||||
switch res {
|
||||
case let .contactConnected(contact, _):
|
||||
return (contact.id, createContactConnectedNtf(contact))
|
||||
case let .contactConnected(user, contact, _):
|
||||
return (contact.id, createContactConnectedNtf(user, contact))
|
||||
// case let .contactConnecting(contact):
|
||||
// TODO profile update
|
||||
case let .receivedContactRequest(contactRequest):
|
||||
return (UserContact(contactRequest: contactRequest).id, createContactRequestNtf(contactRequest))
|
||||
case let .newChatItem(aChatItem):
|
||||
case let .receivedContactRequest(user, contactRequest):
|
||||
return (UserContact(contactRequest: contactRequest).id, createContactRequestNtf(user, contactRequest))
|
||||
case let .newChatItem(user, aChatItem):
|
||||
let cInfo = aChatItem.chatInfo
|
||||
var cItem = aChatItem.chatItem
|
||||
if case .image = cItem.content.msgContent {
|
||||
@@ -234,7 +236,7 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, UNMutableNotification
|
||||
cItem = apiReceiveFile(fileId: file.fileId, inline: inline)?.chatItem ?? cItem
|
||||
}
|
||||
}
|
||||
return cItem.showMutableNotification ? (aChatItem.chatId, createMessageReceivedNtf(cInfo, cItem)) : nil
|
||||
return cItem.showMutableNotification ? (aChatItem.chatId, createMessageReceivedNtf(user, cInfo, cItem)) : nil
|
||||
case let .callInvitation(invitation):
|
||||
return (invitation.contact.id, createCallInvitationNtf(invitation))
|
||||
default:
|
||||
@@ -256,12 +258,21 @@ func updateNetCfg() {
|
||||
}
|
||||
}
|
||||
|
||||
func listUsers() -> [UserInfo] {
|
||||
let r = sendSimpleXCmd(.listUsers)
|
||||
logger.debug("listUsers sendSimpleXCmd response: \(String(describing: r))")
|
||||
switch r {
|
||||
case let .usersList(users): return users
|
||||
default: return []
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetActiveUser() -> User? {
|
||||
let r = sendSimpleXCmd(.showActiveUser)
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd responce: \(String(describing: r))")
|
||||
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(String(describing: r))")
|
||||
switch r {
|
||||
case let .activeUser(user): return user
|
||||
case .chatCmdError(.error(.noActiveUser)): return nil
|
||||
case .chatCmdError(_, .error(.noActiveUser)): return nil
|
||||
default:
|
||||
logger.error("NotificationService apiGetActiveUser unexpected response: \(String(describing: r))")
|
||||
return nil
|
||||
@@ -289,22 +300,26 @@ func apiSetIncognito(incognito: Bool) throws {
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
||||
guard let user = apiGetActiveUser() else {
|
||||
logger.debug("no active user")
|
||||
return nil
|
||||
func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> [NtfMessages]? {
|
||||
let users = listUsers()
|
||||
if users.isEmpty {
|
||||
logger.debug("no users")
|
||||
return []
|
||||
}
|
||||
let r = sendSimpleXCmd(.apiGetNtfMessage(userId: user.userId, nonce: nonce, encNtfInfo: encNtfInfo))
|
||||
if case let .ntfMessages(connEntity, msgTs, ntfMessages) = r {
|
||||
return NtfMessages(connEntity: connEntity, msgTs: msgTs, ntfMessages: ntfMessages)
|
||||
var result: [NtfMessages] = []
|
||||
users.forEach {
|
||||
let r = sendSimpleXCmd(.apiGetNtfMessage(userId: $0.user.userId, nonce: nonce, encNtfInfo: encNtfInfo))
|
||||
if case let .ntfMessages(user, connEntity, msgTs, ntfMessages) = r {
|
||||
result.append(NtfMessages(user: user, connEntity: connEntity, msgTs: msgTs, ntfMessages: ntfMessages))
|
||||
}
|
||||
logger.debug("apiGetNtfMessage ignored response: \(String.init(describing: r), privacy: .public)")
|
||||
}
|
||||
logger.debug("apiGetNtfMessage ignored response: \(String.init(describing: r), privacy: .public)")
|
||||
return nil
|
||||
return result
|
||||
}
|
||||
|
||||
func apiReceiveFile(fileId: Int64, inline: Bool) -> AChatItem? {
|
||||
let r = sendSimpleXCmd(.receiveFile(fileId: fileId, inline: inline))
|
||||
if case let .rcvFileAccepted(chatItem) = r { return chatItem }
|
||||
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
|
||||
logger.error("receiveFile error: \(responseError(r))")
|
||||
return nil
|
||||
}
|
||||
@@ -316,6 +331,7 @@ func setNetworkConfig(_ cfg: NetCfg) throws {
|
||||
}
|
||||
|
||||
struct NtfMessages {
|
||||
var user: User
|
||||
var connEntity: ConnectionEntity?
|
||||
var msgTs: Date?
|
||||
var ntfMessages: [NtfMsgInfo]
|
||||
|
||||
@@ -134,6 +134,7 @@ public func chatResponse(_ s: String) -> ChatResponse {
|
||||
type = jResp.allKeys[0] as? String
|
||||
if type == "apiChats" {
|
||||
if let jApiChats = jResp["apiChats"] as? NSDictionary,
|
||||
let user: User = try? decodeObject(jApiChats["user"] as Any),
|
||||
let jChats = jApiChats["chats"] as? NSArray {
|
||||
let chats = jChats.map { jChat in
|
||||
if let chatData = try? parseChatData(jChat) {
|
||||
@@ -141,13 +142,14 @@ public func chatResponse(_ s: String) -> ChatResponse {
|
||||
}
|
||||
return ChatData.invalidJSON(prettyJSON(jChat) ?? "")
|
||||
}
|
||||
return .apiChats(chats: chats)
|
||||
return .apiChats(user: user, chats: chats)
|
||||
}
|
||||
} else if type == "apiChat" {
|
||||
if let jApiChat = jResp["apiChat"] as? NSDictionary,
|
||||
let user: User = try? decodeObject(jApiChat["user"] as Any),
|
||||
let jChat = jApiChat["chat"] as? NSDictionary,
|
||||
let chat = try? parseChatData(jChat) {
|
||||
return .apiChat(chat: chat)
|
||||
return .apiChat(user: user, chat: chat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,102 +316,102 @@ public enum ChatResponse: Decodable, Error {
|
||||
case chatRunning
|
||||
case chatStopped
|
||||
case chatSuspended
|
||||
case apiChats(chats: [ChatData])
|
||||
case apiChat(chat: ChatData)
|
||||
case userSMPServers(smpServers: [ServerCfg], presetSMPServers: [String])
|
||||
case smpTestResult(smpTestFailure: SMPTestFailure?)
|
||||
case chatItemTTL(chatItemTTL: Int64?)
|
||||
case apiChats(user: User, chats: [ChatData])
|
||||
case apiChat(user: User, chat: ChatData)
|
||||
case userSMPServers(user: User, smpServers: [ServerCfg], presetSMPServers: [String])
|
||||
case smpTestResult(user: User, smpTestFailure: SMPTestFailure?)
|
||||
case chatItemTTL(user: User, chatItemTTL: Int64?)
|
||||
case networkConfig(networkConfig: NetCfg)
|
||||
case contactInfo(contact: Contact, connectionStats: ConnectionStats, customUserProfile: Profile?)
|
||||
case groupMemberInfo(groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?)
|
||||
case contactCode(contact: Contact, connectionCode: String)
|
||||
case groupMemberCode(groupInfo: GroupInfo, member: GroupMember, connectionCode: String)
|
||||
case connectionVerified(verified: Bool, expectedCode: String)
|
||||
case invitation(connReqInvitation: String)
|
||||
case sentConfirmation
|
||||
case sentInvitation
|
||||
case contactAlreadyExists(contact: Contact)
|
||||
case contactDeleted(contact: Contact)
|
||||
case chatCleared(chatInfo: ChatInfo)
|
||||
case userProfileNoChange
|
||||
case userProfileUpdated(fromProfile: Profile, toProfile: Profile)
|
||||
case contactAliasUpdated(toContact: Contact)
|
||||
case connectionAliasUpdated(toConnection: PendingContactConnection)
|
||||
case contactPrefsUpdated(fromContact: Contact, toContact: Contact)
|
||||
case userContactLink(contactLink: UserContactLink)
|
||||
case userContactLinkUpdated(contactLink: UserContactLink)
|
||||
case userContactLinkCreated(connReqContact: String)
|
||||
case userContactLinkDeleted
|
||||
case contactConnected(contact: Contact, userCustomProfile: Profile?)
|
||||
case contactConnecting(contact: Contact)
|
||||
case receivedContactRequest(contactRequest: UserContactRequest)
|
||||
case acceptingContactRequest(contact: Contact)
|
||||
case contactRequestRejected
|
||||
case contactUpdated(toContact: Contact)
|
||||
case contactsSubscribed(server: String, contactRefs: [ContactRef])
|
||||
case contactsDisconnected(server: String, contactRefs: [ContactRef])
|
||||
case contactSubError(contact: Contact, chatError: ChatError)
|
||||
case contactSubSummary(contactSubscriptions: [ContactSubStatus])
|
||||
case groupSubscribed(groupInfo: GroupInfo)
|
||||
case memberSubErrors(memberSubErrors: [MemberSubError])
|
||||
case groupEmpty(groupInfo: GroupInfo)
|
||||
case contactInfo(user: User, contact: Contact, connectionStats: ConnectionStats, customUserProfile: Profile?)
|
||||
case groupMemberInfo(user: User, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?)
|
||||
case contactCode(user: User, contact: Contact, connectionCode: String)
|
||||
case groupMemberCode(user: User, groupInfo: GroupInfo, member: GroupMember, connectionCode: String)
|
||||
case connectionVerified(user: User, verified: Bool, expectedCode: String)
|
||||
case invitation(user: User, connReqInvitation: String)
|
||||
case sentConfirmation(user: User)
|
||||
case sentInvitation(user: User)
|
||||
case contactAlreadyExists(user: User, contact: Contact)
|
||||
case contactDeleted(user: User, contact: Contact)
|
||||
case chatCleared(user: User, chatInfo: ChatInfo)
|
||||
case userProfileNoChange(user: User)
|
||||
case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile)
|
||||
case contactAliasUpdated(user: User, toContact: Contact)
|
||||
case connectionAliasUpdated(user: User, toConnection: PendingContactConnection)
|
||||
case contactPrefsUpdated(user: User, fromContact: Contact, toContact: Contact)
|
||||
case userContactLink(user: User, contactLink: UserContactLink)
|
||||
case userContactLinkUpdated(user: User, contactLink: UserContactLink)
|
||||
case userContactLinkCreated(user: User, connReqContact: String)
|
||||
case userContactLinkDeleted(user: User)
|
||||
case contactConnected(user: User, contact: Contact, userCustomProfile: Profile?)
|
||||
case contactConnecting(user: User, contact: Contact)
|
||||
case receivedContactRequest(user: User, contactRequest: UserContactRequest)
|
||||
case acceptingContactRequest(user: User, contact: Contact)
|
||||
case contactRequestRejected(user: User)
|
||||
case contactUpdated(user: User, toContact: Contact)
|
||||
case contactsSubscribed(user: User, server: String, contactRefs: [ContactRef])
|
||||
case contactsDisconnected(user: User, server: String, contactRefs: [ContactRef])
|
||||
case contactSubError(user: User, contact: Contact, chatError: ChatError)
|
||||
case contactSubSummary(user: User, contactSubscriptions: [ContactSubStatus])
|
||||
case groupSubscribed(user: User, groupInfo: GroupInfo)
|
||||
case memberSubErrors(user: User, memberSubErrors: [MemberSubError])
|
||||
case groupEmpty(user: User, groupInfo: GroupInfo)
|
||||
case userContactLinkSubscribed
|
||||
case newChatItem(chatItem: AChatItem)
|
||||
case chatItemStatusUpdated(chatItem: AChatItem)
|
||||
case chatItemUpdated(chatItem: AChatItem)
|
||||
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool)
|
||||
case contactsList(contacts: [Contact])
|
||||
case newChatItem(user: User, chatItem: AChatItem)
|
||||
case chatItemStatusUpdated(user: User, chatItem: AChatItem)
|
||||
case chatItemUpdated(user: User, chatItem: AChatItem)
|
||||
case chatItemDeleted(user: User, deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool)
|
||||
case contactsList(user: User, contacts: [Contact])
|
||||
// group events
|
||||
case groupCreated(groupInfo: GroupInfo)
|
||||
case sentGroupInvitation(groupInfo: GroupInfo, contact: Contact, member: GroupMember)
|
||||
case userAcceptedGroupSent(groupInfo: GroupInfo, hostContact: Contact?)
|
||||
case userDeletedMember(groupInfo: GroupInfo, member: GroupMember)
|
||||
case leftMemberUser(groupInfo: GroupInfo)
|
||||
case groupMembers(group: Group)
|
||||
case receivedGroupInvitation(groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole)
|
||||
case groupDeletedUser(groupInfo: GroupInfo)
|
||||
case joinedGroupMemberConnecting(groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember)
|
||||
case memberRole(groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case memberRoleUser(groupInfo: GroupInfo, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case deletedMemberUser(groupInfo: GroupInfo, member: GroupMember)
|
||||
case deletedMember(groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember)
|
||||
case leftMember(groupInfo: GroupInfo, member: GroupMember)
|
||||
case groupDeleted(groupInfo: GroupInfo, member: GroupMember)
|
||||
case contactsMerged(intoContact: Contact, mergedContact: Contact)
|
||||
case groupInvitation(groupInfo: GroupInfo) // unused
|
||||
case userJoinedGroup(groupInfo: GroupInfo)
|
||||
case joinedGroupMember(groupInfo: GroupInfo, member: GroupMember)
|
||||
case connectedToGroupMember(groupInfo: GroupInfo, member: GroupMember)
|
||||
case groupRemoved(groupInfo: GroupInfo) // unused
|
||||
case groupUpdated(toGroup: GroupInfo)
|
||||
case groupLinkCreated(groupInfo: GroupInfo, connReqContact: String)
|
||||
case groupLink(groupInfo: GroupInfo, connReqContact: String)
|
||||
case groupLinkDeleted(groupInfo: GroupInfo)
|
||||
case groupCreated(user: User, groupInfo: GroupInfo)
|
||||
case sentGroupInvitation(user: User, groupInfo: GroupInfo, contact: Contact, member: GroupMember)
|
||||
case userAcceptedGroupSent(user: User, groupInfo: GroupInfo, hostContact: Contact?)
|
||||
case userDeletedMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case leftMemberUser(user: User, groupInfo: GroupInfo)
|
||||
case groupMembers(user: User, group: Group)
|
||||
case receivedGroupInvitation(user: User, groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole)
|
||||
case groupDeletedUser(user: User, groupInfo: GroupInfo)
|
||||
case joinedGroupMemberConnecting(user: User, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember)
|
||||
case memberRole(user: User, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case memberRoleUser(user: User, groupInfo: GroupInfo, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
|
||||
case deletedMemberUser(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case deletedMember(user: User, groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember)
|
||||
case leftMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case groupDeleted(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case contactsMerged(user: User, intoContact: Contact, mergedContact: Contact)
|
||||
case groupInvitation(user: User, groupInfo: GroupInfo) // unused
|
||||
case userJoinedGroup(user: User, groupInfo: GroupInfo)
|
||||
case joinedGroupMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case connectedToGroupMember(user: User, groupInfo: GroupInfo, member: GroupMember)
|
||||
case groupRemoved(user: User, groupInfo: GroupInfo) // unused
|
||||
case groupUpdated(user: User, toGroup: GroupInfo)
|
||||
case groupLinkCreated(user: User, groupInfo: GroupInfo, connReqContact: String)
|
||||
case groupLink(user: User, groupInfo: GroupInfo, connReqContact: String)
|
||||
case groupLinkDeleted(user: User, groupInfo: GroupInfo)
|
||||
// receiving file events
|
||||
case rcvFileAccepted(chatItem: AChatItem)
|
||||
case rcvFileAcceptedSndCancelled(rcvFileTransfer: RcvFileTransfer)
|
||||
case rcvFileStart(chatItem: AChatItem)
|
||||
case rcvFileComplete(chatItem: AChatItem)
|
||||
case rcvFileAccepted(user: User, chatItem: AChatItem)
|
||||
case rcvFileAcceptedSndCancelled(user: User, rcvFileTransfer: RcvFileTransfer)
|
||||
case rcvFileStart(user: User, chatItem: AChatItem)
|
||||
case rcvFileComplete(user: User, chatItem: AChatItem)
|
||||
// sending file events
|
||||
case sndFileStart(chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileComplete(chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileStart(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileComplete(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileCancelled(chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileRcvCancelled(chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndGroupFileCancelled(chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer])
|
||||
case sndFileRcvCancelled(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndGroupFileCancelled(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer])
|
||||
case callInvitation(callInvitation: RcvCallInvitation)
|
||||
case callOffer(contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool)
|
||||
case callAnswer(contact: Contact, answer: WebRTCSession)
|
||||
case callExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo)
|
||||
case callEnded(contact: Contact)
|
||||
case callOffer(user: User, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool)
|
||||
case callAnswer(user: User, contact: Contact, answer: WebRTCSession)
|
||||
case callExtraInfo(user: User, contact: Contact, extraInfo: WebRTCExtraInfo)
|
||||
case callEnded(user: User, contact: Contact)
|
||||
case callInvitations(callInvitations: [RcvCallInvitation])
|
||||
case ntfTokenStatus(status: NtfTknStatus)
|
||||
case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode)
|
||||
case ntfMessages(connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
|
||||
case newContactConnection(connection: PendingContactConnection)
|
||||
case contactConnectionDeleted(connection: PendingContactConnection)
|
||||
case cmdOk
|
||||
case chatCmdError(chatError: ChatError)
|
||||
case chatError(chatError: ChatError)
|
||||
case ntfMessages(user: User, connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
|
||||
case newContactConnection(user: User, connection: PendingContactConnection)
|
||||
case contactConnectionDeleted(user: User, connection: PendingContactConnection)
|
||||
case cmdOk(user: User?)
|
||||
case chatCmdError(user: User?, chatError: ChatError)
|
||||
case chatError(user: User?, chatError: ChatError)
|
||||
|
||||
public var responseType: String {
|
||||
get {
|
||||
@@ -530,104 +530,111 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .chatRunning: return noDetails
|
||||
case .chatStopped: return noDetails
|
||||
case .chatSuspended: return noDetails
|
||||
case let .apiChats(chats): return String(describing: chats)
|
||||
case let .apiChat(chat): return String(describing: chat)
|
||||
case let .userSMPServers(smpServers, presetServers): return "smpServers: \(String(describing: smpServers))\npresetServers: \(String(describing: presetServers))"
|
||||
case let .smpTestResult(smpTestFailure): return String(describing: smpTestFailure)
|
||||
case let .chatItemTTL(chatItemTTL): return String(describing: chatItemTTL)
|
||||
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
|
||||
case let .apiChat(u, chat): return withUser(u, String(describing: chat))
|
||||
case let .userSMPServers(u, smpServers, presetServers): return withUser(u, "smpServers: \(String(describing: smpServers))\npresetServers: \(String(describing: presetServers))")
|
||||
case let .smpTestResult(u, smpTestFailure): return withUser(u, String(describing: smpTestFailure))
|
||||
case let .chatItemTTL(u, chatItemTTL): return withUser(u, String(describing: chatItemTTL))
|
||||
case let .networkConfig(networkConfig): return String(describing: networkConfig)
|
||||
case let .contactInfo(contact, connectionStats, customUserProfile): return "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))\ncustomUserProfile: \(String(describing: customUserProfile))"
|
||||
case let .groupMemberInfo(groupInfo, member, connectionStats_): return "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_)))"
|
||||
case let .contactCode(contact, connectionCode): return "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)"
|
||||
case let .groupMemberCode(groupInfo, member, connectionCode): return "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)"
|
||||
case let .connectionVerified(verified, expectedCode): return "verified: \(verified)\nconnectionCode: \(expectedCode)"
|
||||
case let .invitation(connReqInvitation): return connReqInvitation
|
||||
case let .contactInfo(u, contact, connectionStats, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))\ncustomUserProfile: \(String(describing: customUserProfile))")
|
||||
case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_)))")
|
||||
case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)")
|
||||
case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)")
|
||||
case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)")
|
||||
case let .invitation(u, connReqInvitation): return withUser(u, connReqInvitation)
|
||||
case .sentConfirmation: return noDetails
|
||||
case .sentInvitation: return noDetails
|
||||
case let .contactAlreadyExists(contact): return String(describing: contact)
|
||||
case let .contactDeleted(contact): return String(describing: contact)
|
||||
case let .chatCleared(chatInfo): return String(describing: chatInfo)
|
||||
case let .contactAlreadyExists(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo))
|
||||
case .userProfileNoChange: return noDetails
|
||||
case let .userProfileUpdated(_, toProfile): return String(describing: toProfile)
|
||||
case let .contactAliasUpdated(toContact): return String(describing: toContact)
|
||||
case let .connectionAliasUpdated(toConnection): return String(describing: toConnection)
|
||||
case let .contactPrefsUpdated(fromContact, toContact): return "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))"
|
||||
case let .userContactLink(contactLink): return contactLink.responseDetails
|
||||
case let .userContactLinkUpdated(contactLink): return contactLink.responseDetails
|
||||
case let .userContactLinkCreated(connReq): return connReq
|
||||
case let .userProfileUpdated(u, _, toProfile): return withUser(u, String(describing: toProfile))
|
||||
case let .contactAliasUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .connectionAliasUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
|
||||
case let .contactPrefsUpdated(u, fromContact, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\ntoContact: \(String(describing: toContact))")
|
||||
case let .userContactLink(u, contactLink): return withUser(u, contactLink.responseDetails)
|
||||
case let .userContactLinkUpdated(u, contactLink): return withUser(u, contactLink.responseDetails)
|
||||
case let .userContactLinkCreated(u, connReq): return withUser(u, connReq)
|
||||
case .userContactLinkDeleted: return noDetails
|
||||
case let .contactConnected(contact, _): return String(describing: contact)
|
||||
case let .contactConnecting(contact): return String(describing: contact)
|
||||
case let .receivedContactRequest(contactRequest): return String(describing: contactRequest)
|
||||
case let .acceptingContactRequest(contact): return String(describing: contact)
|
||||
case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact))
|
||||
case let .contactConnecting(u, contact): return withUser(u, String(describing: contact))
|
||||
case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest))
|
||||
case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact))
|
||||
case .contactRequestRejected: return noDetails
|
||||
case let .contactUpdated(toContact): return String(describing: toContact)
|
||||
case let .contactsSubscribed(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactsDisconnected(server, contactRefs): return "server: \(server)\ncontacts:\n\(String(describing: contactRefs))"
|
||||
case let .contactSubError(contact, chatError): return "contact:\n\(String(describing: contact))\nerror:\n\(String(describing: chatError))"
|
||||
case let .contactSubSummary(contactSubscriptions): return String(describing: contactSubscriptions)
|
||||
case let .groupSubscribed(groupInfo): return String(describing: groupInfo)
|
||||
case let .memberSubErrors(memberSubErrors): return String(describing: memberSubErrors)
|
||||
case let .groupEmpty(groupInfo): return String(describing: groupInfo)
|
||||
case let .contactUpdated(u, toContact): return withUser(u, String(describing: toContact))
|
||||
case let .contactsSubscribed(u, server, contactRefs): return withUser(u, "server: \(server)\ncontacts:\n\(String(describing: contactRefs))")
|
||||
case let .contactsDisconnected(u, server, contactRefs): return withUser(u, "server: \(server)\ncontacts:\n\(String(describing: contactRefs))")
|
||||
case let .contactSubError(u, contact, chatError): return withUser(u, "contact:\n\(String(describing: contact))\nerror:\n\(String(describing: chatError))")
|
||||
case let .contactSubSummary(u, contactSubscriptions): return withUser(u, String(describing: contactSubscriptions))
|
||||
case let .groupSubscribed(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .memberSubErrors(u, memberSubErrors): return withUser(u, String(describing: memberSubErrors))
|
||||
case let .groupEmpty(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case .userContactLinkSubscribed: return noDetails
|
||||
case let .newChatItem(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemStatusUpdated(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemUpdated(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemDeleted(deletedChatItem, toChatItem, byUser): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)"
|
||||
case let .contactsList(contacts): return String(describing: contacts)
|
||||
case let .groupCreated(groupInfo): return String(describing: groupInfo)
|
||||
case let .sentGroupInvitation(groupInfo, contact, member): return "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)"
|
||||
case let .userAcceptedGroupSent(groupInfo, hostContact): return "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))"
|
||||
case let .userDeletedMember(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .leftMemberUser(groupInfo): return String(describing: groupInfo)
|
||||
case let .groupMembers(group): return String(describing: group)
|
||||
case let .receivedGroupInvitation(groupInfo, contact, memberRole): return "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)"
|
||||
case let .groupDeletedUser(groupInfo): return String(describing: groupInfo)
|
||||
case let .joinedGroupMemberConnecting(groupInfo, hostMember, member): return "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)"
|
||||
case let .memberRole(groupInfo, byMember, member, fromRole, toRole): return "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)"
|
||||
case let .memberRoleUser(groupInfo, member, fromRole, toRole): return "groupInfo: \(groupInfo)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)"
|
||||
case let .deletedMemberUser(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .deletedMember(groupInfo, byMember, deletedMember): return "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)"
|
||||
case let .leftMember(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .groupDeleted(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .contactsMerged(intoContact, mergedContact): return "intoContact: \(intoContact)\nmergedContact: \(mergedContact)"
|
||||
case let .groupInvitation(groupInfo): return String(describing: groupInfo)
|
||||
case let .userJoinedGroup(groupInfo): return String(describing: groupInfo)
|
||||
case let .joinedGroupMember(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .connectedToGroupMember(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)"
|
||||
case let .groupRemoved(groupInfo): return String(describing: groupInfo)
|
||||
case let .groupUpdated(toGroup): return String(describing: toGroup)
|
||||
case let .groupLinkCreated(groupInfo, connReqContact): return "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)"
|
||||
case let .groupLink(groupInfo, connReqContact): return "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)"
|
||||
case let .groupLinkDeleted(groupInfo): return String(describing: groupInfo)
|
||||
case let .rcvFileAccepted(chatItem): return String(describing: chatItem)
|
||||
case let .newChatItem(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemStatusUpdated(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemUpdated(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .chatItemDeleted(u, deletedChatItem, toChatItem, byUser): return withUser(u, "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)")
|
||||
case let .contactsList(u, contacts): return withUser(u, String(describing: contacts))
|
||||
case let .groupCreated(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .sentGroupInvitation(u, groupInfo, contact, member): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)")
|
||||
case let .userAcceptedGroupSent(u, groupInfo, hostContact): return withUser(u, "groupInfo: \(groupInfo)\nhostContact: \(String(describing: hostContact))")
|
||||
case let .userDeletedMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .groupMembers(u, group): return withUser(u, String(describing: group))
|
||||
case let .receivedGroupInvitation(u, groupInfo, contact, memberRole): return withUser(u, "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)")
|
||||
case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)")
|
||||
case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
|
||||
case let .memberRoleUser(u, groupInfo, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
|
||||
case let .deletedMemberUser(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .deletedMember(u, groupInfo, byMember, deletedMember): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)")
|
||||
case let .leftMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .groupDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .contactsMerged(u, intoContact, mergedContact): return withUser(u, "intoContact: \(intoContact)\nmergedContact: \(mergedContact)")
|
||||
case let .groupInvitation(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .userJoinedGroup(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .joinedGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .connectedToGroupMember(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
|
||||
case let .groupRemoved(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
|
||||
case let .groupLinkCreated(u, groupInfo, connReqContact): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)")
|
||||
case let .groupLink(u, groupInfo, connReqContact): return withUser(u, "groupInfo: \(groupInfo)\nconnReqContact: \(connReqContact)")
|
||||
case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo))
|
||||
case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case .rcvFileAcceptedSndCancelled: return noDetails
|
||||
case let .rcvFileStart(chatItem): return String(describing: chatItem)
|
||||
case let .rcvFileComplete(chatItem): return String(describing: chatItem)
|
||||
case let .sndFileStart(chatItem, _): return String(describing: chatItem)
|
||||
case let .sndFileComplete(chatItem, _): return String(describing: chatItem)
|
||||
case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem))
|
||||
case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem))
|
||||
case let .sndFileCancelled(chatItem, _): return String(describing: chatItem)
|
||||
case let .sndFileRcvCancelled(chatItem, _): return String(describing: chatItem)
|
||||
case let .sndGroupFileCancelled(chatItem, _, _): return String(describing: chatItem)
|
||||
case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
|
||||
case let .sndGroupFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem))
|
||||
case let .callInvitation(inv): return String(describing: inv)
|
||||
case let .callOffer(contact, callType, offer, sharedKey, askConfirmation): return "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))"
|
||||
case let .callAnswer(contact, answer): return "contact: \(contact.id)\nanswer: \(String(describing: answer))"
|
||||
case let .callExtraInfo(contact, extraInfo): return "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))"
|
||||
case let .callEnded(contact): return "contact: \(contact.id)"
|
||||
case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))")
|
||||
case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))")
|
||||
case let .callExtraInfo(u, contact, extraInfo): return withUser(u, "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))")
|
||||
case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)")
|
||||
case let .callInvitations(invs): return String(describing: invs)
|
||||
case let .ntfTokenStatus(status): return String(describing: status)
|
||||
case let .ntfToken(token, status, ntfMode): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)"
|
||||
case let .ntfMessages(connEntity, msgTs, ntfMessages): return "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))"
|
||||
case let .newContactConnection(connection): return String(describing: connection)
|
||||
case let .contactConnectionDeleted(connection): return String(describing: connection)
|
||||
case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))")
|
||||
case let .newContactConnection(u, connection): return withUser(u, String(describing: connection))
|
||||
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
|
||||
case .cmdOk: return noDetails
|
||||
case let .chatCmdError(chatError): return String(describing: chatError)
|
||||
case let .chatError(chatError): return String(describing: chatError)
|
||||
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var noDetails: String { get { "\(responseType): no details" } }
|
||||
|
||||
private func withUser(_ u: User?, _ s: String) -> String {
|
||||
if let id = u?.userId {
|
||||
return "userId: \(id)\n\(s)"
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatPagination {
|
||||
|
||||
@@ -38,6 +38,7 @@ public struct WebRTCExtraInfo: Codable {
|
||||
}
|
||||
|
||||
public struct RcvCallInvitation: Decodable {
|
||||
public var user: User
|
||||
public var contact: Contact
|
||||
public var callkitUUID: UUID? = UUID()
|
||||
public var callType: CallType
|
||||
@@ -53,6 +54,7 @@ public struct RcvCallInvitation: Decodable {
|
||||
}
|
||||
|
||||
public static let sampleData = RcvCallInvitation(
|
||||
user: User.sampleData,
|
||||
contact: Contact.sampleData,
|
||||
callType: CallType(media: .audio, capabilities: CallCapabilities(encryption: false)),
|
||||
callTs: .now
|
||||
|
||||
@@ -21,7 +21,7 @@ public let appNotificationId = "chat.simplex.app.notification"
|
||||
|
||||
let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification")
|
||||
|
||||
public func createContactRequestNtf(_ contactRequest: UserContactRequest) -> UNMutableNotificationContent {
|
||||
public func createContactRequestNtf(_ user: User, _ contactRequest: UserContactRequest) -> UNMutableNotificationContent {
|
||||
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
|
||||
return createNotification(
|
||||
categoryIdentifier: ntfCategoryContactRequest,
|
||||
@@ -34,11 +34,11 @@ public func createContactRequestNtf(_ contactRequest: UserContactRequest) -> UNM
|
||||
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contactRequest.chatViewName
|
||||
),
|
||||
targetContentIdentifier: nil,
|
||||
userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId]
|
||||
userInfo: ["chatId": contactRequest.id, "contactRequestId": contactRequest.apiId, "userId": user.userId]
|
||||
)
|
||||
}
|
||||
|
||||
public func createContactConnectedNtf(_ contact: Contact) -> UNMutableNotificationContent {
|
||||
public func createContactConnectedNtf(_ user: User, _ contact: Contact) -> UNMutableNotificationContent {
|
||||
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
|
||||
return createNotification(
|
||||
categoryIdentifier: ntfCategoryContactConnected,
|
||||
@@ -50,12 +50,13 @@ public func createContactConnectedNtf(_ contact: Contact) -> UNMutableNotificati
|
||||
NSLocalizedString("You can now send messages to %@", comment: "notification body"),
|
||||
hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName
|
||||
),
|
||||
targetContentIdentifier: contact.id
|
||||
targetContentIdentifier: contact.id,
|
||||
userInfo: ["userId": user.userId]
|
||||
// userInfo: ["chatId": contact.id, "contactId": contact.apiId]
|
||||
)
|
||||
}
|
||||
|
||||
public func createMessageReceivedNtf(_ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent {
|
||||
public func createMessageReceivedNtf(_ user: User, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent {
|
||||
let previewMode = ntfPreviewModeGroupDefault.get()
|
||||
var title: String
|
||||
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
|
||||
@@ -67,7 +68,8 @@ public func createMessageReceivedNtf(_ cInfo: ChatInfo, _ cItem: ChatItem) -> UN
|
||||
categoryIdentifier: ntfCategoryMessageReceived,
|
||||
title: title,
|
||||
body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"),
|
||||
targetContentIdentifier: cInfo.id
|
||||
targetContentIdentifier: cInfo.id,
|
||||
userInfo: ["userId": user.userId]
|
||||
// userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id]
|
||||
)
|
||||
}
|
||||
@@ -82,11 +84,11 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation) -> UNMutabl
|
||||
title: hideContent ? contactHidden : "\(invitation.contact.chatViewName):",
|
||||
body: text,
|
||||
targetContentIdentifier: nil,
|
||||
userInfo: ["chatId": invitation.contact.id]
|
||||
userInfo: ["chatId": invitation.contact.id, "userId": invitation.user.userId]
|
||||
)
|
||||
}
|
||||
|
||||
public func createConnectionEventNtf(_ connEntity: ConnectionEntity) -> UNMutableNotificationContent {
|
||||
public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity) -> UNMutableNotificationContent {
|
||||
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
|
||||
var title: String
|
||||
var body: String? = nil
|
||||
@@ -115,7 +117,8 @@ public func createConnectionEventNtf(_ connEntity: ConnectionEntity) -> UNMutabl
|
||||
categoryIdentifier: ntfCategoryConnectionEvent,
|
||||
title: title,
|
||||
body: body,
|
||||
targetContentIdentifier: targetContentIdentifier
|
||||
targetContentIdentifier: targetContentIdentifier,
|
||||
userInfo: ["userId": user.userId]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user