@@ -114,11 +114,13 @@ struct LockNotificationsData : Equatable {
114114final class SharedNotificationBindings {
115115 let navigateToChat : ( Account , PeerId ) -> Void
116116 let navigateToThread : ( Account , MessageId , MessageId ? , MessageHistoryThreadData ? ) -> Void // threadId, fromId
117+ let navigateToStory : ( Account , PeerId ) -> Void
117118 let updateCurrectController : ( ) -> Void
118119 let applyMaxReadIndexInteractively : ( MessageIndex ) -> Void
119- init ( navigateToChat : @escaping ( Account , PeerId ) -> Void , navigateToThread : @escaping ( Account , MessageId , MessageId ? , MessageHistoryThreadData ? ) -> Void , updateCurrectController : @escaping ( ) -> Void , applyMaxReadIndexInteractively : @escaping ( MessageIndex ) -> Void ) {
120+ init ( navigateToChat : @escaping ( Account , PeerId ) -> Void , navigateToThread : @escaping ( Account , MessageId , MessageId ? , MessageHistoryThreadData ? ) -> Void , navigateToStory : @escaping ( Account , PeerId ) -> Void , updateCurrectController : @escaping ( ) -> Void , applyMaxReadIndexInteractively : @escaping ( MessageIndex ) -> Void ) {
120121 self . navigateToChat = navigateToChat
121122 self . navigateToThread = navigateToThread
123+ self . navigateToStory = navigateToStory
122124 self . updateCurrectController = updateCurrectController
123125 self . applyMaxReadIndexInteractively = applyMaxReadIndexInteractively
124126 }
@@ -156,6 +158,7 @@ final class SharedNotificationManager : NSObject, NSUserNotificationCenterDelega
156158 }
157159
158160 private let disposableDict : DisposableDict < AccountRecordId > = DisposableDict ( )
161+ private let storyDisposableDict : DisposableDict < AccountRecordId > = DisposableDict ( )
159162 private let inAppSettingsDisposable = MetaDisposable ( )
160163 private let lockTimeoutDisposable = MetaDisposable ( )
161164 let accountManager : AccountManager < TelegramAccountManagerTypes >
@@ -256,6 +259,7 @@ final class SharedNotificationManager : NSObject, NSUserNotificationCenterDelega
256259 let activeIds = Set ( accounts . accounts . map { $0 . 0 } )
257260 for ( id , _) in self . activeAccounts . accounts where ! activeIds . contains ( id ) {
258261 self . disposableDict . set ( nil , forKey : id )
262+ self . storyDisposableDict . set ( nil , forKey : id )
259263 }
260264 for account in accounts . accounts {
261265 self . startNotifyListener ( with : account . 1 , primary : account . 0 == accounts . primary ? . id )
@@ -811,22 +815,185 @@ final class SharedNotificationManager : NSObject, NSUserNotificationCenterDelega
811815
812816 notification . userInfo = dict
813817
814- _ = UNUserNotifications . authorizationStatus . start ( next : { status in
815- switch status {
816- case . authorized :
817- UNUserNotifications . current ? . add ( notification )
818- case . notDetermined :
819- UNUserNotifications . current ? . authorize { manager in
820- manager . add ( notification )
821- }
822- default :
823- break
824- }
825- } )
818+ self . deliver ( notification )
826819 }
827820 }
828821 }
829822 } ) , forKey : account . id )
823+
824+ startStoryNotifyListener ( with : account , primary : primary )
825+ }
826+
827+ private func deliver ( _ notification : NSUserNotification ) {
828+ _ = UNUserNotifications . authorizationStatus . start ( next : { status in
829+ switch status {
830+ case . authorized :
831+ UNUserNotifications . current ? . add ( notification )
832+ case . notDetermined :
833+ UNUserNotifications . current ? . authorize { manager in
834+ manager . add ( notification )
835+ }
836+ default :
837+ break
838+ }
839+ } )
840+ }
841+
842+ private struct StoryNotification {
843+ let peer : EnginePeer
844+ let hideSender : Bool
845+ let sound : PeerMessageSound
846+ }
847+
848+ private func startStoryNotifyListener ( with account : Account , primary : Bool ) {
849+ let engine = TelegramEngine ( account : account )
850+ let screenLocked = self . screenLocked
851+ let accountManager = self . accountManager
852+ let knownTimestamps = Atomic < [ PeerId : Int32 ] ? > ( value : nil )
853+
854+ let newStoryPeers : Signal < [ EnginePeer ] , NoError > = engine . messages . storySubscriptions ( isHidden : false ) |> map { subscriptions -> [ EnginePeer ] in
855+ var current : [ PeerId : Int32 ] = [ : ]
856+ for item in subscriptions . items {
857+ current [ item . peer . id ] = item . lastTimestamp
858+ }
859+ guard let previous = knownTimestamps . swap ( current ) else {
860+ return [ ]
861+ }
862+ let minTimestamp = Int32 ( Date ( ) . timeIntervalSince1970 ) - 60
863+ return subscriptions . items . filter { item in
864+ return item . peer . id != account . peerId && item . hasUnseen && item . lastTimestamp >= minTimestamp && item . lastTimestamp > ( previous [ item . peer . id ] ?? 0 )
865+ } . map { $0 . peer }
866+ } |> filter { ! $0 . isEmpty }
867+
868+ let topPeerIds : Signal < Set < PeerId > , NoError > = engine . peers . recentPeers ( ) |> take ( 1 ) |> map { recentPeers in
869+ guard case let . peers ( peers ) = recentPeers else {
870+ return Set ( )
871+ }
872+ return Set ( peers . prefix ( 5 ) . map { $0 . id } )
873+ }
874+
875+ let signal = newStoryPeers |> mapToQueue { peers -> Signal < ( [ StoryNotification ] , InAppNotificationSettings , Bool , Peer , [ PeerId : NSImage ] , [ ( PeerMessageSound , String ? ) ] ) , NoError > in
876+ return combineLatest ( appNotificationSettings ( accountManager : accountManager ) |> take ( 1 ) , topPeerIds , screenLocked . get ( ) |> take ( 1 ) , account . postbox . loadedPeerWithId ( account . peerId ) |> take ( 1 ) ) |> mapToSignal { inAppSettings , topPeerIds , lockData , accountPeer in
877+ guard inAppSettings . enabled && inAppSettings . muteUntil < Int32 ( Date ( ) . timeIntervalSince1970 ) else {
878+ return . complete ( )
879+ }
880+ return account . postbox . transaction { transaction -> [ StoryNotification ] in
881+ let globalSettings = transaction . getPreferencesEntry ( key : PreferencesKeys . globalNotifications ) ? . get ( GlobalNotificationSettings . self ) ? . effective ?? GlobalNotificationSettingsSet . defaultSettings
882+ let globalStorySettings = globalSettings . privateChats . storySettings
883+ var result : [ StoryNotification ] = [ ]
884+ for peer in peers {
885+ let storySettings = ( transaction . getPeerNotificationSettings ( id : peer . id ) as? TelegramPeerNotificationSettings ) ? . storySettings ?? PeerStoryNotificationSettings . default
886+ let enabled : Bool
887+ switch storySettings . mute {
888+ case . muted :
889+ enabled = false
890+ case . unmuted :
891+ enabled = true
892+ case . default :
893+ switch globalStorySettings . mute {
894+ case . muted :
895+ enabled = false
896+ case . unmuted :
897+ enabled = true
898+ case . default :
899+ enabled = topPeerIds . contains ( peer . id )
900+ }
901+ }
902+ if ! enabled {
903+ continue
904+ }
905+ let hideSender : Bool
906+ switch storySettings . hideSender {
907+ case . hide :
908+ hideSender = true
909+ case . show :
910+ hideSender = false
911+ case . default :
912+ hideSender = globalStorySettings . hideSender == . hide
913+ }
914+ let sound = storySettings . sound != . default ? storySettings . sound : effectiveCategorySound ( globalStorySettings . sound )
915+ result . append ( StoryNotification ( peer : peer , hideSender : hideSender , sound : sound ) )
916+ }
917+ return result
918+ } |> mapToSignal { notifications -> Signal < ( [ StoryNotification ] , InAppNotificationSettings , Bool , Peer , [ PeerId : NSImage ] , [ ( PeerMessageSound , String ? ) ] ) , NoError > in
919+ if notifications . isEmpty {
920+ return . complete ( )
921+ }
922+ let photos : [ Signal < ( PeerId , CGImage ? ) , NoError > ] = notifications . map { notification in
923+ let peer = notification . peer . _asPeer ( )
924+ return peerAvatarImage ( account : account , photo : . peer ( peer , peer . smallProfileImage , peer . namePeerColor , peer . displayLetters , nil , nil ) , genCap : false ) |> take ( 1 ) |> map { ( peer . id , $0 . 0 ) }
925+ }
926+ var sounds : [ PeerMessageSound ] = [ ]
927+ for notification in notifications where ! sounds . contains ( notification . sound ) {
928+ sounds . append ( notification . sound )
929+ }
930+ let deviceTone = getNotificationToneFile ( account : account , sound : inAppSettings . tone )
931+ |> timeout ( 3.0 , queue : Queue . concurrentDefaultQueue ( ) , alternate : . single ( " default " ) )
932+ let paths : [ Signal < ( PeerMessageSound , String ? ) , NoError > ] = sounds . map { sound in
933+ switch sound {
934+ case . none :
935+ return . single ( ( sound , nil ) )
936+ case . default :
937+ return deviceTone |> map { ( sound , $0 ) }
938+ default :
939+ return getNotificationToneFile ( account : account , sound : sound )
940+ |> timeout ( 3.0 , queue : Queue . concurrentDefaultQueue ( ) , alternate : deviceTone )
941+ |> map { ( sound , $0 ) }
942+ }
943+ }
944+ return combineLatest ( combineLatest ( photos ) , combineLatest ( paths ) ) |> take ( 1 ) |> map { photos , paths in
945+ var images : [ PeerId : NSImage ] = [ : ]
946+ for ( peerId , image ) in photos {
947+ if let image = image {
948+ images [ peerId ] = NSImage ( cgImage : image , size : NSMakeSize ( 50 , 50 ) )
949+ }
950+ }
951+ return ( notifications , inAppSettings , lockData . isLocked , accountPeer , images , paths )
952+ }
953+ }
954+ }
955+ }
956+
957+ storyDisposableDict . set ( ( signal |> deliverOnMainQueue ) . start ( next : { [ weak self ] notifications , inAppSettings , screenIsLocked , accountPeer , images , soundPaths in
958+ guard let self else {
959+ return
960+ }
961+ if ! primary , ! inAppSettings . notifyAllAccounts {
962+ return
963+ }
964+ if ! self . shouldPresent ( accountId : account . id , wasScheduled : false ) {
965+ return
966+ }
967+ for story in notifications {
968+ let hidden = story . hideSender || screenIsLocked
969+ var title = hidden ? appName : story . peer . _asPeer ( ) . displayTitle
970+ if self . activeAccounts . accounts . count > 1 && ! screenIsLocked {
971+ title += " → \( accountPeer . addressName ?? accountPeer . displayTitle ) "
972+ }
973+ let notification = NSUserNotification ( )
974+ notification . identifier = " story_ \( story . peer . id . toInt64 ( ) ) "
975+ notification . title = title
976+ notification . informativeText = hidden ? strings ( ) . notificationStoryHidden : strings ( ) . notificationStoryPosted
977+ notification . contentImage = hidden ? nil : images [ story . peer . id ]
978+ notification . hasReplyButton = false
979+ notification . hasActionButton = false
980+ if #available( macOS 10 . 14 , * ) , ! screenIsLocked {
981+ switch inAppSettings . tone {
982+ case . none :
983+ notification . soundName = nil
984+ default :
985+ notification . soundName = soundPaths . first ( where : { $0 . 0 == story . sound } ) ? . 1
986+ }
987+ }
988+ notification . userInfo = [
989+ " story.peer.id " : story . peer . id . id . _internalGetInt64Value ( ) ,
990+ " story.peer.namespace " : story . peer . id . namespace . _internalGetInt32Value ( ) ,
991+ " accountId " : account . id . int64 ,
992+ " timestamp " : Int32 ( Date ( ) . timeIntervalSince1970 )
993+ ]
994+ self . deliver ( notification )
995+ }
996+ } ) , forKey : account . id )
830997 }
831998
832999
0 commit comments