17
17
import UserNotifications
18
18
import MatrixKit
19
19
import MatrixSDK
20
+ import Intents
20
21
21
22
/// The number of milliseconds in one second.
22
23
private let MSEC_PER_SEC : TimeInterval = 1000
@@ -296,6 +297,7 @@ class NotificationService: UNNotificationServiceExtension {
296
297
switch response {
297
298
case . success( let ( roomState, eventSenderName) ) :
298
299
var notificationTitle : String ?
300
+ var notificationSubTitle : String ?
299
301
var notificationBody : String ?
300
302
var additionalUserInfo : [ AnyHashable : Any ] ?
301
303
@@ -361,9 +363,12 @@ class NotificationService: UNNotificationServiceExtension {
361
363
let isReply = event. isReply ( )
362
364
363
365
if isReply {
364
- notificationTitle = self . replyTitle ( for: eventSenderName, in : roomDisplayName )
366
+ notificationTitle = self . replyTitle ( for: eventSenderName)
365
367
} else {
366
- notificationTitle = self . messageTitle ( for: eventSenderName, in: roomDisplayName)
368
+ notificationTitle = eventSenderName
369
+ }
370
+ if !( roomSummary? . isDirect ?? false ) {
371
+ notificationSubTitle = roomDisplayName
367
372
}
368
373
369
374
if event. isEncrypted && !self . showDecryptedContentInNotifications {
@@ -402,7 +407,10 @@ class NotificationService: UNNotificationServiceExtension {
402
407
// If the current user is already joined, display updated displayname/avatar events.
403
408
// This is an unexpected path, but has been seen in some circumstances.
404
409
if NotificationService . backgroundSyncService. roomSummary ( forRoomId: roomId) ? . membership == . join {
405
- notificationTitle = self . messageTitle ( for: eventSenderName, in: roomDisplayName)
410
+ notificationTitle = eventSenderName
411
+ if !( roomSummary? . isDirect ?? false ) {
412
+ notificationSubTitle = roomDisplayName
413
+ }
406
414
407
415
// If the sender's membership is join and hasn't changed.
408
416
if let newContent = MXRoomMemberEventContent ( fromJSON: event. content) ,
@@ -441,12 +449,18 @@ class NotificationService: UNNotificationServiceExtension {
441
449
}
442
450
443
451
case . sticker:
444
- notificationTitle = self . messageTitle ( for: eventSenderName, in: roomDisplayName)
452
+ notificationTitle = eventSenderName
453
+ if !( roomSummary? . isDirect ?? false ) {
454
+ notificationSubTitle = roomDisplayName
455
+ }
445
456
notificationBody = NSString . localizedUserNotificationString ( forKey: " STICKER_FROM_USER " , arguments: [ eventSenderName as Any ] )
446
457
447
458
// Reactions are unexpected notification types, but have been seen in some circumstances.
448
459
case . reaction:
449
- notificationTitle = self . messageTitle ( for: eventSenderName, in: roomDisplayName)
460
+ notificationTitle = eventSenderName
461
+ if !( roomSummary? . isDirect ?? false ) {
462
+ notificationSubTitle = roomDisplayName
463
+ }
450
464
if let reactionKey = event. relatesTo? . key {
451
465
// Try to show the reaction key in the notification.
452
466
notificationBody = NSString . localizedUserNotificationString ( forKey: " REACTION_FROM_USER " , arguments: [ eventSenderName, reactionKey] )
@@ -479,52 +493,48 @@ class NotificationService: UNNotificationServiceExtension {
479
493
MXLog . debug ( " [NotificationService] notificationContentForEvent: Resetting title and body because app protection is set " )
480
494
notificationBody = NSString . localizedUserNotificationString ( forKey: " MESSAGE_PROTECTED " , arguments: [ ] )
481
495
notificationTitle = nil
496
+ notificationSubTitle = nil
482
497
}
483
498
484
499
guard notificationBody != nil else {
485
500
MXLog . debug ( " [NotificationService] notificationContentForEvent: notificationBody is nil " )
486
501
onComplete ( nil )
487
502
return
488
503
}
489
-
504
+
490
505
let notificationContent = self . notificationContent ( withTitle: notificationTitle,
506
+ withSubTitle: notificationSubTitle,
491
507
body: notificationBody,
492
508
threadIdentifier: threadIdentifier,
493
509
userId: currentUserId,
494
510
event: event,
495
511
pushRule: pushRule,
496
512
additionalInfo: additionalUserInfo)
497
-
498
- MXLog . debug ( " [NotificationService] notificationContentForEvent: Calling onComplete. " )
499
- onComplete ( notificationContent)
513
+
514
+ if #available( iOS 15 . 0 , * ) {
515
+ self . makeCommunicationNotification (
516
+ forEvent: event,
517
+ // TODO: use real room/user avatar
518
+ senderImage: INImage . systemImageNamed ( " person.circle.fill " ) ,
519
+ body: notificationBody,
520
+ notificationContent: notificationContent,
521
+ roomDisplayName: roomDisplayName,
522
+ senderName: eventSenderName,
523
+ roomSummary: roomSummary,
524
+ onComplete: onComplete)
525
+ } else {
526
+ MXLog . debug ( " [NotificationService] notificationContentForEvent: Calling onComplete. " )
527
+ onComplete ( notificationContent)
528
+ }
500
529
case . failure( let error) :
501
530
MXLog . debug ( " [NotificationService] notificationContentForEvent: error: \( error) " )
502
531
onComplete ( nil )
503
532
}
504
533
} )
505
534
}
506
535
507
- /// Returns the default title for message notifications.
508
- /// - Parameters:
509
- /// - eventSenderName: The displayname of the sender.
510
- /// - roomDisplayName: The displayname of the room the message was sent in.
511
- /// - Returns: A string to be used for the notification's title.
512
- private func messageTitle( for eventSenderName: String , in roomDisplayName: String ? ) -> String {
513
- // Display the room name only if it is different than the sender name
514
- if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
515
- return NSString . localizedUserNotificationString ( forKey: " MSG_FROM_USER_IN_ROOM_TITLE " , arguments: [ eventSenderName, roomDisplayName] )
516
- } else {
517
- return eventSenderName
518
- }
519
- }
520
-
521
- private func replyTitle( for eventSenderName: String , in roomDisplayName: String ? ) -> String {
522
- // Display the room name only if it is different than the sender name
523
- if let roomDisplayName = roomDisplayName, roomDisplayName != eventSenderName {
524
- return NSString . localizedUserNotificationString ( forKey: " REPLY_FROM_USER_IN_ROOM_TITLE " , arguments: [ eventSenderName, roomDisplayName] )
525
- } else {
526
- return NSString . localizedUserNotificationString ( forKey: " REPLY_FROM_USER_TITLE " , arguments: [ eventSenderName] )
527
- }
536
+ private func replyTitle( for eventSenderName: String ) -> String {
537
+ return NSString . localizedUserNotificationString ( forKey: " REPLY_FROM_USER_TITLE " , arguments: [ eventSenderName] )
528
538
}
529
539
530
540
/// Get the context of an event.
@@ -570,6 +580,7 @@ class NotificationService: UNNotificationServiceExtension {
570
580
}
571
581
572
582
private func notificationContent( withTitle title: String ? ,
583
+ withSubTitle subTitle: String ? ,
573
584
body: String ? ,
574
585
threadIdentifier: String ? ,
575
586
userId: String ? ,
@@ -581,6 +592,9 @@ class NotificationService: UNNotificationServiceExtension {
581
592
if let title = title {
582
593
notificationContent. title = title
583
594
}
595
+ if let subTitle = subTitle {
596
+ notificationContent. subtitle = subTitle
597
+ }
584
598
if let body = body {
585
599
notificationContent. body = body
586
600
}
@@ -600,6 +614,106 @@ class NotificationService: UNNotificationServiceExtension {
600
614
return notificationContent
601
615
}
602
616
617
+ @available ( iOS 15 , * )
618
+ private func makeCommunicationNotification( forEvent event: MXEvent ,
619
+ senderImage: INImage ? ,
620
+ body notificationBody: String ? ,
621
+ notificationContent: UNNotificationContent ,
622
+ roomDisplayName: String ? ,
623
+ senderName: String ,
624
+ roomSummary: MXRoomSummary ? ,
625
+ onComplete: @escaping ( UNNotificationContent ? ) -> Void ) {
626
+ switch event. eventType {
627
+ case . roomMessage:
628
+ let intent = self . getMessageIntent (
629
+ forEvent: event,
630
+ body: notificationBody ?? " " ,
631
+ groupName: roomSummary? . isDirect ?? true ? nil : roomDisplayName,
632
+ senderName: senderName,
633
+ senderImage: senderImage)
634
+ intent. setImage ( senderImage, forParameterNamed: \. sender)
635
+ do {
636
+ // TODO: figure out how to set _mentionsCurrentUser and _replyToCurrentUser in the context
637
+ let notificationContent = try notificationContent. updating ( from: intent)
638
+ MXLog . debug ( " [NotificationService] makeCommunicationNotification: Calling onComplete. " )
639
+ onComplete ( notificationContent)
640
+ } catch {
641
+ MXLog . debug ( " [NotificationService] makeCommunicationNotification: error (roomMessage): \( error) " )
642
+ onComplete ( notificationContent)
643
+ }
644
+ default :
645
+ onComplete ( notificationContent)
646
+ }
647
+ }
648
+
649
+
650
+ @available ( iOS 15 , * )
651
+ private func getMessageIntent( forEvent event: MXEvent ,
652
+ body: String ,
653
+ groupName: String ? ,
654
+ senderName: String ,
655
+ senderImage: INImage ? ) -> INSendMessageIntent {
656
+ let intentType = getMessageIntentType ( event. content [ " msgtype " ] as? String , event)
657
+
658
+ let speakableGroupName = groupName. flatMap ( { INSpeakableString ( spokenPhrase: $0 ) } )
659
+
660
+ let sender = INPerson (
661
+ personHandle: INPersonHandle ( value: event. sender, type: . unknown) ,
662
+ nameComponents: nil ,
663
+ displayName: senderName,
664
+ image: senderImage,
665
+ contactIdentifier: nil ,
666
+ customIdentifier: event. sender,
667
+ isContactSuggestion: false ,
668
+ suggestionType: . instantMessageAddress
669
+ )
670
+
671
+ let incomingMessageIntent = INSendMessageIntent (
672
+ // TODO: check
673
+ recipients: [ ] ,
674
+ outgoingMessageType: intentType,
675
+ content: body,
676
+ speakableGroupName: speakableGroupName,
677
+ conversationIdentifier: event. roomId,
678
+ // TODO: check
679
+ serviceName: nil ,
680
+ sender: sender,
681
+ attachments: nil
682
+ )
683
+
684
+ let intent = INInteraction ( intent: incomingMessageIntent, response: nil )
685
+ intent. direction = . incoming
686
+
687
+ intent. donate ( completion: nil )
688
+
689
+ return incomingMessageIntent
690
+ }
691
+
692
+ @available ( iOS 14 , * )
693
+ private func getMessageIntentType( _ msgType: String ? , _ event: MXEvent ) -> INOutgoingMessageType {
694
+ guard let msgType = msgType else {
695
+ return . unknown
696
+ }
697
+
698
+ switch msgType {
699
+ case kMXMessageTypeEmote:
700
+ fallthrough
701
+ case kMXMessageTypeImage:
702
+ fallthrough
703
+ case kMXMessageTypeVideo:
704
+ fallthrough
705
+ case kMXMessageTypeFile:
706
+ return . unknown
707
+ case kMXMessageTypeAudio:
708
+ if event. isVoiceMessage ( ) {
709
+ return . outgoingMessageAudio
710
+ }
711
+ return . unknown
712
+ default :
713
+ return . outgoingMessageText
714
+ }
715
+ }
716
+
603
717
private func notificationUserInfo( forEvent event: MXEvent ,
604
718
andUserId userId: String ? ,
605
719
additionalInfo: [ AnyHashable : Any ] ? = nil ) -> [ AnyHashable : Any ] {
0 commit comments