Skip to content

Commit 2941d2c

Browse files
committed
Fix avatar on iOS videoconf
1 parent ddba383 commit 2941d2c

File tree

2 files changed

+95
-18
lines changed

2 files changed

+95
-18
lines changed

ios/NotificationService/NotificationService.swift

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@ class NotificationService: UNNotificationServiceExtension {
1010

1111
// MARK: - Avatar Fetching
1212

13+
/// Fetches avatar image data from a given avatar path
14+
private func fetchAvatarDataFromPath(avatarPath: String, server: String, credentials: Credentials, completion: @escaping (Data?) -> Void) {
15+
let fullPath = "\(avatarPath)?format=png&size=100&rc_token=\(credentials.userToken)&rc_uid=\(credentials.userId)"
16+
guard let avatarURL = URL(string: server + fullPath) else {
17+
completion(nil)
18+
return
19+
}
20+
21+
// Create request with 3-second timeout
22+
var request = URLRequest(url: avatarURL, timeoutInterval: 3)
23+
request.httpMethod = "GET"
24+
request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
25+
26+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
27+
guard error == nil,
28+
let httpResponse = response as? HTTPURLResponse,
29+
httpResponse.statusCode == 200,
30+
let data = data else {
31+
completion(nil)
32+
return
33+
}
34+
completion(data)
35+
}
36+
task.resume()
37+
}
38+
1339
/// Fetches avatar image data - sender's avatar for DMs, room avatar for groups/channels
1440
func fetchAvatarData(from payload: Payload, completion: @escaping (Data?) -> Void) {
1541
let server = payload.host.removeTrailingSlash()
@@ -39,28 +65,27 @@ class NotificationService: UNNotificationServiceExtension {
3965
avatarPath = "/avatar/room/\(rid)"
4066
}
4167

42-
let fullPath = "\(avatarPath)?format=png&size=100&rc_token=\(credentials.userToken)&rc_uid=\(credentials.userId)"
43-
guard let avatarURL = URL(string: server + fullPath) else {
68+
fetchAvatarDataFromPath(avatarPath: avatarPath, server: server, credentials: credentials, completion: completion)
69+
}
70+
71+
/// Fetches avatar image data for video conference caller
72+
func fetchCallerAvatarData(from payload: Payload, completion: @escaping (Data?) -> Void) {
73+
let server = payload.host.removeTrailingSlash()
74+
75+
guard let credentials = Storage().getCredentials(server: server) else {
4476
completion(nil)
4577
return
4678
}
4779

48-
// Create request with 3-second timeout
49-
var request = URLRequest(url: avatarURL, timeoutInterval: 3)
50-
request.httpMethod = "GET"
51-
request.addValue(Bundle.userAgent, forHTTPHeaderField: "User-Agent")
52-
53-
let task = URLSession.shared.dataTask(with: request) { data, response, error in
54-
guard error == nil,
55-
let httpResponse = response as? HTTPURLResponse,
56-
httpResponse.statusCode == 200,
57-
let data = data else {
58-
completion(nil)
59-
return
60-
}
61-
completion(data)
80+
// Check if caller has username (required - /avatar/{userId} endpoint doesn't exist)
81+
guard let username = payload.caller?.username,
82+
let encodedUsername = username.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
83+
completion(nil)
84+
return
6285
}
63-
task.resume()
86+
87+
let avatarPath = "/avatar/\(encodedUsername)"
88+
fetchAvatarDataFromPath(avatarPath: avatarPath, server: server, credentials: credentials, completion: completion)
6489
}
6590

6691
// MARK: - Communication Notification
@@ -225,16 +250,67 @@ class NotificationService: UNNotificationServiceExtension {
225250

226251
// Status 0 (or nil) means incoming call - show notification with actions
227252
let callerName = payload.caller?.name ?? "Unknown"
253+
let callerUsername = payload.caller?.username ?? ""
228254

229255
bestAttemptContent.title = NSLocalizedString("Video Call", comment: "")
230256
bestAttemptContent.body = String(format: NSLocalizedString("Incoming call from %@", comment: ""), callerName)
231257
bestAttemptContent.categoryIdentifier = "VIDEOCONF"
232258
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName("ringtone.mp3"))
233259
bestAttemptContent.interruptionLevel = .timeSensitive
234260

235-
contentHandler?(bestAttemptContent)
261+
// Fetch caller avatar if username is available
262+
fetchCallerAvatarData(from: payload) { [weak self] avatarData in
263+
guard let self = self, let bestAttemptContent = self.bestAttemptContent else {
264+
self?.contentHandler?(bestAttemptContent ?? UNNotificationContent())
265+
return
266+
}
267+
268+
// Create caller image from avatar data
269+
var callerImage: INImage? = nil
270+
if let data = avatarData {
271+
callerImage = INImage(imageData: data)
272+
}
273+
274+
// Create caller as INPerson with avatar (similar to regular message notifications)
275+
let caller = INPerson(
276+
personHandle: INPersonHandle(value: callerUsername, type: .unknown),
277+
nameComponents: nil,
278+
displayName: callerName,
279+
image: callerImage,
280+
contactIdentifier: nil,
281+
customIdentifier: nil
282+
)
283+
284+
// Use INSendMessageIntent to display avatar (works better than attachments for call notifications)
285+
// This uses the Communication Notifications API which properly displays avatars
286+
let intent = INSendMessageIntent(
287+
recipients: nil, // No recipients for incoming calls
288+
outgoingMessageType: .outgoingMessageText,
289+
content: bestAttemptContent.body,
290+
speakableGroupName: nil,
291+
conversationIdentifier: payload.rid ?? "",
292+
serviceName: nil,
293+
sender: caller, // Set caller as sender to display avatar
294+
attachments: nil
295+
)
296+
297+
// Donate the interaction for Siri suggestions
298+
let interaction = INInteraction(intent: intent, response: nil)
299+
interaction.direction = .incoming
300+
interaction.donate(completion: nil)
301+
302+
// Update the notification content with the intent to display avatar
303+
do {
304+
let updatedContent = try bestAttemptContent.updating(from: intent)
305+
self.contentHandler?(updatedContent)
306+
} catch {
307+
// Fallback to bestAttemptContent if intent update fails
308+
self.contentHandler?(bestAttemptContent)
309+
}
310+
}
236311
}
237312

313+
238314
func processPayload(payload: Payload) {
239315
// Set notification title based on payload type
240316
let senderName = payload.sender?.name ?? payload.senderName ?? "Unknown"

ios/Shared/Models/Payload.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Foundation
1111
struct Caller: Codable {
1212
let _id: String?
1313
let name: String?
14+
let username: String?
1415
}
1516

1617
struct Payload: Codable {

0 commit comments

Comments
 (0)