ios: asynchronous api calls when entering foreground (#4710)

* ios: get call invitations asynchronously

* async update chats

* async user list on appear

* move model changes to main thread

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Arturs Krumins
2024-08-20 13:28:06 +03:00
committed by GitHub
parent 70991debfd
commit b52dfee078
4 changed files with 50 additions and 41 deletions

View File

@@ -1218,8 +1218,8 @@ func apiEndCall(_ contact: Contact) async throws {
try await sendCommandOkResp(.apiEndCall(contact: contact))
}
func apiGetCallInvitations() throws -> [RcvCallInvitation] {
let r = chatSendCmdSync(.apiGetCallInvitations)
func apiGetCallInvitations() async throws -> [RcvCallInvitation] {
let r = await chatSendCmd(.apiGetCallInvitations)
if case let .callInvitations(invs) = r { return invs }
throw r
}
@@ -1517,7 +1517,7 @@ func startChat(refreshInvitations: Bool = true) throws {
try getUserChatData()
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCountForAllUsers())
if (refreshInvitations) {
try refreshCallInvitations()
Task { try await refreshCallInvitations() }
}
(m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken()
_ = try apiStartChat()
@@ -2161,22 +2161,25 @@ func chatItemSimpleUpdate(_ user: any UserLike, _ aChatItem: AChatItem) async {
}
}
func refreshCallInvitations() throws {
func refreshCallInvitations() async throws {
let m = ChatModel.shared
let callInvitations = try justRefreshCallInvitations()
let callInvitations = try await justRefreshCallInvitations()
if let (chatId, ntfAction) = m.ntfCallInvitationAction,
let invitation = m.callInvitations.removeValue(forKey: chatId) {
m.ntfCallInvitationAction = nil
await MainActor.run { m.ntfCallInvitationAction = nil }
CallController.shared.callAction(invitation: invitation, action: ntfAction)
} else if let invitation = callInvitations.last(where: { $0.user.showNotifications }) {
activateCall(invitation)
}
}
func justRefreshCallInvitations() throws -> [RcvCallInvitation] {
func justRefreshCallInvitations() async throws -> [RcvCallInvitation] {
let m = ChatModel.shared
let callInvitations = try apiGetCallInvitations()
m.callInvitations = callInvitations.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
let callInvitations = try await apiGetCallInvitations()
await MainActor.run {
m.callInvitations = callInvitations
.reduce(into: [ChatId: RcvCallInvitation]()) { result, inv in result[inv.contact.id] = inv }
}
return callInvitations
}

View File

@@ -83,9 +83,11 @@ struct SimpleXApp: App {
if appState != .stopped {
startChatAndActivate {
if appState.inactive && chatModel.chatRunning == true {
updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
updateCallInvitations()
Task {
await updateChats()
if !chatModel.showCallView && !CallController.shared.hasActiveCalls() {
await updateCallInvitations()
}
}
}
}
@@ -130,16 +132,16 @@ struct SimpleXApp: App {
}
}
private func updateChats() {
private func updateChats() async {
do {
let chats = try apiGetChats()
chatModel.updateChats(chats)
let chats = try await apiGetChatsAsync()
await MainActor.run { chatModel.updateChats(chats) }
if let id = chatModel.chatId,
let chat = chatModel.getChat(id) {
Task { await loadChat(chat: chat, clearItems: false) }
}
if let ncr = chatModel.ntfContactRequest {
chatModel.ntfContactRequest = nil
await MainActor.run { chatModel.ntfContactRequest = nil }
if case let .contactRequest(contactRequest) = chatModel.getChat(ncr.chatId)?.chatInfo {
Task { await acceptContactRequest(incognito: ncr.incognito, contactRequest: contactRequest) }
}
@@ -149,9 +151,9 @@ struct SimpleXApp: App {
}
}
private func updateCallInvitations() {
private func updateCallInvitations() async {
do {
try refreshCallInvitations()
try await refreshCallInvitations()
} catch let error {
logger.error("apiGetCallInvitations: cannot update call invitations \(responseError(error))")
}

View File

@@ -186,29 +186,30 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
logger.debug("CallController: started chat")
self.shouldSuspendChat = true
// There are no invitations in the model, as it was processed by NSE
_ = try? justRefreshCallInvitations()
logger.debug("CallController: updated call invitations chat")
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload
let m = ChatModel.shared
if let contactId = payload.dictionaryPayload["contactId"] as? String,
let invitation = m.callInvitations[contactId] {
let update = self.cxCallUpdate(invitation: invitation)
if let uuid = invitation.callkitUUID {
logger.debug("CallController: report pushkit call via CallKit")
Task {
_ = try? await justRefreshCallInvitations()
logger.debug("CallController: updated call invitations chat")
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
// Extract the call information from the push notification payload
let m = ChatModel.shared
if let contactId = payload.dictionaryPayload["contactId"] as? String,
let invitation = m.callInvitations[contactId] {
let update = self.cxCallUpdate(invitation: invitation)
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error != nil {
m.callInvitations.removeValue(forKey: contactId)
if let uuid = invitation.callkitUUID {
logger.debug("CallController: report pushkit call via CallKit")
let update = self.cxCallUpdate(invitation: invitation)
do {
try await self.provider.reportNewIncomingCall(with: uuid, update: update)
} catch {
_ = await MainActor.run { m.callInvitations.removeValue(forKey: contactId) }
}
// Tell PushKit that the notification is handled.
completion()
} else {
self.reportExpiredCall(update: update, completion)
}
} else {
self.reportExpiredCall(update: update, completion)
self.reportExpiredCall(payload: payload, completion)
}
} else {
self.reportExpiredCall(payload: payload, completion)
}
}

View File

@@ -85,13 +85,16 @@ struct UserPicker: View {
.padding(8)
.opacity(userPickerVisible ? 1.0 : 0.0)
.onAppear {
do {
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
if case .active = scenePhase {
m.users = try listUsers()
// This check prevents the call of listUsers after the app is suspended, and the database is closed.
if case .active = scenePhase {
Task {
do {
let users = try await listUsersAsync()
await MainActor.run { m.users = users }
} catch {
logger.error("Error loading users \(responseError(error))")
}
}
} catch let error {
logger.error("Error loading users \(responseError(error))")
}
}
}