Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-06-30 03:28:40 +02:00
commit ae1fd7df68
31 changed files with 1609 additions and 464 deletions

View File

@ -1,5 +1,7 @@
import UIKit import UIKit
@objc(Application) class Application: UIApplication { @objc(Application) class Application: UIApplication {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
}
} }

View File

@ -723,17 +723,18 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue
} }
let updatePeerStoryNotifications: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = { peerId, storyNotifications in let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
var isMuted: Bool? peerId, mute in
switch storyNotifications { return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
case .default: }
isMuted = nil
case .show: let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
isMuted = false peerId, hideSender in
case .hide: return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
isMuted = true }
}
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, isMuted: isMuted) |> deliverOnMainQueue let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
} }
let defaultSound: PeerMessageSound let defaultSound: PeerMessageSound
@ -769,8 +770,14 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
|> deliverOnMainQueue).start(next: { _ in |> deliverOnMainQueue).start(next: { _ in
}) })
}, updatePeerStoryNotifications: { peerId, storyNotifications in }, updatePeerStoriesMuted: { peerId, mute in
let _ = (updatePeerStoryNotifications(peerId, storyNotifications) let _ = (updatePeerStoriesMuted(peerId, mute)
|> deliverOnMainQueue).start()
}, updatePeerStoriesHideSender: { peerId, hideSender in
let _ = (updatePeerStoriesHideSender(peerId, hideSender)
|> deliverOnMainQueue).start()
}, updatePeerStorySound: { peerId, sound in
let _ = (updatePeerStorySound(peerId, sound)
|> deliverOnMainQueue).start() |> deliverOnMainQueue).start()
}, removePeerFromExceptions: { }, removePeerFromExceptions: {
}, modifiedPeer: { }, modifiedPeer: {

View File

@ -2560,7 +2560,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if let rawStorySubscriptions = self.rawStorySubscriptions { if let rawStorySubscriptions = self.rawStorySubscriptions {
var openCamera = false var openCamera = false
if let accountItem = rawStorySubscriptions.accountItem { if let accountItem = rawStorySubscriptions.accountItem {
openCamera = accountItem.storyCount == 0 openCamera = accountItem.storyCount == 0 && !accountItem.hasPending
} else { } else {
openCamera = true openCamera = true
} }
@ -2581,9 +2581,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
} }
let _ = (self.context.engine.data.get( let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id) TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
) )
|> deliverOnMainQueue).start(next: { [weak self] notificationSettings in |> deliverOnMainQueue).start(next: { [weak self] notificationSettings, globalSettings in
guard let self else { guard let self else {
return return
} }
@ -2639,7 +2640,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}) })
}))) })))
let isMuted = notificationSettings.storiesMuted == true let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, action: { [weak self] _, f in
@ -5338,11 +5339,20 @@ private final class ChatListLocationContext {
peerStatus = .single(nil) peerStatus = .single(nil)
} }
let networkState: Signal<AccountNetworkState, NoError>
#if DEBUG && false
networkState = .single(AccountNetworkState.connecting(proxy: nil)) |> then(.single(AccountNetworkState.updating(proxy: nil)) |> delay(2.0, queue: .mainQueue())) |> then(.single(AccountNetworkState.online(proxy: nil)) |> delay(2.0, queue: .mainQueue())) |> then(.complete() |> delay(2.0, queue: .mainQueue())) |> restart
#elseif DEBUG && false
networkState = .single(AccountNetworkState.connecting(proxy: nil))
#else
networkState = context.account.networkState
#endif
switch location { switch location {
case .chatList: case .chatList:
if !hideNetworkActivityStatus { if !hideNetworkActivityStatus {
self.titleDisposable = combineLatest(queue: .mainQueue(), self.titleDisposable = combineLatest(queue: .mainQueue(),
context.account.networkState, networkState,
hasProxy, hasProxy,
passcode, passcode,
containerNode.currentItemState, containerNode.currentItemState,
@ -5354,6 +5364,7 @@ private final class ChatListLocationContext {
guard let self else { guard let self else {
return return
} }
self.updateChatList( self.updateChatList(
networkState: networkState, networkState: networkState,
proxy: proxy, proxy: proxy,

View File

@ -3843,6 +3843,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let item = self.item else {
return nil
}
if let compoundTextButtonNode = self.compoundTextButtonNode, let compoundHighlightingNode = self.compoundHighlightingNode, compoundHighlightingNode.alpha != 0.0 { if let compoundTextButtonNode = self.compoundTextButtonNode, let compoundHighlightingNode = self.compoundHighlightingNode, compoundHighlightingNode.alpha != 0.0 {
let localPoint = self.view.convert(point, to: compoundHighlightingNode.view) let localPoint = self.view.convert(point, to: compoundHighlightingNode.view)
var matches = false var matches = false
@ -3857,8 +3861,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} }
} }
if let avatarStoryIndicatorView = self.avatarStoryIndicator?.view, let result = avatarStoryIndicatorView.hitTest(self.view.convert(point, to: avatarStoryIndicatorView), with: event) { if let _ = item.interaction.inlineNavigationLocation {
return result } else {
if let avatarStoryIndicatorView = self.avatarStoryIndicator?.view, let result = avatarStoryIndicatorView.hitTest(self.view.convert(point, to: avatarStoryIndicatorView), with: event) {
return result
}
} }
return super.hitTest(point, with: event) return super.hitTest(point, with: event)

View File

@ -22,11 +22,11 @@ open class HierarchyTrackingLayer: CALayer {
override open func action(forKey event: String) -> CAAction? { override open func action(forKey event: String) -> CAAction? {
if event == kCAOnOrderIn { if event == kCAOnOrderIn {
self.didEnterHierarchy?()
self.isInHierarchy = true self.isInHierarchy = true
self.didEnterHierarchy?()
} else if event == kCAOnOrderOut { } else if event == kCAOnOrderOut {
self.didExitHierarchy?()
self.isInHierarchy = false self.isInHierarchy = false
self.didExitHierarchy?()
} }
return nullAction return nullAction
} }

View File

@ -19,9 +19,14 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVoiceCallsAvailable(id: peerId), TelegramEngine.EngineData.Item.Peer.AreVoiceCallsAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.AreVideoCallsAvailable(id: peerId), TelegramEngine.EngineData.Item.Peer.AreVideoCallsAvailable(id: peerId),
TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId) TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
) )
|> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable, notificationSettings -> [ContextMenuItem] in |> map { [weak contactsController] peer, areVoiceCallsAvailable, areVideoCallsAvailable, notificationSettings, globalSettings -> [ContextMenuItem] in
guard let peer else {
return []
}
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
if isStories { if isStories {
@ -42,7 +47,8 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
}) })
}))) })))
let isMuted = notificationSettings.storiesMuted == true let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
@ -50,7 +56,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
let _ = context.engine.peers.togglePeerStoriesMuted(peerId: peerId).start() let _ = context.engine.peers.togglePeerStoriesMuted(peerId: peerId).start()
if let peer { do {
let iconColor = UIColor.white let iconColor = UIColor.white
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if isMuted { if isMuted {
@ -96,7 +102,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con
return return
} }
if let peer { do {
let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize()) let location = CGRect(origin: CGPoint(x: sourceFrame.midX, y: sourceFrame.minY + 1.0), size: CGSize())
let tooltipController = TooltipScreen( let tooltipController = TooltipScreen(
context: context, context: context,

View File

@ -62,8 +62,16 @@ private final class NotificationExceptionState : Equatable {
return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing) return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
} }
func withUpdatedPeerStoryNotifications(_ peer: EnginePeer, _ storyNotifications: PeerNotificationDisplayPreviews) -> NotificationExceptionState { func withUpdatedPeerStoriesMuted(_ peer: EnginePeer, _ mute: PeerStoryNotificationSettings.Mute) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStoryNotifications(peer, storyNotifications), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing) return NotificationExceptionState(mode: mode.withUpdatedPeerStoriesMuted(peer, mute), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerStoriesHideSender(_ peer: EnginePeer, _ hideSender: PeerStoryNotificationSettings.HideSender) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStoriesHideSender(peer, hideSender), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerStorySound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStorySound(peer, sound), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
} }
static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool { static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool {
@ -584,17 +592,18 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue
} }
let updatePeerStoryNotifications: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = { peerId, storyNotifications in let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
var isMuted: Bool? peerId, mute in
switch storyNotifications { return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
case .default: }
isMuted = nil
case .show: let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
isMuted = false peerId, hideSender in
case .hide: return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
isMuted = true }
}
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, isMuted: isMuted) |> deliverOnMainQueue let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
} }
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
@ -670,12 +679,32 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
} }
updateNotificationsView({}) updateNotificationsView({})
}) })
}, updatePeerStoryNotifications: { peerId, displayPreviews in }, updatePeerStoriesMuted: { peerId, mute in
updateNotificationsDisposable.set(nil) updateNotificationsDisposable.set(nil)
_ = combineLatest(updatePeerStoryNotifications(peerId, displayPreviews), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in let _ = combineLatest(updatePeerStoriesMuted(peerId, mute), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer { if let peer = peer {
updateState { value in updateState { value in
return value.withUpdatedPeerStoryNotifications(peer, displayPreviews) return value.withUpdatedPeerStoriesMuted(peer, mute)
}
}
updateNotificationsView({})
})
}, updatePeerStoriesHideSender: { peerId, hideSender in
updateNotificationsDisposable.set(nil)
let _ = combineLatest(updatePeerStoriesHideSender(peerId, hideSender), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer {
updateState { value in
return value.withUpdatedPeerStoriesHideSender(peer, hideSender)
}
}
updateNotificationsView({})
})
}, updatePeerStorySound: { peerId, sound in
updateNotificationsDisposable.set(nil)
let _ = combineLatest(updatePeerStorySound(peerId, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer {
updateState { value in
return value.withUpdatedPeerStorySound(peer, sound)
} }
} }
updateNotificationsView({}) updateNotificationsView({})

View File

@ -551,8 +551,19 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
entries.append(.privateChats(presentationData.theme, presentationData.strings.Notifications_PrivateChats, !exceptions.users.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.users.peerIds.count)) : "", globalSettings.privateChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.privateChats(presentationData.theme, presentationData.strings.Notifications_PrivateChats, !exceptions.users.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.users.peerIds.count)) : "", globalSettings.privateChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.groupChats(presentationData.theme, presentationData.strings.Notifications_GroupChats, !exceptions.groups.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.groups.peerIds.count)) : "", globalSettings.groupChats.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.channels(presentationData.theme, presentationData.strings.Notifications_Channels, !exceptions.channels.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.channels.peerIds.count)) : "", globalSettings.channels.enabled ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off))
//TODO:localize //TODO:localize
entries.append(.stories(presentationData.theme, "Stories", !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", globalSettings.privateChats.storiesMuted != true ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) let storiesValue: String
switch globalSettings.privateChats.storySettings.mute {
case .default:
storiesValue = "Top 5"
case .muted:
storiesValue = presentationData.strings.Notifications_Off
case .unmuted:
storiesValue = presentationData.strings.Notifications_On
}
entries.append(.stories(presentationData.theme, "Stories", !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased())) entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds)) entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
@ -730,6 +741,8 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init)) let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init))
let defaultStorySettings = PeerStoryNotificationSettings.default
notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in
var users:[PeerId : NotificationExceptionWrapper] = [:] var users:[PeerId : NotificationExceptionWrapper] = [:]
var groups: [PeerId : NotificationExceptionWrapper] = [:] var groups: [PeerId : NotificationExceptionWrapper] = [:]
@ -738,7 +751,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
if let list = list { if let list = list {
for (key, value) in list.settings { for (key, value) in list.settings {
if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId { if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId {
if value.storiesMuted != nil { if value.storySettings != defaultStorySettings {
stories[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) stories[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer))
} }

View File

@ -37,6 +37,7 @@ private final class NotificationsPeerCategoryControllerArguments {
let soundSelectionDisposable: MetaDisposable let soundSelectionDisposable: MetaDisposable
let updateEnabled: (Bool) -> Void let updateEnabled: (Bool) -> Void
let updateEnabledImportant: (Bool) -> Void
let updatePreviews: (Bool) -> Void let updatePreviews: (Bool) -> Void
let openSound: (PeerMessageSound) -> Void let openSound: (PeerMessageSound) -> Void
@ -49,11 +50,12 @@ private final class NotificationsPeerCategoryControllerArguments {
let updatedExceptionMode: (NotificationExceptionMode) -> Void let updatedExceptionMode: (NotificationExceptionMode) -> Void
init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (EnginePeer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) { init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updateEnabledImportant: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (EnginePeer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) {
self.context = context self.context = context
self.soundSelectionDisposable = soundSelectionDisposable self.soundSelectionDisposable = soundSelectionDisposable
self.updateEnabled = updateEnabled self.updateEnabled = updateEnabled
self.updateEnabledImportant = updateEnabledImportant
self.updatePreviews = updatePreviews self.updatePreviews = updatePreviews
self.openSound = openSound self.openSound = openSound
@ -90,6 +92,8 @@ public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
case enable(PresentationTheme, String, Bool) case enable(PresentationTheme, String, Bool)
case enableImportant(PresentationTheme, String, Bool)
case importantInfo(PresentationTheme, String)
case optionsHeader(PresentationTheme, String) case optionsHeader(PresentationTheme, String)
case previews(PresentationTheme, String, Bool) case previews(PresentationTheme, String, Bool)
case sound(PresentationTheme, String, String, PeerMessageSound) case sound(PresentationTheme, String, String, PeerMessageSound)
@ -101,7 +105,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .enable: case .enable, .enableImportant, .importantInfo:
return NotificationsPeerCategorySection.enable.rawValue return NotificationsPeerCategorySection.enable.rawValue
case .optionsHeader, .previews, .sound: case .optionsHeader, .previews, .sound:
return NotificationsPeerCategorySection.options.rawValue return NotificationsPeerCategorySection.options.rawValue
@ -114,18 +118,22 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
switch self { switch self {
case .enable: case .enable:
return 0 return 0
case .optionsHeader: case .enableImportant:
return 1 return 1
case .previews: case .importantInfo:
return 2 return 2
case .sound: case .optionsHeader:
return 3 return 3
case .exceptionsHeader: case .previews:
return 4 return 4
case .addException: case .sound:
return 5 return 5
case .exceptionsHeader:
return 6
case .addException:
return 7
case let .exception(index, _, _, _, _, _, _, _, _, _): case let .exception(index, _, _, _, _, _, _, _, _, _):
return 6 + index return 8 + index
case .removeAllExceptions: case .removeAllExceptions:
return 100000 return 100000
} }
@ -152,6 +160,18 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .enableImportant(lhsTheme, lhsText, lhsValue):
if case let .enableImportant(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .importantInfo(lhsTheme, lhsText):
if case let .importantInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .optionsHeader(lhsTheme, lhsText): case let .optionsHeader(lhsTheme, lhsText):
if case let .optionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { if case let .optionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
@ -209,6 +229,12 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateEnabled(updatedValue) arguments.updateEnabled(updatedValue)
}, tag: self.tag) }, tag: self.tag)
case let .enableImportant(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateEnabledImportant(updatedValue)
}, tag: self.tag)
case let .importantInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .optionsHeader(_, text): case let .optionsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .previews(_, text, value): case let .previews(_, text, value):
@ -266,7 +292,34 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
} }
if case .stories = category { if case .stories = category {
entries.append(.enable(presentationData.theme, "Show All Notifications", notificationSettings.storiesMuted == nil || notificationSettings.storiesMuted == false)) var allEnabled = false
var importantEnabled = false
switch notificationSettings.storySettings.mute {
case .muted:
allEnabled = false
importantEnabled = false
case .unmuted:
allEnabled = true
importantEnabled = true
case .default:
allEnabled = false
importantEnabled = true
}
//TODO:localize
entries.append(.enable(presentationData.theme, "Show All Notifications", allEnabled))
if !allEnabled {
entries.append(.enableImportant(presentationData.theme, "Show Important Notifications", importantEnabled))
entries.append(.importantInfo(presentationData.theme, "Always on for top 5 contacts."))
}
if notificationSettings.enabled || !notificationExceptions.isEmpty {
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
entries.append(.previews(presentationData.theme, "Display Author Name", notificationSettings.storySettings.hideSender != .hide))
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
}
} else { } else {
entries.append(.enable(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, notificationSettings.enabled)) entries.append(.enable(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, notificationSettings.enabled))
@ -280,7 +333,6 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsExceptions.uppercased())) entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsExceptions.uppercased()))
entries.append(.addException(presentationData.theme, presentationData.strings.Notification_Exceptions_AddException)) entries.append(.addException(presentationData.theme, presentationData.strings.Notification_Exceptions_AddException))
let sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in let sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in
let lhsName = lhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let lhsName = lhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let rhsName = rhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let rhsName = rhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
@ -312,11 +364,39 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
var title: String var title: String
if case .stories = category { if case .stories = category {
if value.settings.storiesMuted == true { var muted = false
if value.settings.storySettings.mute == .muted {
muted = true
title = presentationData.strings.Notification_Exceptions_AlwaysOff title = presentationData.strings.Notification_Exceptions_AlwaysOff
} else { } else {
title = presentationData.strings.Notification_Exceptions_AlwaysOn title = presentationData.strings.Notification_Exceptions_AlwaysOn
} }
if !muted {
switch value.settings.storySettings.sound {
case .default:
break
default:
if !title.isEmpty {
title.append(", ")
}
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
}
switch value.settings.storySettings.hideSender {
case .default:
break
default:
if !title.isEmpty {
title += ", "
}
//TODO:localize
if case .show = value.settings.displayPreviews {
title += "Show Names"
} else {
title += "Hide Names"
}
}
}
} else { } else {
var muted = false var muted = false
switch value.settings.muteState { switch value.settings.muteState {
@ -427,8 +507,16 @@ private final class NotificationExceptionState : Equatable {
return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), revealedPeerId: self.revealedPeerId, editing: self.editing) return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), revealedPeerId: self.revealedPeerId, editing: self.editing)
} }
func withUpdatedPeerStoryNotifications(_ peer: EnginePeer, _ storyNotifications: PeerNotificationDisplayPreviews) -> NotificationExceptionState { func withUpdatedPeerStoriesMuted(_ peer: EnginePeer, _ mute: PeerStoryNotificationSettings.Mute) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStoryNotifications(peer, storyNotifications), revealedPeerId: self.revealedPeerId, editing: self.editing) return NotificationExceptionState(mode: mode.withUpdatedPeerStoriesMuted(peer, mute), revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerStoriesHideSender(_ peer: EnginePeer, _ hideSender: PeerStoryNotificationSettings.HideSender) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStoriesHideSender(peer, hideSender), revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerStorySound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerStorySound(peer, sound), revealedPeerId: self.revealedPeerId, editing: self.editing)
} }
static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool { static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool {
@ -470,17 +558,18 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue
} }
let updatePeerStoryNotifications: (EnginePeer.Id, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = { peerId, storyNotifications in let updatePeerStoriesMuted: (EnginePeer.Id, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
var isMuted: Bool? peerId, mute in
switch storyNotifications { return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
case .default: }
isMuted = nil
case .show: let updatePeerStoriesHideSender: (EnginePeer.Id, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
isMuted = false peerId, hideSender in
case .hide: return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
isMuted = true }
}
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, isMuted: isMuted) |> deliverOnMainQueue let updatePeerStorySound: (EnginePeer.Id, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
} }
var peerIds: Set<EnginePeer.Id> = Set(mode.peerIds) var peerIds: Set<EnginePeer.Id> = Set(mode.peerIds)
@ -587,12 +676,32 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
} }
updateNotificationsView({}) updateNotificationsView({})
}) })
}, updatePeerStoryNotifications: { peerId, storyNotifications in }, updatePeerStoriesMuted: { peerId, mute in
updateNotificationsDisposable.set(nil) updateNotificationsDisposable.set(nil)
_ = combineLatest(updatePeerStoryNotifications(peerId, storyNotifications), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in let _ = combineLatest(updatePeerStoriesMuted(peerId, mute), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer { if let peer = peer {
updateState { value in updateState { value in
return value.withUpdatedPeerStoryNotifications(peer, storyNotifications) return value.withUpdatedPeerStoriesMuted(peer, mute)
}
}
updateNotificationsView({})
})
}, updatePeerStoriesHideSender: { peerId, hideSender in
updateNotificationsDisposable.set(nil)
let _ = combineLatest(updatePeerStoriesHideSender(peerId, hideSender), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer {
updateState { value in
return value.withUpdatedPeerStoriesHideSender(peer, hideSender)
}
}
updateNotificationsView({})
})
}, updatePeerStorySound: { peerId, sound in
updateNotificationsDisposable.set(nil)
let _ = combineLatest(updatePeerStorySound(peerId, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in
if let peer = peer {
updateState { value in
return value.withUpdatedPeerStorySound(peer, sound)
} }
} }
updateNotificationsView({}) updateNotificationsView({})
@ -608,7 +717,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
return return
} }
updateState { value in updateState { value in
return value.withUpdatedPeerStoryNotifications(peer, .default) return value.withUpdatedPeerStorySound(peer, .default).withUpdatedPeerStoriesMuted(peer, .default).withUpdatedPeerStoriesHideSender(peer, .default)
} }
updateNotificationsView({}) updateNotificationsView({})
}) })
@ -637,14 +746,25 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings var settings = settings
switch category { switch category {
case .privateChat: case .privateChat:
settings.privateChats.enabled = value settings.privateChats.enabled = value
case .group: case .group:
settings.groupChats.enabled = value settings.groupChats.enabled = value
case .channel: case .channel:
settings.channels.enabled = value settings.channels.enabled = value
case .stories: case .stories:
settings.privateChats.storiesMuted = !value settings.privateChats.storySettings.mute = value ? .unmuted : .default
}
return settings
}).start()
}, updateEnabledImportant: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
switch category {
case .stories:
settings.privateChats.storySettings.mute = value ? .default : .muted
default:
break
} }
return settings return settings
}).start() }).start()
@ -659,7 +779,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
case .channel: case .channel:
settings.channels.displayPreviews = value settings.channels.displayPreviews = value
case .stories: case .stories:
break settings.privateChats.storySettings.hideSender = value ? .show : .hide
} }
return settings return settings
}).start() }).start()
@ -675,7 +795,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
case .channel: case .channel:
settings.channels.sound = value settings.channels.sound = value
case .stories: case .stories:
break settings.privateChats.storySettings.sound = value
} }
return settings return settings
}).start() }).start()
@ -722,7 +842,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
updateState { state in updateState { state in
var state = state var state = state
for value in values { for value in values {
state = state.withUpdatedPeerStoryNotifications(value.peer, .default) state = state.withUpdatedPeerStorySound(value.peer, .default).withUpdatedPeerStoriesMuted(value.peer, .default).withUpdatedPeerStoriesHideSender(value.peer, .default)
} }
return state return state
} }
@ -765,7 +885,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil) updateNotificationsDisposable.set(nil)
updateState { value in updateState { value in
return value.withUpdatedPeerStoryNotifications(peer, .default) return value.withUpdatedPeerStorySound(peer, .default).withUpdatedPeerStoriesMuted(peer, .default).withUpdatedPeerStoriesHideSender(peer, .default)
} }
let _ = (context.engine.peers.removeCustomStoryNotificationSettings(peerIds: [peer.id]) let _ = (context.engine.peers.removeCustomStoryNotificationSettings(peerIds: [peer.id])
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(completed: {

View File

@ -6,12 +6,15 @@ import TelegramApi
extension TelegramPeerNotificationSettings { extension TelegramPeerNotificationSettings {
convenience init(apiSettings: Api.PeerNotifySettings) { convenience init(apiSettings: Api.PeerNotifySettings) {
switch apiSettings { switch apiSettings {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, _, _, _, _): case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound? let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS) #if os(iOS)
sound = iosSound sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS) #elseif os(macOS)
sound = desktopSound sound = desktopSound
storiesSound = storiesDesktopSound
#endif #endif
let muteState: PeerMuteState let muteState: PeerMuteState
@ -35,12 +38,25 @@ extension TelegramPeerNotificationSettings {
displayPreviews = .default displayPreviews = .default
} }
var storiesMutedValue: Bool? let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted { if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
} }
self.init(muteState: muteState, messageSound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), displayPreviews: displayPreviews, storiesMuted: storiesMutedValue) var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
self.init(muteState: muteState, messageSound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), displayPreviews: displayPreviews, storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: storiesSound ?? .notificationSoundDefault)
))
} }
} }
} }

View File

@ -6,24 +6,48 @@ import TelegramApi
extension MessageNotificationSettings { extension MessageNotificationSettings {
init(apiSettings: Api.PeerNotifySettings) { init(apiSettings: Api.PeerNotifySettings) {
switch apiSettings { switch apiSettings {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, _, _, _, _): case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound? let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS) #if os(iOS)
sound = iosSound sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS) #elseif os(macOS)
sound = desktopSound sound = desktopSound
storiesSound = storiesDesktopSound
#endif #endif
let displayPreviews: Bool let displayPreviews: Bool
if let showPreviews = showPreviews, case .boolFalse = showPreviews { if let showPreviews = showPreviews, case .boolFalse = showPreviews {
displayPreviews = false displayPreviews = false
} else { } else {
displayPreviews = true displayPreviews = true
} }
var storiesMutedValue: Bool?
let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted { if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
} }
self = MessageNotificationSettings(enabled: muteUntil == 0, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue)
var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
self = MessageNotificationSettings(
enabled: muteUntil == 0,
displayPreviews: displayPreviews,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault),
storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault)
)
)
} }
} }
} }

View File

@ -116,12 +116,15 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|> map { chats, users, channels, contactsJoinedMuted in |> map { chats, users, channels, contactsJoinedMuted in
let chatsSettings: MessageNotificationSettings let chatsSettings: MessageNotificationSettings
switch chats { switch chats {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, _, _, _, _): case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound? let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS) #if os(iOS)
sound = iosSound sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS) #elseif os(macOS)
sound = desktopSound sound = desktopSound
storiesSound = storiesDesktopSound
#endif #endif
let enabled: Bool let enabled: Bool
@ -137,22 +140,43 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
displayPreviews = true displayPreviews = true
} }
var storiesMutedValue: Bool? let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted { if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
} }
chatsSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue) var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
chatsSettings = MessageNotificationSettings(
enabled: enabled,
displayPreviews: displayPreviews,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault),
storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault)
)
)
} }
let userSettings: MessageNotificationSettings let userSettings: MessageNotificationSettings
switch users { switch users {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, _, _, _, _): case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound? let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS) #if os(iOS)
sound = iosSound sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS) #elseif os(macOS)
sound = desktopSound sound = desktopSound
storiesSound = storiesDesktopSound
#endif #endif
let enabled: Bool let enabled: Bool
@ -168,22 +192,43 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
displayPreviews = true displayPreviews = true
} }
var storiesMutedValue: Bool? let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted { if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
} }
userSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue) var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
userSettings = MessageNotificationSettings(
enabled: enabled,
displayPreviews: displayPreviews,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault),
storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault)
)
)
} }
let channelSettings: MessageNotificationSettings let channelSettings: MessageNotificationSettings
switch channels { switch channels {
case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, _, _, _, _): case let .peerNotifySettings(_, showPreviews, _, muteUntil, iosSound, _, desktopSound, storiesMuted, storiesHideSender, storiesIosSound, _, storiesDesktopSound):
let sound: Api.NotificationSound? let sound: Api.NotificationSound?
let storiesSound: Api.NotificationSound?
#if os(iOS) #if os(iOS)
sound = iosSound sound = iosSound
storiesSound = storiesIosSound
#elseif os(macOS) #elseif os(macOS)
sound = desktopSound sound = desktopSound
storiesSound = storiesDesktopSound
#endif #endif
let enabled: Bool let enabled: Bool
@ -199,12 +244,30 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
displayPreviews = true displayPreviews = true
} }
var storiesMutedValue: Bool? let storiesMutedValue: PeerStoryNotificationSettings.Mute
if let storiesMuted = storiesMuted { if let storiesMuted = storiesMuted {
storiesMutedValue = storiesMuted == .boolTrue storiesMutedValue = storiesMuted == .boolTrue ? .muted : .unmuted
} else {
storiesMutedValue = .default
} }
channelSettings = MessageNotificationSettings(enabled: enabled, displayPreviews: displayPreviews, sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault), storiesMuted: storiesMutedValue) var storiesHideSenderValue: PeerStoryNotificationSettings.HideSender
if let storiesHideSender = storiesHideSender {
storiesHideSenderValue = storiesHideSender == .boolTrue ? .hide : .show
} else {
storiesHideSenderValue = .default
}
channelSettings = MessageNotificationSettings(
enabled: enabled,
displayPreviews: displayPreviews,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault),
storySettings: PeerStoryNotificationSettings(
mute: storiesMutedValue,
hideSender: storiesHideSenderValue,
sound: PeerMessageSound(apiSound: sound ?? .notificationSoundDefault)
)
)
} }
return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, contactsJoined: contactsJoinedMuted == .boolFalse) return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, contactsJoined: contactsJoinedMuted == .boolFalse)
@ -228,13 +291,38 @@ private func apiInputPeerNotifySettings(_ settings: MessageNotificationSettings)
flags |= (1 << 3) flags |= (1 << 3)
} }
var storiesMuted: Api.Bool? let storiesMuted: Api.Bool?
if let storiesMutedValue = settings.storiesMuted { switch settings.storySettings.mute {
case .default:
storiesMuted = nil
case .muted:
storiesMuted = .boolTrue
case .unmuted:
storiesMuted = .boolFalse
}
if storiesMuted != nil {
flags |= (1 << 6) flags |= (1 << 6)
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse
} }
return .inputPeerNotifySettings(flags: flags, showPreviews: settings.displayPreviews ? .boolTrue : .boolFalse, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: nil, storiesSound: nil) let storiesHideSender: Api.Bool?
switch settings.storySettings.hideSender {
case .default:
storiesHideSender = nil
case .hide:
storiesHideSender = .boolTrue
case .show:
storiesHideSender = .boolFalse
}
if storiesHideSender != nil {
flags |= (1 << 7)
}
let storiesSound: Api.NotificationSound? = settings.storySettings.sound.apiSound
if storiesSound != nil {
flags |= (1 << 8)
}
return .inputPeerNotifySettings(flags: flags, showPreviews: settings.displayPreviews ? .boolTrue : .boolFalse, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: storiesHideSender, storiesSound: storiesSound)
} }
private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal<Void, NoError> { private func pushedNotificationSettings(network: Network, settings: GlobalNotificationSettingsSet) -> Signal<Void, NoError> {

View File

@ -130,13 +130,39 @@ func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: Pe
if sound != nil { if sound != nil {
flags |= (1 << 3) flags |= (1 << 3)
} }
var storiesMuted: Api.Bool?
if let storiesMutedValue = settings.storiesMuted { let storiesMuted: Api.Bool?
switch settings.storySettings.mute {
case .default:
storiesMuted = nil
case .muted:
storiesMuted = .boolTrue
case .unmuted:
storiesMuted = .boolFalse
}
if storiesMuted != nil {
flags |= (1 << 6) flags |= (1 << 6)
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse
} }
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: nil, storiesSound: nil) let storiesHideSender: Api.Bool?
switch settings.storySettings.hideSender {
case .default:
storiesHideSender = nil
case .hide:
storiesHideSender = .boolTrue
case .show:
storiesHideSender = .boolFalse
}
if storiesHideSender != nil {
flags |= (1 << 7)
}
let storiesSound: Api.NotificationSound? = settings.storySettings.sound.apiSound
if storiesSound != nil {
flags |= (1 << 8)
}
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: storiesHideSender, storiesSound: storiesSound)
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyForumTopic(peer: inputPeer, topMsgId: Int32(clamping: threadId)), settings: inputSettings)) return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyForumTopic(peer: inputPeer, topMsgId: Int32(clamping: threadId)), settings: inputSettings))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)
@ -179,12 +205,39 @@ func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: Pe
if sound != nil { if sound != nil {
flags |= (1 << 3) flags |= (1 << 3)
} }
var storiesMuted: Api.Bool?
if let storiesMutedValue = settings.storiesMuted { let storiesMuted: Api.Bool?
flags |= (1 << 6) switch settings.storySettings.mute {
storiesMuted = storiesMutedValue ? .boolTrue : .boolFalse case .default:
storiesMuted = nil
case .muted:
storiesMuted = .boolTrue
case .unmuted:
storiesMuted = .boolFalse
} }
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: nil, storiesSound: nil) if storiesMuted != nil {
flags |= (1 << 6)
}
let storiesHideSender: Api.Bool?
switch settings.storySettings.hideSender {
case .default:
storiesHideSender = nil
case .hide:
storiesHideSender = .boolTrue
case .show:
storiesHideSender = .boolFalse
}
if storiesHideSender != nil {
flags |= (1 << 7)
}
let storiesSound: Api.NotificationSound? = settings.storySettings.sound.apiSound
if storiesSound != nil {
flags |= (1 << 8)
}
let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound, storiesMuted: storiesMuted, storiesHideSender: storiesHideSender, storiesSound: storiesSound)
return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings)) return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings))
|> `catch` { _ -> Signal<Api.Bool, NoError> in |> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse) return .single(.boolFalse)

View File

@ -4,17 +4,17 @@ public struct MessageNotificationSettings: Codable, Equatable {
public var enabled: Bool public var enabled: Bool
public var displayPreviews: Bool public var displayPreviews: Bool
public var sound: PeerMessageSound public var sound: PeerMessageSound
public var storiesMuted: Bool? public var storySettings: PeerStoryNotificationSettings
public static var defaultSettings: MessageNotificationSettings { public static var defaultSettings: MessageNotificationSettings {
return MessageNotificationSettings(enabled: true, displayPreviews: true, sound: defaultCloudPeerNotificationSound, storiesMuted: nil) return MessageNotificationSettings(enabled: true, displayPreviews: true, sound: defaultCloudPeerNotificationSound, storySettings: PeerStoryNotificationSettings.default)
} }
public init(enabled: Bool, displayPreviews: Bool, sound: PeerMessageSound, storiesMuted: Bool?) { public init(enabled: Bool, displayPreviews: Bool, sound: PeerMessageSound, storySettings: PeerStoryNotificationSettings) {
self.enabled = enabled self.enabled = enabled
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
self.sound = sound self.sound = sound
self.storiesMuted = storiesMuted self.storySettings = storySettings
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -25,7 +25,7 @@ public struct MessageNotificationSettings: Codable, Equatable {
self.sound = try PeerMessageSound.decodeInline(container) self.sound = try PeerMessageSound.decodeInline(container)
self.storiesMuted = try? container.decodeIfPresent(Bool.self, forKey: "st") self.storySettings = try container.decodeIfPresent(PeerStoryNotificationSettings.self, forKey: "stor") ?? PeerStoryNotificationSettings(mute: .unmuted, hideSender: .show, sound: defaultCloudPeerNotificationSound)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -34,7 +34,7 @@ public struct MessageNotificationSettings: Codable, Equatable {
try container.encode((self.enabled ? 1 : 0) as Int32, forKey: "e") try container.encode((self.enabled ? 1 : 0) as Int32, forKey: "e")
try container.encode((self.displayPreviews ? 1 : 0) as Int32, forKey: "p") try container.encode((self.displayPreviews ? 1 : 0) as Int32, forKey: "p")
try self.sound.encodeInline(&container) try self.sound.encodeInline(&container)
try container.encodeIfPresent(self.storiesMuted, forKey: "st") try container.encode(self.storySettings, forKey: "stor")
} }
} }

View File

@ -388,24 +388,90 @@ public enum PeerNotificationDisplayPreviews: Equatable, Codable {
} }
} }
public struct PeerStoryNotificationSettings: Codable, Equatable {
public enum CodingError: Error {
case generic
}
public static var `default`: PeerStoryNotificationSettings {
return PeerStoryNotificationSettings(mute: .default, hideSender: .default, sound: .default)
}
private enum CodingKeys: String, CodingKey {
case mute = "m"
case hideSender = "hs"
case sound = "s"
}
public enum Mute: Int32, Codable {
case `default` = 0
case unmuted = 1
case muted = 2
}
public enum HideSender: Int32, Codable {
case `default` = 0
case hide = 1
case show = 2
}
public var mute: Mute
public var hideSender: HideSender
public var sound: PeerMessageSound
public init(
mute: Mute,
hideSender: HideSender,
sound: PeerMessageSound
) {
self.mute = mute
self.hideSender = hideSender
self.sound = sound
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let mute = Mute(rawValue: try container.decode(Int32.self, forKey: .mute)) {
self.mute = mute
} else {
throw CodingError.generic
}
if let hideSender = HideSender(rawValue: try container.decode(Int32.self, forKey: .hideSender)) {
self.hideSender = hideSender
} else {
throw CodingError.generic
}
self.sound = try container.decode(PeerMessageSound.self, forKey: .sound)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.mute.rawValue, forKey: .mute)
try container.encode(self.hideSender.rawValue, forKey: .hideSender)
try container.encode(self.sound, forKey: .sound)
}
}
public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Codable, Equatable { public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Codable, Equatable {
public let muteState: PeerMuteState public let muteState: PeerMuteState
public let messageSound: PeerMessageSound public let messageSound: PeerMessageSound
public let displayPreviews: PeerNotificationDisplayPreviews public let displayPreviews: PeerNotificationDisplayPreviews
public let storiesMuted: Bool? public let storySettings: PeerStoryNotificationSettings
public static var defaultSettings: TelegramPeerNotificationSettings { public static var defaultSettings: TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storiesMuted: nil) return TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storySettings: PeerStoryNotificationSettings.default)
} }
public func isRemovedFromTotalUnreadCount(`default`: Bool) -> Bool { public func isRemovedFromTotalUnreadCount(`default`: Bool) -> Bool {
switch self.muteState { switch self.muteState {
case .unmuted: case .unmuted:
return false return false
case .muted: case .muted:
return true return true
case .default: case .default:
return `default` return `default`
} }
} }
@ -417,18 +483,18 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
} }
} }
public init(muteState: PeerMuteState, messageSound: PeerMessageSound, displayPreviews: PeerNotificationDisplayPreviews, storiesMuted: Bool?) { public init(muteState: PeerMuteState, messageSound: PeerMessageSound, displayPreviews: PeerNotificationDisplayPreviews, storySettings: PeerStoryNotificationSettings) {
self.muteState = muteState self.muteState = muteState
self.messageSound = messageSound self.messageSound = messageSound
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
self.storiesMuted = storiesMuted self.storySettings = storySettings
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.muteState = PeerMuteState.decodeInline(decoder) self.muteState = PeerMuteState.decodeInline(decoder)
self.messageSound = PeerMessageSound.decodeInline(decoder) self.messageSound = PeerMessageSound.decodeInline(decoder)
self.displayPreviews = PeerNotificationDisplayPreviews.decodeInline(decoder) self.displayPreviews = PeerNotificationDisplayPreviews.decodeInline(decoder)
self.storiesMuted = decoder.decodeOptionalBoolForKey("stm") self.storySettings = decoder.decode(PeerStoryNotificationSettings.self, forKey: "stor") ?? PeerStoryNotificationSettings.default
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -437,7 +503,7 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
self.muteState = try container.decode(PeerMuteState.self, forKey: "muteState") self.muteState = try container.decode(PeerMuteState.self, forKey: "muteState")
self.messageSound = try container.decode(PeerMessageSound.self, forKey: "messageSound") self.messageSound = try container.decode(PeerMessageSound.self, forKey: "messageSound")
self.displayPreviews = try container.decode(PeerNotificationDisplayPreviews.self, forKey: "displayPreviews") self.displayPreviews = try container.decode(PeerNotificationDisplayPreviews.self, forKey: "displayPreviews")
self.storiesMuted = try? container.decodeIfPresent(Bool.self, forKey: "stm") self.storySettings = try container.decodeIfPresent(PeerStoryNotificationSettings.self, forKey: "stor") ?? PeerStoryNotificationSettings.default
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -446,18 +512,14 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
try container.encode(self.muteState, forKey: "muteState") try container.encode(self.muteState, forKey: "muteState")
try container.encode(self.messageSound, forKey: "messageSound") try container.encode(self.messageSound, forKey: "messageSound")
try container.encode(self.displayPreviews, forKey: "displayPreviews") try container.encode(self.displayPreviews, forKey: "displayPreviews")
try container.encodeIfPresent(self.storiesMuted, forKey: "stm") try container.encode(self.storySettings, forKey: "stor")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
self.muteState.encodeInline(encoder) self.muteState.encodeInline(encoder)
self.messageSound.encodeInline(encoder) self.messageSound.encodeInline(encoder)
self.displayPreviews.encodeInline(encoder) self.displayPreviews.encodeInline(encoder)
if let storiesMuted = self.storiesMuted { encoder.encode(self.storySettings, forKey: "stor")
encoder.encodeBool(storiesMuted, forKey: "stm")
} else {
encoder.encodeNil(forKey: "stm")
}
} }
public func isEqual(to: PeerNotificationSettings) -> Bool { public func isEqual(to: PeerNotificationSettings) -> Bool {
@ -469,22 +531,22 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, C
} }
public func withUpdatedMuteState(_ muteState: PeerMuteState) -> TelegramPeerNotificationSettings { public func withUpdatedMuteState(_ muteState: PeerMuteState) -> TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(muteState: muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storiesMuted: self.storiesMuted) return TelegramPeerNotificationSettings(muteState: muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storySettings: self.storySettings)
} }
public func withUpdatedMessageSound(_ messageSound: PeerMessageSound) -> TelegramPeerNotificationSettings { public func withUpdatedMessageSound(_ messageSound: PeerMessageSound) -> TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: messageSound, displayPreviews: self.displayPreviews, storiesMuted: self.storiesMuted) return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: messageSound, displayPreviews: self.displayPreviews, storySettings: self.storySettings)
} }
public func withUpdatedDisplayPreviews(_ displayPreviews: PeerNotificationDisplayPreviews) -> TelegramPeerNotificationSettings { public func withUpdatedDisplayPreviews(_ displayPreviews: PeerNotificationDisplayPreviews) -> TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: displayPreviews, storiesMuted: self.storiesMuted) return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: displayPreviews, storySettings: self.storySettings)
} }
public func withUpdatedStoriesMuted(_ storiesMuted: Bool?) -> TelegramPeerNotificationSettings { public func withUpdatedStorySettings(_ storySettings: PeerStoryNotificationSettings) -> TelegramPeerNotificationSettings {
return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storiesMuted: storiesMuted) return TelegramPeerNotificationSettings(muteState: self.muteState, messageSound: self.messageSound, displayPreviews: self.displayPreviews, storySettings: storySettings)
} }
public static func ==(lhs: TelegramPeerNotificationSettings, rhs: TelegramPeerNotificationSettings) -> Bool { public static func ==(lhs: TelegramPeerNotificationSettings, rhs: TelegramPeerNotificationSettings) -> Bool {
return lhs.muteState == rhs.muteState && lhs.messageSound == rhs.messageSound && lhs.displayPreviews == rhs.displayPreviews && lhs.storiesMuted == rhs.storiesMuted return lhs.muteState == rhs.muteState && lhs.messageSound == rhs.messageSound && lhs.displayPreviews == rhs.displayPreviews && lhs.storySettings == rhs.storySettings
} }
} }

View File

@ -254,6 +254,44 @@ public extension TelegramEngine {
} }
} }
public func subscribe<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem,
T3: TelegramEngineDataItem,
T4: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2,
_ t3: T3,
_ t4: T4
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result,
T3.Result,
T4.Result
),
NoError> {
return self._subscribe(items: [
t0 as! AnyPostboxViewDataItem,
t1 as! AnyPostboxViewDataItem,
t2 as! AnyPostboxViewDataItem,
t3 as! AnyPostboxViewDataItem,
t4 as! AnyPostboxViewDataItem
])
|> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result, T4.Result) in
return (
results[0] as! T0.Result,
results[1] as! T1.Result,
results[2] as! T2.Result,
results[3] as! T3.Result,
results[4] as! T4.Result
)
}
}
public func get< public func get<
T0: TelegramEngineDataItem, T0: TelegramEngineDataItem,
@ -308,5 +346,29 @@ public extension TelegramEngine {
NoError> { NoError> {
return self.subscribe(t0, t1, t2, t3) |> take(1) return self.subscribe(t0, t1, t2, t3) |> take(1)
} }
public func get<
T0: TelegramEngineDataItem,
T1: TelegramEngineDataItem,
T2: TelegramEngineDataItem,
T3: TelegramEngineDataItem,
T4: TelegramEngineDataItem
>(
_ t0: T0,
_ t1: T1,
_ t2: T2,
_ t3: T3,
_ t4: T4
) -> Signal<
(
T0.Result,
T1.Result,
T2.Result,
T3.Result,
T4.Result
),
NoError> {
return self.subscribe(t0, t1, t2, t3, t4) |> take(1)
}
} }
} }

View File

@ -677,12 +677,17 @@ public extension TelegramEngine {
hasMoreToken = "" hasMoreToken = ""
} }
var accountPendingItemCount = 0
if let view = views.views[PostboxViewKey.storiesState(key: .local)] as? StoryStatesView, let localState = view.value?.get(Stories.LocalState.self) {
accountPendingItemCount = localState.items.count
}
var accountItem: EngineStorySubscriptions.Item = EngineStorySubscriptions.Item( var accountItem: EngineStorySubscriptions.Item = EngineStorySubscriptions.Item(
peer: EnginePeer(accountPeer), peer: EnginePeer(accountPeer),
hasUnseen: false, hasUnseen: false,
hasUnseenCloseFriends: false, hasUnseenCloseFriends: false,
hasPending: false, hasPending: accountPendingItemCount != 0,
storyCount: 0, storyCount: accountPendingItemCount,
unseenCount: 0, unseenCount: 0,
lastTimestamp: 0 lastTimestamp: 0
) )
@ -698,7 +703,6 @@ public extension TelegramEngine {
var hasUnseen = false var hasUnseen = false
var hasUnseenCloseFriends = false var hasUnseenCloseFriends = false
var unseenCount = 0 var unseenCount = 0
var hasPending = false
if let peerState = peerState { if let peerState = peerState {
hasUnseen = peerState.maxReadId < lastEntry.id hasUnseen = peerState.maxReadId < lastEntry.id
@ -717,18 +721,12 @@ public extension TelegramEngine {
} }
} }
if let view = views.views[PostboxViewKey.storiesState(key: .local)] as? StoryStatesView, let localState = view.value?.get(Stories.LocalState.self) {
if !localState.items.isEmpty {
hasPending = true
}
}
let item = EngineStorySubscriptions.Item( let item = EngineStorySubscriptions.Item(
peer: EnginePeer(accountPeer), peer: EnginePeer(accountPeer),
hasUnseen: hasUnseen, hasUnseen: hasUnseen,
hasUnseenCloseFriends: hasUnseenCloseFriends, hasUnseenCloseFriends: hasUnseenCloseFriends,
hasPending: hasPending, hasPending: accountPendingItemCount != 0,
storyCount: itemsView.items.count, storyCount: itemsView.items.count + accountPendingItemCount,
unseenCount: unseenCount, unseenCount: unseenCount,
lastTimestamp: lastEntry.timestamp lastTimestamp: lastEntry.timestamp
) )

View File

@ -62,6 +62,20 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64
} }
} }
public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: Peer, peerSettings: TelegramPeerNotificationSettings?) -> Bool {
let defaultIsMuted = globalSettings.privateChats.storySettings.mute == .muted
switch peerSettings?.storySettings.mute {
case .none:
return defaultIsMuted
case .muted:
return true
case .unmuted:
return false
default:
return defaultIsMuted
}
}
func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> { func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in return account.postbox.transaction { transaction -> Void in
guard let peer = transaction.getPeer(peerId) else { guard let peer = transaction.getPeer(peerId) else {
@ -82,12 +96,22 @@ func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signa
} }
let updatedSettings: TelegramPeerNotificationSettings let updatedSettings: TelegramPeerNotificationSettings
if let previousStoriesMuted = previousSettings.storiesMuted { var storySettings = previousSettings.storySettings
updatedSettings = previousSettings.withUpdatedStoriesMuted(!previousStoriesMuted) switch previousSettings.storySettings.mute {
} else { case .default:
updatedSettings = previousSettings.withUpdatedStoriesMuted(true) let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings
if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: peer, peerSettings: previousSettings) {
storySettings.mute = .unmuted
} else {
storySettings.mute = .muted
}
case .unmuted:
storySettings.mute = .muted
case .muted:
storySettings.mute = .unmuted
} }
updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: notificationPeerId, settings: updatedSettings) transaction.updatePendingPeerNotificationSettings(peerId: notificationPeerId, settings: updatedSettings)
} }
} }
@ -213,13 +237,13 @@ func _internal_updatePeerDisplayPreviewsSetting(account: Account, transaction: T
} }
} }
func _internal_updatePeerStoriesMutedSetting(account: Account, peerId: PeerId, isMuted: Bool?) -> Signal<Void, NoError> { func _internal_updatePeerStoriesMutedSetting(account: Account, peerId: PeerId, mute: PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Void in return account.postbox.transaction { transaction -> Void in
_internal_updatePeerStoriesMutedSetting(account: account, transaction: transaction, peerId: peerId, isMuted: isMuted) _internal_updatePeerStoriesMutedSetting(account: account, transaction: transaction, peerId: peerId, mute: mute)
} }
} }
func _internal_updatePeerStoriesMutedSetting(account: Account, transaction: Transaction, peerId: PeerId, isMuted: Bool?) { func _internal_updatePeerStoriesMutedSetting(account: Account, transaction: Transaction, peerId: PeerId, mute: PeerStoryNotificationSettings.Mute) {
if let peer = transaction.getPeer(peerId) { if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId { if let associatedPeerId = peer.associatedPeerId {
@ -234,7 +258,31 @@ func _internal_updatePeerStoriesMutedSetting(account: Account, transaction: Tran
previousSettings = TelegramPeerNotificationSettings.defaultSettings previousSettings = TelegramPeerNotificationSettings.defaultSettings
} }
let updatedSettings = previousSettings.withUpdatedStoriesMuted(isMuted) var storySettings = previousSettings.storySettings
storySettings.mute = mute
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}
func _internal_updatePeerStoriesHideSenderSetting(account: Account, transaction: Transaction, peerId: PeerId, hideSender: PeerStoryNotificationSettings.HideSender) {
if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var storySettings = previousSettings.storySettings
storySettings.hideSender = hideSender
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
} }
} }
@ -279,3 +327,25 @@ func _internal_updatePeerNotificationSoundInteractive(account: Account, transact
} }
} }
} }
func _internal_updatePeerStoryNotificationSoundInteractive(account: Account, transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) {
if let peer = transaction.getPeer(peerId) {
var notificationPeerId = peerId
if let associatedPeerId = peer.associatedPeerId {
notificationPeerId = associatedPeerId
}
let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings
let previousSettings: TelegramPeerNotificationSettings
if let currentSettings = currentSettings {
previousSettings = currentSettings
} else {
previousSettings = TelegramPeerNotificationSettings.defaultSettings
}
var storySettings = previousSettings.storySettings
storySettings.sound = sound
let updatedSettings = previousSettings.withUpdatedStorySettings(storySettings)
transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings)
}
}

View File

@ -19,7 +19,11 @@ public final class NotificationExceptionsList: Equatable {
} }
func _internal_notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> { func _internal_notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> {
return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil)) var flags: Int32 = 0
flags |= 1 << 1
flags |= 1 << 2
return network.request(Api.functions.account.getNotifyExceptions(flags: flags, peer: nil))
|> retryRequest |> retryRequest
|> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in |> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in
return postbox.transaction { transaction -> NotificationExceptionsList in return postbox.transaction { transaction -> NotificationExceptionsList in

View File

@ -84,22 +84,24 @@ public enum EnginePeer: Equatable {
case show case show
case hide case hide
} }
public typealias StorySettigs = PeerStoryNotificationSettings
public var muteState: MuteState public var muteState: MuteState
public var messageSound: MessageSound public var messageSound: MessageSound
public var displayPreviews: DisplayPreviews public var displayPreviews: DisplayPreviews
public var storiesMuted: Bool? public var storySettings: StorySettigs
public init( public init(
muteState: MuteState, muteState: MuteState,
messageSound: MessageSound, messageSound: MessageSound,
displayPreviews: DisplayPreviews, displayPreviews: DisplayPreviews,
storiesMuted: Bool? storySettings: StorySettigs
) { ) {
self.muteState = muteState self.muteState = muteState
self.messageSound = messageSound self.messageSound = messageSound
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
self.storiesMuted = storiesMuted self.storySettings = storySettings
} }
} }
@ -219,13 +221,13 @@ public struct EngineGlobalNotificationSettings: Equatable {
public var enabled: Bool public var enabled: Bool
public var displayPreviews: Bool public var displayPreviews: Bool
public var sound: EnginePeer.NotificationSettings.MessageSound public var sound: EnginePeer.NotificationSettings.MessageSound
public var storiesMuted: Bool public var storySettings: EnginePeer.NotificationSettings.StorySettigs
public init(enabled: Bool, displayPreviews: Bool, sound: EnginePeer.NotificationSettings.MessageSound, storiesMuted: Bool) { public init(enabled: Bool, displayPreviews: Bool, sound: EnginePeer.NotificationSettings.MessageSound, storySettings: EnginePeer.NotificationSettings.StorySettigs) {
self.enabled = enabled self.enabled = enabled
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
self.sound = sound self.sound = sound
self.storiesMuted = storiesMuted self.storySettings = storySettings
} }
} }
@ -333,7 +335,7 @@ public extension EnginePeer.NotificationSettings {
muteState: MuteState(notificationSettings.muteState), muteState: MuteState(notificationSettings.muteState),
messageSound: MessageSound(notificationSettings.messageSound), messageSound: MessageSound(notificationSettings.messageSound),
displayPreviews: DisplayPreviews(notificationSettings.displayPreviews), displayPreviews: DisplayPreviews(notificationSettings.displayPreviews),
storiesMuted: notificationSettings.storiesMuted storySettings: notificationSettings.storySettings
) )
} }
@ -342,7 +344,7 @@ public extension EnginePeer.NotificationSettings {
muteState: self.muteState._asMuteState(), muteState: self.muteState._asMuteState(),
messageSound: self.messageSound._asMessageSound(), messageSound: self.messageSound._asMessageSound(),
displayPreviews: self.displayPreviews._asDisplayPreviews(), displayPreviews: self.displayPreviews._asDisplayPreviews(),
storiesMuted: self.storiesMuted storySettings: self.storySettings
) )
} }
} }
@ -602,7 +604,7 @@ public extension EngineGlobalNotificationSettings.CategorySettings {
enabled: categorySettings.enabled, enabled: categorySettings.enabled,
displayPreviews: categorySettings.displayPreviews, displayPreviews: categorySettings.displayPreviews,
sound: EnginePeer.NotificationSettings.MessageSound(categorySettings.sound), sound: EnginePeer.NotificationSettings.MessageSound(categorySettings.sound),
storiesMuted: categorySettings.storiesMuted ?? false storySettings: categorySettings.storySettings
) )
} }
@ -611,7 +613,7 @@ public extension EngineGlobalNotificationSettings.CategorySettings {
enabled: self.enabled, enabled: self.enabled,
displayPreviews: self.displayPreviews, displayPreviews: self.displayPreviews,
sound: self.sound._asMessageSound(), sound: self.sound._asMessageSound(),
storiesMuted: self.storiesMuted storySettings: self.storySettings
) )
} }
} }
@ -625,4 +627,13 @@ public extension EngineGlobalNotificationSettings {
contactsJoined: globalNotificationSettings.contactsJoined contactsJoined: globalNotificationSettings.contactsJoined
) )
} }
func _asGlobalNotificationSettings() -> GlobalNotificationSettingsSet {
return GlobalNotificationSettingsSet(
privateChats: self.privateChats._asMessageNotificationSettings(),
groupChats: self.groupChats._asMessageNotificationSettings(),
channels: self.channels._asMessageNotificationSettings(),
contactsJoined: self.contactsJoined
)
}
} }

View File

@ -273,8 +273,20 @@ public extension TelegramEngine {
return _internal_updatePeerDisplayPreviewsSetting(account: self.account, peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) return _internal_updatePeerDisplayPreviewsSetting(account: self.account, peerId: peerId, threadId: threadId, displayPreviews: displayPreviews)
} }
public func updatePeerStoriesMutedSetting(peerId: PeerId, isMuted: Bool?) -> Signal<Void, NoError> { public func updatePeerStoriesMutedSetting(peerId: PeerId, mute: PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> {
return _internal_updatePeerStoriesMutedSetting(account: self.account, peerId: peerId, isMuted: isMuted) return _internal_updatePeerStoriesMutedSetting(account: self.account, peerId: peerId, mute: mute)
}
public func updatePeerStoriesHideSenderSetting(peerId: PeerId, hideSender: PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> {
return self.account.postbox.transaction { transaction -> Void in
_internal_updatePeerStoriesHideSenderSetting(account: self.account, transaction: transaction, peerId: peerId, hideSender: hideSender)
}
}
public func updatePeerStorySoundInteractive(peerId: PeerId, sound: PeerMessageSound) -> Signal<Void, NoError> {
return self.account.postbox.transaction { transaction -> Void in
_internal_updatePeerStoryNotificationSoundInteractive(account: self.account, transaction: transaction, peerId: peerId, sound: sound)
}
} }
public func updatePeerNotificationSoundInteractive(peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) -> Signal<Void, NoError> { public func updatePeerNotificationSoundInteractive(peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) -> Signal<Void, NoError> {
@ -306,7 +318,9 @@ public extension TelegramEngine {
public func removeCustomStoryNotificationSettings(peerIds: [PeerId]) -> Signal<Never, NoError> { public func removeCustomStoryNotificationSettings(peerIds: [PeerId]) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in return self.account.postbox.transaction { transaction -> Void in
for peerId in peerIds { for peerId in peerIds {
_internal_updatePeerStoriesMutedSetting(account: self.account, transaction: transaction, peerId: peerId, isMuted: nil) _internal_updatePeerStoriesMutedSetting(account: self.account, transaction: transaction, peerId: peerId, mute: .default)
_internal_updatePeerStoriesHideSenderSetting(account: self.account, transaction: transaction, peerId: peerId, hideSender: .default)
_internal_updatePeerStoryNotificationSoundInteractive(account: self.account, transaction: transaction, peerId: peerId, sound: .default)
} }
} }
|> ignoreValues |> ignoreValues

View File

@ -496,7 +496,9 @@ public func threadNotificationExceptionsScreen(context: AccountContext, peerId:
} }
updated(stateValue.with({ $0 }).notificationExceptions) updated(stateValue.with({ $0 }).notificationExceptions)
}) })
}, updatePeerStoryNotifications: { _, _ in }, updatePeerStoriesMuted: { _, _ in
}, updatePeerStoriesHideSender: { _, _ in
}, updatePeerStorySound: { _, _ in
}, removePeerFromExceptions: { }, removePeerFromExceptions: {
let _ = context.engine.peers.removeCustomThreadNotificationSettings(peerId: peerId, threadIds: [item.threadId]).start() let _ = context.engine.peers.removeCustomThreadNotificationSettings(peerId: peerId, threadIds: [item.threadId]).start()
updateState { current in updateState { current in

View File

@ -123,7 +123,7 @@ public enum NotificationExceptionMode : Equatable {
case .default: case .default:
break break
default: default:
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .default, messageSound: sound, displayPreviews: .default, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970) values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .default, messageSound: sound, displayPreviews: .default, storySettings: .default), peer: peer, date: Date().timeIntervalSince1970)
} }
} }
return values return values
@ -161,7 +161,7 @@ public enum NotificationExceptionMode : Equatable {
case .default: case .default:
break break
default: default:
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: muteState, messageSound: .default, displayPreviews: .default, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970) values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: muteState, messageSound: .default, displayPreviews: .default, storySettings: .default), peer: peer, date: Date().timeIntervalSince1970)
} }
} }
return values return values
@ -215,7 +215,7 @@ public enum NotificationExceptionMode : Equatable {
case .default: case .default:
break break
default: default:
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: displayPreviews, storiesMuted: nil), peer: peer, date: Date().timeIntervalSince1970) values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: displayPreviews, storySettings: .default), peer: peer, date: Date().timeIntervalSince1970)
} }
} }
return values return values
@ -233,32 +233,38 @@ public enum NotificationExceptionMode : Equatable {
} }
} }
public func withUpdatedPeerStoryNotifications(_ peer: EnginePeer, _ storyNotifications: PeerNotificationDisplayPreviews) -> NotificationExceptionMode { public func withUpdatedPeerStoriesMuted(_ peer: EnginePeer, _ mute: PeerStoryNotificationSettings.Mute) -> NotificationExceptionMode {
let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerNotificationDisplayPreviews) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, storyNotifications in let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerStoryNotificationSettings.Mute) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, mute in
let storiesMuted: Bool?
switch storyNotifications {
case .default:
storiesMuted = nil
case .show:
storiesMuted = false
case .hide:
storiesMuted = true
}
var values = values var values = values
if let value = values[peerId] { if let value = values[peerId] {
switch storyNotifications { switch mute {
case .default: case .default:
values.removeValue(forKey: peerId) switch value.settings.storySettings.mute {
case .default:
//values.removeValue(forKey: peerId)
break
default:
values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.mute = mute
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
}
default: default:
values[peerId] = value.updateSettings({$0.withUpdatedStoriesMuted(storiesMuted)}).withUpdatedDate(Date().timeIntervalSince1970) values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.mute = mute
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
} }
} else { } else {
switch storyNotifications { switch mute {
case .default: case .default:
break break
default: default:
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storiesMuted: storiesMuted), peer: peer, date: Date().timeIntervalSince1970) var updatedSettings = PeerStoryNotificationSettings.default
updatedSettings.mute = mute
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storySettings: updatedSettings), peer: peer, date: Date().timeIntervalSince1970)
} }
} }
return values return values
@ -266,7 +272,97 @@ public enum NotificationExceptionMode : Equatable {
switch self { switch self {
case let .stories(values): case let .stories(values):
return .stories(apply(values, peer.id, storyNotifications)) return .stories(apply(values, peer.id, mute))
default:
return self
}
}
public func withUpdatedPeerStoriesHideSender(_ peer: EnginePeer, _ hideSender: PeerStoryNotificationSettings.HideSender) -> NotificationExceptionMode {
let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerStoryNotificationSettings.HideSender) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, hideSender in
var values = values
if let value = values[peerId] {
switch hideSender {
case .default:
switch value.settings.storySettings.hideSender {
case .default:
//values.removeValue(forKey: peerId)
break
default:
values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.hideSender = hideSender
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
}
default:
values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.hideSender = hideSender
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
}
} else {
switch hideSender {
case .default:
break
default:
var updatedSettings = PeerStoryNotificationSettings.default
updatedSettings.hideSender = hideSender
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storySettings: updatedSettings), peer: peer, date: Date().timeIntervalSince1970)
}
}
return values
}
switch self {
case let .stories(values):
return .stories(apply(values, peer.id, hideSender))
default:
return self
}
}
public func withUpdatedPeerStorySound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionMode {
let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerMessageSound) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, sound in
var values = values
if let value = values[peerId] {
switch sound {
case .default:
switch value.settings.storySettings.sound {
case .default:
//values.removeValue(forKey: peerId)
break
default:
values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.sound = sound
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
}
default:
values[peerId] = value.updateSettings({
var updatedSettings = $0.storySettings
updatedSettings.sound = sound
return $0.withUpdatedStorySettings(updatedSettings)
}).withUpdatedDate(Date().timeIntervalSince1970)
}
} else {
switch sound {
case .default:
break
default:
var updatedSettings = PeerStoryNotificationSettings.default
updatedSettings.sound = sound
values[peerId] = NotificationExceptionWrapper(settings: TelegramPeerNotificationSettings(muteState: .unmuted, messageSound: .default, displayPreviews: .default, storySettings: updatedSettings), peer: peer, date: Date().timeIntervalSince1970)
}
}
return values
}
switch self {
case let .stories(values):
return .stories(apply(values, peer.id, sound))
default: default:
return self return self
} }
@ -308,6 +404,7 @@ private enum NotificationPeerExceptionEntryId: Hashable {
case sound(PeerMessageSound.Id) case sound(PeerMessageSound.Id)
case switcherHeader case switcherHeader
case displayPreviews(NotificationPeerExceptionSwitcher) case displayPreviews(NotificationPeerExceptionSwitcher)
case showSender(NotificationPeerExceptionSwitcher)
case displayPreviewsHeader case displayPreviewsHeader
case storyNotifications(NotificationPeerExceptionSwitcher) case storyNotifications(NotificationPeerExceptionSwitcher)
case storyNotificationsHeader case storyNotificationsHeader
@ -326,19 +423,36 @@ private final class NotificationPeerExceptionArguments {
let selectSound: (PeerMessageSound) -> Void let selectSound: (PeerMessageSound) -> Void
let selectMode: (NotificationPeerExceptionSwitcher) -> Void let selectMode: (NotificationPeerExceptionSwitcher) -> Void
let selectDisplayPreviews: (NotificationPeerExceptionSwitcher) -> Void let selectDisplayPreviews: (NotificationPeerExceptionSwitcher) -> Void
let selectStoryNotifications: (NotificationPeerExceptionSwitcher) -> Void let selectStoriesMuted: (NotificationPeerExceptionSwitcher) -> Void
let selectHideStoriesSender: (NotificationPeerExceptionSwitcher) -> Void
let selectStorySound: (PeerMessageSound) -> Void
let removeFromExceptions: () -> Void let removeFromExceptions: () -> Void
let complete: () -> Void let complete: () -> Void
let cancel: () -> Void let cancel: () -> Void
let upload: () -> Void let upload: () -> Void
let deleteSound: (PeerMessageSound, String) -> Void let deleteSound: (PeerMessageSound, String) -> Void
init(account: Account, selectSound: @escaping(PeerMessageSound) -> Void, selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void, selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void, selectStoryNotifications: @escaping (NotificationPeerExceptionSwitcher) -> Void, removeFromExceptions: @escaping () -> Void, complete: @escaping()->Void, cancel: @escaping() -> Void, upload: @escaping () -> Void, deleteSound: @escaping (PeerMessageSound, String) -> Void) { init(
account: Account,
selectSound: @escaping(PeerMessageSound) -> Void,
selectMode: @escaping(NotificationPeerExceptionSwitcher) -> Void,
selectDisplayPreviews: @escaping (NotificationPeerExceptionSwitcher) -> Void,
selectStoriesMuted: @escaping (NotificationPeerExceptionSwitcher) -> Void,
selectHideStoriesSender: @escaping (NotificationPeerExceptionSwitcher) -> Void,
selectStorySound: @escaping (PeerMessageSound) -> Void,
removeFromExceptions: @escaping () -> Void,
complete: @escaping() -> Void,
cancel: @escaping() -> Void,
upload: @escaping () -> Void,
deleteSound: @escaping (PeerMessageSound, String) -> Void
) {
self.account = account self.account = account
self.selectSound = selectSound self.selectSound = selectSound
self.selectMode = selectMode self.selectMode = selectMode
self.selectDisplayPreviews = selectDisplayPreviews self.selectDisplayPreviews = selectDisplayPreviews
self.selectStoryNotifications = selectStoryNotifications self.selectStoriesMuted = selectStoriesMuted
self.selectHideStoriesSender = selectHideStoriesSender
self.selectStorySound = selectStorySound
self.removeFromExceptions = removeFromExceptions self.removeFromExceptions = removeFromExceptions
self.complete = complete self.complete = complete
self.cancel = cancel self.cancel = cancel
@ -355,6 +469,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
case switcher(index:Int32, theme: PresentationTheme, strings: PresentationStrings, mode: NotificationPeerExceptionSwitcher, selected: Bool) case switcher(index:Int32, theme: PresentationTheme, strings: PresentationStrings, mode: NotificationPeerExceptionSwitcher, selected: Bool)
case switcherHeader(index:Int32, theme: PresentationTheme, title: String) case switcherHeader(index:Int32, theme: PresentationTheme, title: String)
case displayPreviews(index:Int32, theme: PresentationTheme, strings: PresentationStrings, value: NotificationPeerExceptionSwitcher, selected: Bool) case displayPreviews(index:Int32, theme: PresentationTheme, strings: PresentationStrings, value: NotificationPeerExceptionSwitcher, selected: Bool)
case showSender(index:Int32, theme: PresentationTheme, strings: PresentationStrings, value: NotificationPeerExceptionSwitcher, selected: Bool)
case displayPreviewsHeader(index:Int32, theme: PresentationTheme, title: String) case displayPreviewsHeader(index:Int32, theme: PresentationTheme, title: String)
case storyNotifications(index:Int32, theme: PresentationTheme, strings: PresentationStrings, value: NotificationPeerExceptionSwitcher, selected: Bool) case storyNotifications(index:Int32, theme: PresentationTheme, strings: PresentationStrings, value: NotificationPeerExceptionSwitcher, selected: Bool)
case storyNotificationsHeader(index:Int32, theme: PresentationTheme, title: String) case storyNotificationsHeader(index:Int32, theme: PresentationTheme, title: String)
@ -379,6 +494,8 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return index return index
case let .displayPreviews(index, _, _, _, _): case let .displayPreviews(index, _, _, _, _):
return index return index
case let .showSender(index, _, _, _, _):
return index
case let .storyNotificationsHeader(index, _, _): case let .storyNotificationsHeader(index, _, _):
return index return index
case let .storyNotifications(index, _, _, _, _): case let .storyNotifications(index, _, _, _, _):
@ -408,7 +525,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return NotificationPeerExceptionSection.remove.rawValue return NotificationPeerExceptionSection.remove.rawValue
case .switcher, .switcherHeader: case .switcher, .switcherHeader:
return NotificationPeerExceptionSection.switcher.rawValue return NotificationPeerExceptionSection.switcher.rawValue
case .displayPreviews, .displayPreviewsHeader: case .displayPreviews, .displayPreviewsHeader, .showSender:
return NotificationPeerExceptionSection.displayPreviews.rawValue return NotificationPeerExceptionSection.displayPreviews.rawValue
case .storyNotifications, .storyNotificationsHeader: case .storyNotifications, .storyNotificationsHeader:
return NotificationPeerExceptionSection.storyNotifications.rawValue return NotificationPeerExceptionSection.storyNotifications.rawValue
@ -439,6 +556,8 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return .displayPreviews(mode) return .displayPreviews(mode)
case .displayPreviewsHeader: case .displayPreviewsHeader:
return .displayPreviewsHeader return .displayPreviewsHeader
case let .showSender(_, _, _, mode, _):
return .showSender(mode)
case let .storyNotifications(_, _, _, mode, _): case let .storyNotifications(_, _, _, mode, _):
return .storyNotifications(mode) return .storyNotifications(mode)
case .storyNotificationsHeader: case .storyNotificationsHeader:
@ -497,6 +616,17 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectDisplayPreviews(value) arguments.selectDisplayPreviews(value)
}) })
case let .showSender(_, _, strings, value, selected):
let title: String
switch value {
case .alwaysOn:
title = strings.Notification_Exceptions_MessagePreviewAlwaysOn
case .alwaysOff:
title = strings.Notification_Exceptions_MessagePreviewAlwaysOff
}
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectHideStoriesSender(value)
})
case let .cloudHeader(_, text): case let .cloudHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .cloudInfo(_, text): case let .cloudInfo(_, text):
@ -517,7 +647,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
title = strings.Notification_Exceptions_MessagePreviewAlwaysOff title = strings.Notification_Exceptions_MessagePreviewAlwaysOff
} }
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.selectStoryNotifications(value) arguments.selectStoriesMuted(value)
}) })
case let .storyNotificationsHeader(_, _, text): case let .storyNotificationsHeader(_, _, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
@ -545,7 +675,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry {
private func notificationPeerExceptionEntries(presentationData: PresentationData, peer: EnginePeer?, notificationSoundList: NotificationSoundList?, state: NotificationExceptionPeerState, isStories: Bool?) -> [NotificationPeerExceptionEntry] { private func notificationPeerExceptionEntries(presentationData: PresentationData, peer: EnginePeer?, notificationSoundList: NotificationSoundList?, state: NotificationExceptionPeerState, isStories: Bool?) -> [NotificationPeerExceptionEntry] {
let selectedSound = resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList) let selectedSound = resolvedNotificationSound(sound: isStories == true ? state.selectedStoriesSound : state.selectedSound, notificationSoundList: notificationSoundList)
var entries: [NotificationPeerExceptionEntry] = [] var entries: [NotificationPeerExceptionEntry] = []
@ -580,14 +710,31 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
//TODO:localize //TODO:localize
entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: "STORY NOTIFICATIONS")) entries.append(.storyNotificationsHeader(index: index, theme: presentationData.theme, title: "STORY NOTIFICATIONS"))
index += 1 index += 1
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storyNotifications == .alwaysOn)) entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesMuted == .alwaysOn))
index += 1 index += 1
entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOff, selected: state.storyNotifications == .alwaysOff)) entries.append(.storyNotifications(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOff, selected: state.storiesMuted == .alwaysOff))
index += 1 index += 1
if state.storiesMuted != .alwaysOff {
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: "Display Author Name"))
index += 1
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
index += 1
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOff, selected: state.storiesHideSender == .alwaysOff))
index += 1
}
} }
} }
if isStories == nil || isStories == false { var displaySounds = true
if isStories == true {
if state.storiesMuted == .alwaysOff {
displaySounds = false
}
}
if displaySounds {
entries.append(.cloudHeader(index: index, text: presentationData.strings.Notifications_TelegramTones)) entries.append(.cloudHeader(index: index, text: presentationData.strings.Notifications_TelegramTones))
index += 1 index += 1
@ -650,7 +797,9 @@ private struct NotificationExceptionPeerState : Equatable {
var mode: NotificationPeerExceptionSwitcher var mode: NotificationPeerExceptionSwitcher
var defaultSound: PeerMessageSound var defaultSound: PeerMessageSound
var displayPreviews: NotificationPeerExceptionSwitcher var displayPreviews: NotificationPeerExceptionSwitcher
var storyNotifications: NotificationPeerExceptionSwitcher var storiesMuted: NotificationPeerExceptionSwitcher
var selectedStoriesSound: PeerMessageSound
var storiesHideSender: NotificationPeerExceptionSwitcher
var removedSounds: [PeerMessageSound] var removedSounds: [PeerMessageSound]
init(canRemove: Bool, notifications: TelegramPeerNotificationSettings? = nil) { init(canRemove: Bool, notifications: TelegramPeerNotificationSettings? = nil) {
@ -665,30 +814,64 @@ private struct NotificationExceptionPeerState : Equatable {
self.mode = .alwaysOn self.mode = .alwaysOn
} }
self.displayPreviews = notifications.displayPreviews == .hide ? .alwaysOff : .alwaysOn self.displayPreviews = notifications.displayPreviews == .hide ? .alwaysOff : .alwaysOn
self.storyNotifications = notifications.storiesMuted == true ? .alwaysOff : .alwaysOn self.storiesMuted = notifications.storySettings.mute == .muted ? .alwaysOff : .alwaysOn
self.selectedStoriesSound = notifications.storySettings.sound
self.storiesHideSender = notifications.storySettings.hideSender == .hide ? .alwaysOff : .alwaysOn
} else { } else {
self.selectedSound = .default self.selectedSound = .default
self.mode = .alwaysOn self.mode = .alwaysOn
self.displayPreviews = .alwaysOn self.displayPreviews = .alwaysOn
self.storyNotifications = .alwaysOn self.storiesMuted = PeerStoryNotificationSettings.default.mute == .muted ? .alwaysOff : .alwaysOn
self.selectedStoriesSound = PeerStoryNotificationSettings.default.sound
self.storiesHideSender = PeerStoryNotificationSettings.default.hideSender == .hide ? .alwaysOff : .alwaysOn
} }
self.defaultSound = .default self.defaultSound = .default
self.removedSounds = [] self.removedSounds = []
} }
init(canRemove: Bool, selectedSound: PeerMessageSound, mode: NotificationPeerExceptionSwitcher, defaultSound: PeerMessageSound, displayPreviews: NotificationPeerExceptionSwitcher, storyNotifications: NotificationPeerExceptionSwitcher, removedSounds: [PeerMessageSound]) { init(
canRemove: Bool,
selectedSound: PeerMessageSound,
mode: NotificationPeerExceptionSwitcher,
defaultSound: PeerMessageSound,
displayPreviews: NotificationPeerExceptionSwitcher,
storiesMuted: NotificationPeerExceptionSwitcher,
selectedStoriesSound: PeerMessageSound,
storiesHideSender: NotificationPeerExceptionSwitcher,
removedSounds: [PeerMessageSound]
) {
self.canRemove = canRemove self.canRemove = canRemove
self.selectedSound = selectedSound self.selectedSound = selectedSound
self.mode = mode self.mode = mode
self.defaultSound = defaultSound self.defaultSound = defaultSound
self.displayPreviews = displayPreviews self.displayPreviews = displayPreviews
self.storyNotifications = storyNotifications self.storiesMuted = storiesMuted
self.selectedStoriesSound = selectedStoriesSound
self.storiesHideSender = storiesHideSender
self.removedSounds = removedSounds self.removedSounds = removedSounds
} }
} }
public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer, customTitle: String? = nil, threadId: Int64?, isStories: Bool?, canRemove: Bool, defaultSound: PeerMessageSound, edit: Bool = false, updatePeerSound: @escaping(EnginePeer.Id, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(EnginePeer.Id, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Void, updatePeerStoryNotifications: @escaping(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { public func notificationPeerExceptionController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peer: EnginePeer,
customTitle: String? = nil,
threadId: Int64?,
isStories: Bool?,
canRemove: Bool,
defaultSound: PeerMessageSound,
edit: Bool = false,
updatePeerSound: @escaping(EnginePeer.Id, PeerMessageSound) -> Void,
updatePeerNotificationInterval: @escaping(EnginePeer.Id, Int32?) -> Void,
updatePeerDisplayPreviews: @escaping(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Void,
updatePeerStoriesMuted: @escaping(EnginePeer.Id, PeerStoryNotificationSettings.Mute) -> Void,
updatePeerStoriesHideSender: @escaping (EnginePeer.Id, PeerStoryNotificationSettings.HideSender) -> Void,
updatePeerStorySound: @escaping (EnginePeer.Id, PeerMessageSound) -> Void,
removePeerFromExceptions: @escaping () -> Void,
modifiedPeer: @escaping () -> Void
) -> ViewController {
let initialState = NotificationExceptionPeerState(canRemove: false) let initialState = NotificationExceptionPeerState(canRemove: false)
let statePromise = Promise(initialState) let statePromise = Promise(initialState)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -706,16 +889,30 @@ public func notificationPeerExceptionController(context: AccountContext, updated
let soundActionDisposable = MetaDisposable() let soundActionDisposable = MetaDisposable()
let arguments = NotificationPeerExceptionArguments(account: context.account, selectSound: { sound in let arguments = NotificationPeerExceptionArguments(account: context.account, selectSound: { sound in
updateState { state in if isStories == true {
let _ = (context.engine.peers.notificationSoundList() updateState { state in
|> take(1) let _ = (context.engine.peers.notificationSoundList()
|> deliverOnMainQueue).start(next: { notificationSoundList in |> take(1)
playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start()) |> deliverOnMainQueue).start(next: { notificationSoundList in
}) playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start())
})
var state = state
state.selectedSound = sound var state = state
return state state.selectedStoriesSound = sound
return state
}
} else {
updateState { state in
let _ = (context.engine.peers.notificationSoundList()
|> take(1)
|> deliverOnMainQueue).start(next: { notificationSoundList in
playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start())
})
var state = state
state.selectedSound = sound
return state
}
} }
}, selectMode: { mode in }, selectMode: { mode in
updateState { state in updateState { state in
@ -729,10 +926,28 @@ public func notificationPeerExceptionController(context: AccountContext, updated
state.displayPreviews = value state.displayPreviews = value
return state return state
} }
}, selectStoryNotifications: { value in }, selectStoriesMuted: { value in
updateState { state in updateState { state in
var state = state var state = state
state.storyNotifications = value state.storiesMuted = value
return state
}
}, selectHideStoriesSender: { value in
updateState { state in
var state = state
state.storiesHideSender = value
return state
}
}, selectStorySound: { sound in
updateState { state in
let _ = (context.engine.peers.notificationSoundList()
|> take(1)
|> deliverOnMainQueue).start(next: { notificationSoundList in
playSoundDisposable.set(playSound(context: context, notificationSoundList: notificationSoundList, sound: sound, defaultSound: state.defaultSound).start())
})
var state = state
state.selectedStoriesSound = sound
return state return state
} }
}, removeFromExceptions: { }, removeFromExceptions: {
@ -816,10 +1031,16 @@ public func notificationPeerExceptionController(context: AccountContext, updated
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { notificationSoundList in |> deliverOnMainQueue).start(next: { notificationSoundList in
updateState { state in updateState { state in
updatePeerSound(peer.id, resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList)) if isStories == nil || isStories == false {
updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max) updatePeerSound(peer.id, resolvedNotificationSound(sound: state.selectedSound, notificationSoundList: notificationSoundList))
updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide) updatePeerNotificationInterval(peer.id, state.mode == .alwaysOn ? 0 : Int32.max)
updatePeerStoryNotifications(peer.id, state.storyNotifications == .alwaysOn ? .show : .hide) updatePeerDisplayPreviews(peer.id, state.displayPreviews == .alwaysOn ? .show : .hide)
}
if isStories == nil || isStories == true {
updatePeerStoriesMuted(peer.id, state.storiesMuted == .alwaysOn ? .unmuted : .muted)
updatePeerStoriesHideSender(peer.id, state.storiesHideSender == .alwaysOn ? .show : .hide)
updatePeerStorySound(peer.id, resolvedNotificationSound(sound: state.selectedStoriesSound, notificationSoundList: notificationSoundList))
}
return state return state
} }
}) })

View File

@ -15,7 +15,7 @@ private struct StoryKey: Hashable {
public final class StoryContentContextImpl: StoryContentContext { public final class StoryContentContextImpl: StoryContentContext {
private final class PeerContext { private final class PeerContext {
private let context: AccountContext private let context: AccountContext
private let peerId: EnginePeer.Id let peerId: EnginePeer.Id
private(set) var sliceValue: StoryContentContextState.FocusedSlice? private(set) var sliceValue: StoryContentContextState.FocusedSlice?
fileprivate var nextItems: [EngineStoryItem] = [] fileprivate var nextItems: [EngineStoryItem] = []
@ -100,10 +100,10 @@ public final class StoryContentContextImpl: StoryContentContext {
let additionalPeerData: StoryContentContextState.AdditionalPeerData let additionalPeerData: StoryContentContextState.AdditionalPeerData
if let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData { if let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData {
var isMuted = false var isMuted = false
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, let storiesMuted = notificationSettings.storiesMuted { if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
isMuted = storiesMuted isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings)
} else { } else {
isMuted = globalNotificationSettings.privateChats.storiesMuted isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil)
} }
additionalPeerData = StoryContentContextState.AdditionalPeerData(isMuted: isMuted, areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable) additionalPeerData = StoryContentContextState.AdditionalPeerData(isMuted: isMuted, areVoiceMessagesAvailable: cachedUserData.voiceMessagesAvailable)
} else { } else {
@ -513,29 +513,26 @@ public final class StoryContentContextImpl: StoryContentContext {
} else { } else {
var startedWithUnseenValue = false var startedWithUnseenValue = false
if let (focusedPeerId, _) = self.focusedItem, focusedPeerId == self.context.account.peerId { var centralIndex: Int?
} else { if let (focusedPeerId, _) = self.focusedItem {
var centralIndex: Int? if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == focusedPeerId }) {
if let (focusedPeerId, _) = self.focusedItem { centralIndex = index
if let index = storySubscriptions.items.firstIndex(where: { $0.peer.id == focusedPeerId }) {
centralIndex = index
}
} }
if centralIndex == nil { }
if let index = storySubscriptions.items.firstIndex(where: { $0.hasUnseen }) { if centralIndex == nil {
centralIndex = index if let index = storySubscriptions.items.firstIndex(where: { $0.hasUnseen }) {
} centralIndex = index
} }
if centralIndex == nil { }
if !storySubscriptions.items.isEmpty { if centralIndex == nil {
centralIndex = 0 if !storySubscriptions.items.isEmpty {
} centralIndex = 0
} }
}
if let centralIndex {
if storySubscriptions.items[centralIndex].hasUnseen { if let centralIndex {
startedWithUnseenValue = true if storySubscriptions.items[centralIndex].hasUnseen {
} startedWithUnseenValue = true
} }
} }
@ -585,9 +582,11 @@ public final class StoryContentContextImpl: StoryContentContext {
} }
private func updatePeerContexts() { private func updatePeerContexts() {
if let currentState = self.currentState { if let currentState = self.currentState, let storySubscriptions = self.storySubscriptions, !storySubscriptions.items.contains(where: { $0.peer.id == currentState.centralPeerContext.peerId }) {
let _ = currentState self.currentState = nil
} else { }
if self.currentState == nil {
self.switchToFocusedPeerId() self.switchToFocusedPeerId()
} }
} }
@ -914,13 +913,12 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
let (item, peers) = itemAndPeers let (item, peers) = itemAndPeers
var isMuted = false guard let peer else {
if let storiesMuted = notificationSettings.storiesMuted { return
isMuted = storiesMuted
} else {
isMuted = globalNotificationSettings.privateChats.storiesMuted
} }
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
let additionalPeerData = StoryContentContextState.AdditionalPeerData( let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted, isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable areVoiceMessagesAvailable: areVoiceMessagesAvailable
@ -935,7 +933,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
} }
} }
if let item, case let .item(itemValue) = item, let media = itemValue.media, let peer { if let item, case let .item(itemValue) = item, let media = itemValue.media {
let mappedItem = EngineStoryItem( let mappedItem = EngineStoryItem(
id: itemValue.id, id: itemValue.id,
timestamp: itemValue.timestamp, timestamp: itemValue.timestamp,
@ -1063,13 +1061,12 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data let (peer, areVoiceMessagesAvailable, notificationSettings, globalNotificationSettings) = data
var isMuted = false guard let peer else {
if let storiesMuted = notificationSettings.storiesMuted { return
isMuted = storiesMuted
} else {
isMuted = globalNotificationSettings.privateChats.storiesMuted
} }
let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings())
let additionalPeerData = StoryContentContextState.AdditionalPeerData( let additionalPeerData = StoryContentContextState.AdditionalPeerData(
isMuted: isMuted, isMuted: isMuted,
areVoiceMessagesAvailable: areVoiceMessagesAvailable areVoiceMessagesAvailable: areVoiceMessagesAvailable
@ -1105,7 +1102,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
} }
let stateValue: StoryContentContextState let stateValue: StoryContentContextState
if let focusedIndex = focusedIndex, let peer = peer { if let focusedIndex = focusedIndex {
let item = state.items[focusedIndex] let item = state.items[focusedIndex]
self.focusedId = item.id self.focusedId = item.id
@ -1152,7 +1149,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
var resultResources: [EngineMediaResource.Id: StoryPreloadInfo] = [:] var resultResources: [EngineMediaResource.Id: StoryPreloadInfo] = [:]
var pollItems: [StoryKey] = [] var pollItems: [StoryKey] = []
if let peer, let focusedIndex, let slice = stateValue.slice { if let focusedIndex, let slice = stateValue.slice {
var possibleItems: [(EnginePeer, EngineStoryItem)] = [] var possibleItems: [(EnginePeer, EngineStoryItem)] = []
if peer.id == self.context.account.peerId { if peer.id == self.context.account.peerId {
pollItems.append(StoryKey(peerId: peer.id, id: slice.item.storyItem.id)) pollItems.append(StoryKey(peerId: peer.id, id: slice.item.storyItem.id))

View File

@ -138,7 +138,7 @@ final class StoryItemContentComponent: Component {
loopVideo: true, loopVideo: true,
enableSound: true, enableSound: true,
beginWithAmbientSound: environment.sharedState.useAmbientMode, beginWithAmbientSound: environment.sharedState.useAmbientMode,
useLargeThumbnail: true, useLargeThumbnail: false,
autoFetchFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true,
tempFilePath: nil, tempFilePath: nil,
captureProtected: component.item.isForwardingDisabled, captureProtected: component.item.isForwardingDisabled,
@ -429,7 +429,7 @@ final class StoryItemContentComponent: Component {
userLocation: .other, userLocation: .other,
videoReference: .story(peer: peerReference, id: component.item.id, media: file), videoReference: .story(peer: peerReference, id: component.item.id, media: file),
onlyFullSize: false, onlyFullSize: false,
useLargeThumbnail: true, useLargeThumbnail: false,
synchronousLoad: synchronousLoad, synchronousLoad: synchronousLoad,
autoFetchFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true,
overlayColor: nil, overlayColor: nil,

View File

@ -982,9 +982,18 @@ public final class StoryItemSetContainerComponent: Component {
return true return true
} }
} else { } else {
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { var canReply = true
inputPanelView.activateInput() if component.slice.peer.isService {
return false canReply = false
} else if case .unsupported = component.slice.item.storyItem.media {
canReply = false
}
if canReply {
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
inputPanelView.activateInput()
return false
}
} }
} }
return false return false
@ -3136,8 +3145,11 @@ public final class StoryItemSetContainerComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return return
} }
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: component.slice.peer.id)) let _ = (component.context.engine.data.get(
|> deliverOnMainQueue).start(next: { [weak self] settings in TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: component.slice.peer.id),
TelegramEngine.EngineData.Item.NotificationSettings.Global()
)
|> deliverOnMainQueue).start(next: { [weak self] settings, globalSettings in
guard let self, let component = self.component, let controller = component.controller() else { guard let self, let component = self.component, let controller = component.controller() else {
return return
} }
@ -3152,7 +3164,8 @@ public final class StoryItemSetContainerComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
let isMuted = settings.storiesMuted == true let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings())
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: component.slice.additionalPeerData.isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in

View File

@ -22,8 +22,8 @@ swift_library(
"//submodules/ContextUI", "//submodules/ContextUI",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/ActivityIndicator",
"//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/Components/HierarchyTrackingLayer",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -240,10 +240,50 @@ public final class StoryPeerListComponent: Component {
} }
} }
private final class TitleAnimationState {
let duration: Double
let startTime: Double
let fromFraction: CGFloat
let toFraction: CGFloat
let imageView: UIImageView
init(
duration: Double,
startTime: Double,
fromFraction: CGFloat,
toFraction: CGFloat,
imageView: UIImageView
) {
self.duration = duration
self.startTime = startTime
self.fromFraction = fromFraction
self.toFraction = toFraction
self.imageView = imageView
}
func interpolatedFraction(at timestamp: Double, effectiveFromFraction: CGFloat, toFraction: CGFloat) -> CGFloat {
var rawProgress = CGFloat((timestamp - self.startTime) / self.duration)
rawProgress = max(0.0, min(1.0, rawProgress))
let progress = listViewAnimationCurveSystem(rawProgress)
return effectiveFromFraction * (1.0 - progress) + toFraction * progress
}
func isFinished(at timestamp: Double) -> Bool {
if timestamp > self.startTime + self.duration {
return true
} else {
return false
}
}
}
private final class AnimationState { private final class AnimationState {
let duration: Double let duration: Double
let fromIsUnlocked: Bool let fromIsUnlocked: Bool
let fromFraction: CGFloat let fromFraction: CGFloat
let fromTitleWidth: CGFloat
let fromActivityFraction: CGFloat
let startTime: Double let startTime: Double
let bounce: Bool let bounce: Bool
@ -251,12 +291,16 @@ public final class StoryPeerListComponent: Component {
duration: Double, duration: Double,
fromIsUnlocked: Bool, fromIsUnlocked: Bool,
fromFraction: CGFloat, fromFraction: CGFloat,
fromTitleWidth: CGFloat,
fromActivityFraction: CGFloat,
startTime: Double, startTime: Double,
bounce: Bool bounce: Bool
) { ) {
self.duration = duration self.duration = duration
self.fromIsUnlocked = fromIsUnlocked self.fromIsUnlocked = fromIsUnlocked
self.fromFraction = fromFraction self.fromFraction = fromFraction
self.fromTitleWidth = fromTitleWidth
self.fromActivityFraction = fromActivityFraction
self.startTime = startTime self.startTime = startTime
self.bounce = bounce self.bounce = bounce
} }
@ -278,6 +322,14 @@ public final class StoryPeerListComponent: Component {
} }
} }
private struct TitleState: Equatable {
var text: String
init(text: String) {
self.text = text
}
}
public final class View: UIView, UIScrollViewDelegate { public final class View: UIView, UIScrollViewDelegate {
private let collapsedButton: HighlightableButton private let collapsedButton: HighlightableButton
private let scrollView: ScrollView private let scrollView: ScrollView
@ -292,7 +344,13 @@ public final class StoryPeerListComponent: Component {
private var visibleCollapsableItems: [EnginePeer.Id: VisibleItem] = [:] private var visibleCollapsableItems: [EnginePeer.Id: VisibleItem] = [:]
private var titleIndicatorView: ComponentView<Empty>? private var titleIndicatorView: ComponentView<Empty>?
private let titleView = ComponentView<Empty>()
private let titleView: UIImageView
private var titleState: TitleState?
private var titleViewAnimation: TitleAnimationState?
private var disappearingTitleViews: [TitleAnimationState] = []
private var titleIconView: ComponentView<Empty>? private var titleIconView: ComponentView<Empty>?
private var component: StoryPeerListComponent? private var component: StoryPeerListComponent?
@ -308,6 +366,8 @@ public final class StoryPeerListComponent: Component {
private var animator: ConstantDisplayLinkAnimator? private var animator: ConstantDisplayLinkAnimator?
private var currentFraction: CGFloat = 0.0 private var currentFraction: CGFloat = 0.0
private var currentTitleWidth: CGFloat = 0.0
private var currentActivityFraction: CGFloat = 0.0
public override init(frame: CGRect) { public override init(frame: CGRect) {
self.collapsedButton = HighlightableButton() self.collapsedButton = HighlightableButton()
@ -325,6 +385,9 @@ public final class StoryPeerListComponent: Component {
self.scrollContainerView = UIView() self.scrollContainerView = UIView()
self.titleView = UIImageView()
self.titleView.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5)
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delegate = self self.scrollView.delegate = self
@ -333,6 +396,7 @@ public final class StoryPeerListComponent: Component {
self.addSubview(self.scrollView) self.addSubview(self.scrollView)
self.addSubview(self.scrollContainerView) self.addSubview(self.scrollContainerView)
self.addSubview(self.collapsedButton) self.addSubview(self.collapsedButton)
self.addSubview(self.titleView)
self.collapsedButton.highligthedChanged = { [weak self] highlighted in self.collapsedButton.highligthedChanged = { [weak self] highlighted in
guard let self else { guard let self else {
@ -438,41 +502,10 @@ public final class StoryPeerListComponent: Component {
let titleIconSpacing: CGFloat = 4.0 let titleIconSpacing: CGFloat = 4.0
let titleIndicatorSpacing: CGFloat = 8.0 let titleIndicatorSpacing: CGFloat = 8.0
var titleContentWidth: CGFloat = 0.0 var realTitleContentWidth: CGFloat = 0.0
var titleIndicatorSize: CGSize? let titleSize = self.titleView.image?.size ?? CGSize()
if component.titleHasActivity { realTitleContentWidth += titleSize.width
let titleIndicatorView: ComponentView<Empty>
if let current = self.titleIndicatorView {
titleIndicatorView = current
} else {
titleIndicatorView = ComponentView()
self.titleIndicatorView = titleIndicatorView
}
let titleIndicatorSizeValue = titleIndicatorView.update(
transition: .immediate,
component: AnyComponent(TitleActivityIndicatorComponent(
color: component.theme.rootController.navigationBar.accentTextColor
)),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleIndicatorSize = titleIndicatorSizeValue
titleContentWidth += titleIndicatorSizeValue.width + titleIndicatorSpacing
} else {
if let titleIndicatorView = self.titleIndicatorView {
self.titleIndicatorView = nil
titleIndicatorView.view?.removeFromSuperview()
}
}
let titleSize = self.titleView.update(
transition: .immediate,
component: AnyComponent(Text(text: component.title, font: Font.semibold(17.0), color: component.theme.rootController.navigationBar.primaryTextColor)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
titleContentWidth += titleSize.width
var titleIconSize: CGSize? var titleIconSize: CGSize?
if let peerStatus = component.titlePeerStatus { if let peerStatus = component.titlePeerStatus {
@ -523,12 +556,8 @@ public final class StoryPeerListComponent: Component {
titleIconSize = titleIconSizeValue titleIconSize = titleIconSizeValue
if let titleIconComponentView = titleIconView.view {
titleIconComponentView.isHidden = component.titleHasActivity
}
if !component.titleHasActivity { if !component.titleHasActivity {
titleContentWidth += titleIconSpacing + titleIconSizeValue.width realTitleContentWidth += titleIconSpacing + titleIconSizeValue.width
} }
} else { } else {
if let titleIconView = self.titleIconView { if let titleIconView = self.titleIconView {
@ -552,50 +581,14 @@ public final class StoryPeerListComponent: Component {
collapseStartIndex = 1 collapseStartIndex = 1
} }
let collapsedItemWidth: CGFloat = 24.0
let collapsedItemDistance: CGFloat = 14.0
let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItems.count - collapseStartIndex, 3))
var collapsedContentWidth: CGFloat = 0.0
if collapsedItemCount > 0 {
collapsedContentWidth = 1.0 * collapsedItemWidth + (collapsedItemDistance) * max(0.0, collapsedItemCount - 1.0)
}
let collapseEndIndex = collapseStartIndex + max(0, Int(collapsedItemCount) - 1)
var collapsedContentOrigin: CGFloat
let collapsedItemOffsetY: CGFloat
let titleContentSpacing: CGFloat = 8.0
var combinedTitleContentWidth = titleContentWidth
if !combinedTitleContentWidth.isZero {
combinedTitleContentWidth += titleContentSpacing
}
let centralContentWidth: CGFloat
centralContentWidth = collapsedContentWidth + combinedTitleContentWidth
collapsedContentOrigin = floor((itemLayout.containerSize.width - centralContentWidth) * 0.5)
if component.titleHasActivity {
collapsedContentOrigin -= (collapsedContentWidth + titleContentSpacing) * 0.5
}
collapsedContentOrigin = min(collapsedContentOrigin, component.maxTitleX - centralContentWidth - 4.0)
var collapsedContentOriginOffset: CGFloat = 0.0
if itemLayout.itemCount == 1 && collapsedContentWidth <= 0.1 {
collapsedContentOriginOffset += 4.0
}
collapsedContentOrigin -= collapsedContentOriginOffset
collapsedItemOffsetY = -59.0
struct CollapseState { struct CollapseState {
var globalFraction: CGFloat var globalFraction: CGFloat
var scaleFraction: CGFloat var scaleFraction: CGFloat
var minFraction: CGFloat var minFraction: CGFloat
var maxFraction: CGFloat var maxFraction: CGFloat
var sideAlphaFraction: CGFloat var sideAlphaFraction: CGFloat
var titleWidth: CGFloat
var activityFraction: CGFloat
} }
let targetExpandedFraction = component.collapseFraction let targetExpandedFraction = component.collapseFraction
@ -619,6 +612,17 @@ public final class StoryPeerListComponent: Component {
targetSideAlphaFraction = 0.0 targetSideAlphaFraction = 0.0
} }
let collapsedItemWidth: CGFloat = 24.0
let collapsedItemDistance: CGFloat = 14.0
let collapsedItemOffsetY: CGFloat = -60.0
let titleContentSpacing: CGFloat = 8.0
let collapsedItemCount: CGFloat = CGFloat(min(self.sortedItems.count - collapseStartIndex, 3))
let targetActivityFraction: CGFloat = component.titleHasActivity ? 1.0 : 0.0
let timestamp = CACurrentMediaTime()
let collapsedState: CollapseState let collapsedState: CollapseState
let expandBoundsFraction: CGFloat let expandBoundsFraction: CGFloat
if let animationState = self.animationState { if let animationState = self.animationState {
@ -646,20 +650,22 @@ public final class StoryPeerListComponent: Component {
effectiveFromSideAlphaFraction = 0.0 effectiveFromSideAlphaFraction = 0.0
} }
let timestamp = CACurrentMediaTime()
let animatedGlobalFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: animationState.fromFraction, toFraction: targetFraction) let animatedGlobalFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: animationState.fromFraction, toFraction: targetFraction)
let animatedScaleFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromScaleFraction, toFraction: targetScaleFraction) let animatedScaleFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromScaleFraction, toFraction: targetScaleFraction)
let animatedMinFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMinFraction, toFraction: targetMinFraction) let animatedMinFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMinFraction, toFraction: targetMinFraction)
let animatedMaxFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMaxFraction, toFraction: targetMaxFraction) let animatedMaxFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromMaxFraction, toFraction: targetMaxFraction)
let animatedSideAlphaFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromSideAlphaFraction, toFraction: targetSideAlphaFraction) let animatedSideAlphaFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: effectiveFromSideAlphaFraction, toFraction: targetSideAlphaFraction)
let animatedTitleWidth = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: animationState.fromTitleWidth, toFraction: realTitleContentWidth)
let animatedActivityFraction = animationState.interpolatedFraction(at: timestamp, effectiveFromFraction: animationState.fromActivityFraction, toFraction: targetActivityFraction)
collapsedState = CollapseState( collapsedState = CollapseState(
globalFraction: animatedGlobalFraction, globalFraction: animatedGlobalFraction,
scaleFraction: animatedScaleFraction, scaleFraction: animatedScaleFraction,
minFraction: animatedMinFraction, minFraction: animatedMinFraction,
maxFraction: animatedMaxFraction, maxFraction: animatedMaxFraction,
sideAlphaFraction: animatedSideAlphaFraction sideAlphaFraction: animatedSideAlphaFraction,
titleWidth: animatedTitleWidth,
activityFraction: animatedActivityFraction
) )
var rawProgress = CGFloat((timestamp - animationState.startTime) / animationState.duration) var rawProgress = CGFloat((timestamp - animationState.startTime) / animationState.duration)
@ -676,14 +682,36 @@ public final class StoryPeerListComponent: Component {
scaleFraction: targetScaleFraction, scaleFraction: targetScaleFraction,
minFraction: targetMinFraction, minFraction: targetMinFraction,
maxFraction: targetMaxFraction, maxFraction: targetMaxFraction,
sideAlphaFraction: targetSideAlphaFraction sideAlphaFraction: targetSideAlphaFraction,
titleWidth: realTitleContentWidth,
activityFraction: targetActivityFraction
) )
expandBoundsFraction = 0.0 expandBoundsFraction = 0.0
} }
self.currentFraction = collapsedState.globalFraction var targetCollapsedContentWidth: CGFloat = 0.0
if collapsedItemCount > 0 {
targetCollapsedContentWidth = 1.0 * collapsedItemWidth + (collapsedItemDistance) * max(0.0, collapsedItemCount - 1.0)
}
let activityCollapsedContentWidth: CGFloat = 16.0 + titleIndicatorSpacing
let collapsedContentWidth = activityCollapsedContentWidth * collapsedState.activityFraction + targetCollapsedContentWidth * (1.0 - collapsedState.activityFraction)
component.externalState.collapsedWidth = collapsedContentWidth let collapseEndIndex = collapseStartIndex + max(0, Int(collapsedItemCount) - 1)
var collapsedContentOrigin: CGFloat
let centralContentWidth: CGFloat = collapsedContentWidth + titleContentSpacing + collapsedState.titleWidth
collapsedContentOrigin = floor((itemLayout.containerSize.width - centralContentWidth) * 0.5)
collapsedContentOrigin = min(collapsedContentOrigin, component.maxTitleX - centralContentWidth - 4.0)
let collapsedContentOriginOffset: CGFloat = 0.0
collapsedContentOrigin -= collapsedContentOriginOffset
self.currentFraction = collapsedState.globalFraction
self.currentTitleWidth = collapsedState.titleWidth
self.currentActivityFraction = collapsedState.activityFraction
let effectiveVisibleBounds = self.scrollView.bounds let effectiveVisibleBounds = self.scrollView.bounds
let visibleBounds = effectiveVisibleBounds.insetBy(dx: -200.0, dy: 0.0) let visibleBounds = effectiveVisibleBounds.insetBy(dx: -200.0, dy: 0.0)
@ -698,6 +726,8 @@ public final class StoryPeerListComponent: Component {
} }
} }
let expandedItemWidth: CGFloat = 60.0
struct MeasuredItem { struct MeasuredItem {
var itemFrame: CGRect var itemFrame: CGRect
var itemScale: CGFloat var itemScale: CGFloat
@ -712,10 +742,8 @@ public final class StoryPeerListComponent: Component {
let collapsedItemX: CGFloat let collapsedItemX: CGFloat
if collapseIndex < collapseStartIndex { if collapseIndex < collapseStartIndex {
collapsedItemX = collapsedContentOrigin collapsedItemX = collapsedContentOrigin
} else if collapseIndex > collapseEndIndex {
collapsedItemX = collapsedContentOrigin + CGFloat(collapseEndIndex) * collapsedItemDistance - collapsedItemWidth * 0.5
} else { } else {
collapsedItemX = collapsedContentOrigin + CGFloat(collapseIndex - collapseStartIndex) * collapsedItemDistance collapsedItemX = collapsedContentOrigin + CGFloat(min(collapseIndex - collapseStartIndex, collapseEndIndex - collapseStartIndex)) * collapsedItemDistance * (1.0 - collapsedState.activityFraction) * (1.0 - collapsedState.maxFraction)
} }
let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedItemX, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height)) let collapsedItemFrame = CGRect(origin: CGPoint(x: collapsedItemX, y: regularItemFrame.minY + collapsedItemOffsetY), size: CGSize(width: collapsedItemWidth, height: regularItemFrame.height))
@ -730,12 +758,15 @@ public final class StoryPeerListComponent: Component {
collapsedMaxItemFrame.origin.y += collapsedState.minFraction * 10.0 collapsedMaxItemFrame.origin.y += collapsedState.minFraction * 10.0
} }
let minimizedItemScale: CGFloat = 24.0 / 52.0 let minimizedDefaultItemScale: CGFloat = 24.0 / 52.0
let minimizedItemScale = minimizedDefaultItemScale
let minimizedMaxItemScale: CGFloat = (24.0 + 4.0) / 52.0 let minimizedMaxItemScale: CGFloat = (24.0 + 4.0) / 52.0
let maximizedItemScale: CGFloat = 1.0 let maximizedItemScale: CGFloat = 1.0
let minItemScale = minimizedItemScale.interpolate(to: minimizedMaxItemScale, amount: collapsedState.minFraction) let minItemScale: CGFloat = minimizedItemScale.interpolate(to: minimizedMaxItemScale, amount: collapsedState.minFraction) * (1.0 - collapsedState.activityFraction) + 0.1 * collapsedState.activityFraction
let itemScale: CGFloat = minItemScale.interpolate(to: maximizedItemScale, amount: collapsedState.maxFraction) let itemScale: CGFloat = minItemScale.interpolate(to: maximizedItemScale, amount: collapsedState.maxFraction)
let itemFrame: CGRect let itemFrame: CGRect
@ -787,7 +818,8 @@ public final class StoryPeerListComponent: Component {
continue continue
} }
let isReallyVisible = effectiveVisibleBounds.intersects(regularItemFrame) //let isReallyVisible = effectiveVisibleBounds.intersects(regularItemFrame)
//let _ = isReallyVisible
validIds.append(itemSet.peer.id) validIds.append(itemSet.peer.id)
@ -845,11 +877,7 @@ public final class StoryPeerListComponent: Component {
rightItemFrame = calculateItem(i + 1).itemFrame rightItemFrame = calculateItem(i + 1).itemFrame
} }
if effectiveFirstVisibleIndex == 0 && !component.titleHasActivity { itemAlpha = collapsedState.sideAlphaFraction * 1.0 + (1.0 - collapsedState.sideAlphaFraction) * (1.0 - collapsedState.activityFraction)
itemAlpha = 1.0
} else {
itemAlpha = collapsedState.sideAlphaFraction
}
} else { } else {
if itemLayout.itemCount == 1 { if itemLayout.itemCount == 1 {
itemAlpha = min(1.0, (collapsedState.minFraction + collapsedState.maxFraction) * 4.0) itemAlpha = min(1.0, (collapsedState.minFraction + collapsedState.maxFraction) * 4.0)
@ -879,9 +907,8 @@ public final class StoryPeerListComponent: Component {
hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems,
hasItems: hasItems, hasItems: hasItems,
ringAnimation: itemRingAnimation, ringAnimation: itemRingAnimation,
collapseFraction: isReallyVisible ? (1.0 - collapsedState.maxFraction) : 0.0,
scale: itemScale, scale: itemScale,
collapsedWidth: collapsedItemWidth, fullWidth: expandedItemWidth,
expandedAlphaFraction: collapsedState.sideAlphaFraction, expandedAlphaFraction: collapsedState.sideAlphaFraction,
leftNeighborDistance: leftNeighborDistance, leftNeighborDistance: leftNeighborDistance,
rightNeighborDistance: rightNeighborDistance, rightNeighborDistance: rightNeighborDistance,
@ -967,11 +994,7 @@ public final class StoryPeerListComponent: Component {
var itemAlpha: CGFloat = 1.0 var itemAlpha: CGFloat = 1.0
var isCollapsable: Bool = false var isCollapsable: Bool = false
var itemScale = measuredItem.itemScale let itemScale = measuredItem.itemScale
if itemLayout.itemCount == 1 {
let singleScaleFactor = min(1.0, collapsedState.minFraction + collapsedState.maxFraction)
itemScale = 0.001 * (1.0 - singleScaleFactor) + itemScale * singleScaleFactor
}
if i >= collapseStartIndex && i <= collapseEndIndex { if i >= collapseStartIndex && i <= collapseEndIndex {
isCollapsable = true isCollapsable = true
@ -983,21 +1006,9 @@ public final class StoryPeerListComponent: Component {
rightItemFrame = calculateItem(collapseIndex + 1).itemFrame rightItemFrame = calculateItem(collapseIndex + 1).itemFrame
} }
if effectiveFirstVisibleIndex == 0 { itemAlpha = (1.0 - collapsedState.sideAlphaFraction) * (1.0 - collapsedState.activityFraction)
itemAlpha = 0.0
} else {
itemAlpha = 1.0 - collapsedState.sideAlphaFraction
}
} else { } else {
if itemLayout.itemCount == 1 { itemAlpha = collapsedState.sideAlphaFraction
itemAlpha = min(1.0, (collapsedState.minFraction + collapsedState.maxFraction) * 4.0)
} else {
itemAlpha = collapsedState.sideAlphaFraction
}
}
if component.titleHasActivity {
itemAlpha = 0.0
} }
var leftNeighborDistance: CGPoint? var leftNeighborDistance: CGPoint?
@ -1021,9 +1032,8 @@ public final class StoryPeerListComponent: Component {
hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems, hasUnseenCloseFriendsItems: hasUnseenCloseFriendsItems,
hasItems: hasItems, hasItems: hasItems,
ringAnimation: itemRingAnimation, ringAnimation: itemRingAnimation,
collapseFraction: 1.0 - collapsedState.maxFraction,
scale: itemScale, scale: itemScale,
collapsedWidth: collapsedItemWidth, fullWidth: expandedItemWidth,
expandedAlphaFraction: collapsedState.sideAlphaFraction, expandedAlphaFraction: collapsedState.sideAlphaFraction,
leftNeighborDistance: leftNeighborDistance, leftNeighborDistance: leftNeighborDistance,
rightNeighborDistance: rightNeighborDistance, rightNeighborDistance: rightNeighborDistance,
@ -1093,46 +1103,110 @@ public final class StoryPeerListComponent: Component {
let defaultCollapsedTitleOffset: CGFloat = 0.0 let defaultCollapsedTitleOffset: CGFloat = 0.0
var targetCollapsedTitleOffset: CGFloat = collapsedContentOrigin + collapsedContentOriginOffset + collapsedContentWidth + titleContentSpacing let targetCollapsedTitleOffset: CGFloat = collapsedContentOrigin + collapsedContentOriginOffset + collapsedContentWidth + titleContentSpacing
if itemLayout.itemCount == 1 && collapsedContentWidth <= 0.1 {
let singleScaleFactor = min(1.0, collapsedState.minFraction)
targetCollapsedTitleOffset += singleScaleFactor * 4.0
}
let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset let collapsedTitleOffset = targetCollapsedTitleOffset - defaultCollapsedTitleOffset
let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction) let titleMinContentOffset: CGFloat = collapsedTitleOffset.interpolate(to: collapsedTitleOffset + 12.0, amount: collapsedState.minFraction * (1.0 - collapsedState.activityFraction))
var titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: floor((itemLayout.containerSize.width - titleContentWidth) * 0.5) as CGFloat, amount: collapsedState.maxFraction) var titleContentOffset: CGFloat = titleMinContentOffset.interpolate(to: floor((itemLayout.containerSize.width - collapsedState.titleWidth) * 0.5) as CGFloat, amount: collapsedState.maxFraction * (1.0 - collapsedState.activityFraction))
var titleIndicatorSize: CGSize?
if collapsedState.activityFraction != 0.0 {
let collapsedItemMinX = collapsedContentOrigin - collapsedItemWidth * 0.5
let collapsedItemMaxX = collapsedContentOrigin + CGFloat(collapseEndIndex - collapseStartIndex) * collapsedItemDistance * (1.0 - collapsedState.activityFraction) * (1.0 - collapsedState.sideAlphaFraction) + collapsedItemWidth * 0.5
let collapsedContentWidth = max(collapsedItemWidth, collapsedItemMaxX - collapsedItemMinX)
let titleIndicatorView: ComponentView<Empty>
if let current = self.titleIndicatorView {
titleIndicatorView = current
} else {
titleIndicatorView = ComponentView()
self.titleIndicatorView = titleIndicatorView
}
let titleIndicatorSizeValue = titleIndicatorView.update(
transition: .immediate,
component: AnyComponent(TitleActivityIndicatorComponent(
color: component.theme.rootController.navigationBar.accentTextColor
)),
environment: {},
containerSize: CGSize(width: collapsedContentWidth - 2.0, height: collapsedItemWidth - 2.0)
)
titleIndicatorSize = titleIndicatorSizeValue
} else if let titleIndicatorView = self.titleIndicatorView {
self.titleIndicatorView = nil
titleIndicatorView.view?.removeFromSuperview()
}
if let titleIndicatorSize, let titleIndicatorView = self.titleIndicatorView?.view { if let titleIndicatorSize, let titleIndicatorView = self.titleIndicatorView?.view {
let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize) let titleIndicatorFrame = CGRect(origin: CGPoint(x: titleContentOffset - titleIndicatorSize.width - 9.0, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIndicatorSize.height) * 0.5)), size: titleIndicatorSize)
if titleIndicatorView.superview == nil { if titleIndicatorView.superview == nil {
self.addSubview(titleIndicatorView) self.addSubview(titleIndicatorView)
} }
titleIndicatorView.frame = titleIndicatorFrame titleIndicatorView.center = titleIndicatorFrame.center
titleContentOffset += titleIndicatorSize.width + titleIndicatorSpacing titleIndicatorView.bounds = CGRect(origin: CGPoint(), size: titleIndicatorFrame.size)
var indicatorMinScale: CGFloat = collapsedState.sideAlphaFraction * 0.1 + (1.0 - collapsedState.sideAlphaFraction) * 1.0
if collapsedItemCount == 0 {
indicatorMinScale = 0.1
}
let indicatorScale: CGFloat = collapsedState.activityFraction * 1.0 + (1.0 - collapsedState.activityFraction) * indicatorMinScale
let indicatorAlpha: CGFloat = collapsedState.activityFraction
titleIndicatorView.layer.transform = CATransform3DMakeScale(indicatorScale, indicatorScale, 1.0)
titleIndicatorView.alpha = indicatorAlpha
} }
let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize) let titleFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleSize.height) * 0.5)), size: titleSize)
if let titleComponentView = self.titleView.view { if let image = self.titleView.image {
if titleComponentView.superview == nil { self.titleView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY)
titleComponentView.isUserInteractionEnabled = false self.titleView.bounds = CGRect(origin: CGPoint(), size: image.size)
self.addSubview(titleComponentView)
let titleFraction: CGFloat
if let titleViewAnimation = self.titleViewAnimation {
titleFraction = titleViewAnimation.interpolatedFraction(at: timestamp, effectiveFromFraction: titleViewAnimation.fromFraction, toFraction: titleViewAnimation.toFraction)
} else {
titleFraction = 1.0
}
self.titleView.alpha = titleFraction
let titleScale: CGFloat = titleFraction * 1.0 + (1.0 - titleFraction) * 0.3
self.titleView.layer.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
}
for disappearingTitleView in self.disappearingTitleViews {
if let image = disappearingTitleView.imageView.image {
disappearingTitleView.imageView.center = CGPoint(x: titleFrame.minX, y: titleFrame.midY)
disappearingTitleView.imageView.bounds = CGRect(origin: CGPoint(), size: image.size)
let titleFraction = disappearingTitleView.interpolatedFraction(at: timestamp, effectiveFromFraction: disappearingTitleView.fromFraction, toFraction: disappearingTitleView.toFraction)
disappearingTitleView.imageView.alpha = titleFraction
let titleScale: CGFloat = titleFraction * 1.0 + (1.0 - titleFraction) * 0.3
disappearingTitleView.imageView.layer.transform = CATransform3DMakeScale(titleScale, titleScale, 1.0)
} }
titleComponentView.frame = titleFrame
} }
titleContentOffset += titleSize.width
if let titleIconSize, let titleIconView = self.titleIconView?.view { if let titleIconSize, let titleIconView = self.titleIconView?.view {
titleContentOffset += titleIconSpacing titleContentOffset += titleIconSpacing
let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset, y: collapsedItemOffsetY + 1.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize) let titleIconFrame = CGRect(origin: CGPoint(x: titleContentOffset + titleIconSpacing + (collapsedState.titleWidth - (titleIconSpacing + titleIconSize.width)) * (1.0 - collapsedState.activityFraction), y: collapsedItemOffsetY + 2.0 + floor((56.0 - titleIconSize.height) * 0.5)), size: titleIconSize)
if titleIconView.superview == nil { if titleIconView.superview == nil {
self.addSubview(titleIconView) self.addSubview(titleIconView)
} }
titleIconView.frame = titleIconFrame titleIconView.center = titleIconFrame.center
titleIconView.bounds = CGRect(origin: CGPoint(), size: titleIconFrame.size)
let titleIconFraction = 1.0 - collapsedState.activityFraction
let titleIconAlpha: CGFloat = titleIconFraction
let titleIconScale: CGFloat = titleIconFraction * 1.0 + (1.0 - titleIconFraction) * 0.1
titleIconView.alpha = titleIconAlpha
titleIconView.layer.transform = CATransform3DMakeScale(titleIconScale, titleIconScale, 1.0)
} }
titleContentOffset += collapsedState.titleWidth
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -1179,7 +1253,10 @@ public final class StoryPeerListComponent: Component {
useAnimation = true useAnimation = true
} else if let animationHint, animationHint.allowAvatarsExpansionUpdated { } else if let animationHint, animationHint.allowAvatarsExpansionUpdated {
useAnimation = true useAnimation = true
} else if let previousComponent = self.component, (component.title != previousComponent.title || component.titleHasActivity != previousComponent.titleHasActivity) {
useAnimation = true
} }
if let animationHint, animationHint.disableAnimations { if let animationHint, animationHint.disableAnimations {
useAnimation = false useAnimation = false
self.animationState = nil self.animationState = nil
@ -1195,7 +1272,48 @@ public final class StoryPeerListComponent: Component {
} else { } else {
duration = 0.25 duration = 0.25
} }
self.animationState = AnimationState(duration: duration * UIView.animationDurationFactor(), fromIsUnlocked: previousComponent.unlocked, fromFraction: self.currentFraction, startTime: timestamp, bounce: animationHint?.bounce ?? true)
if useAnimation, let previousComponent = self.component, component.title != previousComponent.title, self.titleView.image != nil {
var fromFraction: CGFloat = 1.0
if let titleViewAnimation = self.titleViewAnimation {
fromFraction = titleViewAnimation.interpolatedFraction(
at: timestamp,
effectiveFromFraction: titleViewAnimation.fromFraction,
toFraction: titleViewAnimation.toFraction
)
}
if let previousImage = self.titleView.image {
let previousImageView = UIImageView(image: previousImage)
previousImageView.layer.anchorPoint = self.titleView.layer.anchorPoint
self.disappearingTitleViews.append(TitleAnimationState(
duration: duration,
startTime: timestamp,
fromFraction: fromFraction,
toFraction: 0.0,
imageView: previousImageView
))
self.insertSubview(previousImageView, belowSubview: self.titleView)
}
self.titleViewAnimation = TitleAnimationState(
duration: duration * UIView.animationDurationFactor(),
startTime: timestamp,
fromFraction: 0.0,
toFraction: 1.0,
imageView: self.titleView
)
}
self.animationState = AnimationState(
duration: duration * UIView.animationDurationFactor(),
fromIsUnlocked: previousComponent.unlocked,
fromFraction: self.currentFraction,
fromTitleWidth: self.currentTitleWidth,
fromActivityFraction: self.currentActivityFraction,
startTime: timestamp,
bounce: animationHint?.bounce ?? true
)
} }
if let animationState = self.animationState { if let animationState = self.animationState {
@ -1204,7 +1322,20 @@ public final class StoryPeerListComponent: Component {
} }
} }
if let _ = self.animationState { if let titleViewAnimation = self.titleViewAnimation {
if titleViewAnimation.isFinished(at: timestamp) {
self.titleViewAnimation = nil
}
}
for i in (0 ..< self.disappearingTitleViews.count).reversed() {
if self.disappearingTitleViews[i].isFinished(at: timestamp) {
self.disappearingTitleViews[i].imageView.removeFromSuperview()
self.disappearingTitleViews.remove(at: i)
}
}
if self.animationState != nil || self.titleViewAnimation != nil || !self.disappearingTitleViews.isEmpty {
if self.animator == nil { if self.animator == nil {
let animator = ConstantDisplayLinkAnimator(update: { [weak self] in let animator = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let self else { guard let self else {
@ -1223,6 +1354,27 @@ public final class StoryPeerListComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
let updatedTitleState = TitleState(text: component.title)
if self.titleState != updatedTitleState {
self.titleState = updatedTitleState
let attributedText = NSAttributedString(string: updatedTitleState.text, attributes: [
NSAttributedString.Key.font: Font.semibold(17.0),
NSAttributedString.Key.foregroundColor: component.theme.rootController.navigationBar.primaryTextColor
])
var boundingRect = attributedText.boundingRect(with: CGSize(width: max(0.0, component.maxTitleX - component.minTitleX - 30.0), height: 100.0), options: .usesLineFragmentOrigin, context: nil)
boundingRect.size.width = ceil(boundingRect.size.width)
boundingRect.size.height = ceil(boundingRect.size.height)
let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: boundingRect.size))
let image = renderer.image { context in
UIGraphicsPushContext(context.cgContext)
attributedText.draw(at: CGPoint())
UIGraphicsPopContext()
}
self.titleView.image = image
}
if let storySubscriptions = component.storySubscriptions, let hasMoreToken = storySubscriptions.hasMoreToken { if let storySubscriptions = component.storySubscriptions, let hasMoreToken = storySubscriptions.hasMoreToken {
if self.requestedLoadMoreToken != hasMoreToken { if self.requestedLoadMoreToken != hasMoreToken {
self.requestedLoadMoreToken = hasMoreToken self.requestedLoadMoreToken = hasMoreToken

View File

@ -269,9 +269,8 @@ public final class StoryPeerListItemComponent: Component {
public let hasUnseenCloseFriendsItems: Bool public let hasUnseenCloseFriendsItems: Bool
public let hasItems: Bool public let hasItems: Bool
public let ringAnimation: RingAnimation? public let ringAnimation: RingAnimation?
public let collapseFraction: CGFloat
public let scale: CGFloat public let scale: CGFloat
public let collapsedWidth: CGFloat public let fullWidth: CGFloat
public let expandedAlphaFraction: CGFloat public let expandedAlphaFraction: CGFloat
public let leftNeighborDistance: CGPoint? public let leftNeighborDistance: CGPoint?
public let rightNeighborDistance: CGPoint? public let rightNeighborDistance: CGPoint?
@ -287,9 +286,8 @@ public final class StoryPeerListItemComponent: Component {
hasUnseenCloseFriendsItems: Bool, hasUnseenCloseFriendsItems: Bool,
hasItems: Bool, hasItems: Bool,
ringAnimation: RingAnimation?, ringAnimation: RingAnimation?,
collapseFraction: CGFloat,
scale: CGFloat, scale: CGFloat,
collapsedWidth: CGFloat, fullWidth: CGFloat,
expandedAlphaFraction: CGFloat, expandedAlphaFraction: CGFloat,
leftNeighborDistance: CGPoint?, leftNeighborDistance: CGPoint?,
rightNeighborDistance: CGPoint?, rightNeighborDistance: CGPoint?,
@ -304,9 +302,8 @@ public final class StoryPeerListItemComponent: Component {
self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems self.hasUnseenCloseFriendsItems = hasUnseenCloseFriendsItems
self.hasItems = hasItems self.hasItems = hasItems
self.ringAnimation = ringAnimation self.ringAnimation = ringAnimation
self.collapseFraction = collapseFraction
self.scale = scale self.scale = scale
self.collapsedWidth = collapsedWidth self.fullWidth = fullWidth
self.expandedAlphaFraction = expandedAlphaFraction self.expandedAlphaFraction = expandedAlphaFraction
self.leftNeighborDistance = leftNeighborDistance self.leftNeighborDistance = leftNeighborDistance
self.rightNeighborDistance = rightNeighborDistance self.rightNeighborDistance = rightNeighborDistance
@ -339,13 +336,10 @@ public final class StoryPeerListItemComponent: Component {
if lhs.ringAnimation != rhs.ringAnimation { if lhs.ringAnimation != rhs.ringAnimation {
return false return false
} }
if lhs.collapseFraction != rhs.collapseFraction {
return false
}
if lhs.scale != rhs.scale { if lhs.scale != rhs.scale {
return false return false
} }
if lhs.collapsedWidth != rhs.collapsedWidth { if lhs.fullWidth != rhs.fullWidth {
return false return false
} }
if lhs.expandedAlphaFraction != rhs.expandedAlphaFraction { if lhs.expandedAlphaFraction != rhs.expandedAlphaFraction {
@ -526,7 +520,7 @@ public final class StoryPeerListItemComponent: Component {
self.component = component self.component = component
self.componentState = state self.componentState = state
let effectiveWidth: CGFloat = (1.0 - component.collapseFraction) * availableSize.width + component.collapseFraction * component.collapsedWidth let effectiveWidth: CGFloat = component.scale * component.fullWidth
let effectiveScale: CGFloat = component.scale let effectiveScale: CGFloat = component.scale
@ -571,7 +565,7 @@ public final class StoryPeerListItemComponent: Component {
} else { } else {
baseLineWidth = 1.0 + UIScreenPixel baseLineWidth = 1.0 + UIScreenPixel
} }
let indicatorLineWidth: CGFloat = baseLineWidth * (1.0 - component.collapseFraction) + minimizedLineWidth * component.collapseFraction let indicatorLineWidth: CGFloat = baseLineWidth * component.scale + minimizedLineWidth * (1.0 - component.scale)
avatarNode.setPeer( avatarNode.setPeer(
context: component.context, context: component.context,
@ -643,7 +637,7 @@ public final class StoryPeerListItemComponent: Component {
let baseRadius: CGFloat = 30.0 let baseRadius: CGFloat = 30.0
let collapsedRadius: CGFloat = 32.0 let collapsedRadius: CGFloat = 32.0
let indicatorRadius: CGFloat = baseRadius * (1.0 - component.collapseFraction) + collapsedRadius * component.collapseFraction let indicatorRadius: CGFloat = baseRadius * component.scale + collapsedRadius * (1.0 - component.scale)
self.indicatorShapeLayer.lineWidth = indicatorLineWidth self.indicatorShapeLayer.lineWidth = indicatorLineWidth

View File

@ -2,7 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import ActivityIndicator import HierarchyTrackingLayer
public final class TitleActivityIndicatorComponent: Component { public final class TitleActivityIndicatorComponent: Component {
let color: UIColor let color: UIColor
@ -21,10 +21,38 @@ public final class TitleActivityIndicatorComponent: Component {
} }
public final class View: UIView { public final class View: UIView {
private var activityIndicator: ActivityIndicator? private let shapeLayer: SimpleShapeLayer
private let hierarchyTrackingLayer: HierarchyTrackingLayer
private var component: TitleActivityIndicatorComponent?
private var animator: ConstantDisplayLinkAnimator?
private var animationPhase: CGFloat = 0.0
public override init(frame: CGRect) { public override init(frame: CGRect) {
self.shapeLayer = SimpleShapeLayer()
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
super.init(frame: frame) super.init(frame: frame)
self.layer.addSublayer(self.hierarchyTrackingLayer)
self.hierarchyTrackingLayer.didExitHierarchy = { [weak self] in
guard let self else {
return
}
self.refreshAnimation()
}
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let self else {
return
}
self.refreshAnimation()
}
self.layer.addSublayer(self.shapeLayer)
self.shapeLayer.lineCap = .round
self.shapeLayer.lineWidth = 1.5
self.shapeLayer.fillColor = nil
} }
required public init?(coder: NSCoder) { required public init?(coder: NSCoder) {
@ -34,18 +62,154 @@ public final class TitleActivityIndicatorComponent: Component {
deinit { deinit {
} }
func update(component: TitleActivityIndicatorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize { private func refreshAnimation() {
let activityIndicator: ActivityIndicator if self.hierarchyTrackingLayer.isInHierarchy {
if let current = self.activityIndicator { if self.animator == nil {
activityIndicator = current let animationStartTime = CACurrentMediaTime()
self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let self else {
return
}
let duration: Double = 0.5
self.animationPhase = (CACurrentMediaTime() - animationStartTime).truncatingRemainder(dividingBy: duration) / duration
self.updateAnimation()
})
self.animator?.isPaused = false
}
} else { } else {
activityIndicator = ActivityIndicator(type: .custom(component.color, availableSize.width, 2.0, true)) if let animator = self.animator {
self.activityIndicator = activityIndicator self.animator = nil
self.addSubview(activityIndicator.view) animator.invalidate()
}
}
}
private func updateAnimation() {
let size = self.shapeLayer.bounds
let path = CGMutablePath()
let radius = size.height * 0.5
enum Segment {
case line(start: CGPoint, end: CGPoint)
case halfCircle(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
func length(radius: CGFloat) -> CGFloat {
switch self {
case let .line(start, end):
return abs(start.x - end.x)
case let .halfCircle(_, radius, startAngle, endAngle):
return (endAngle - startAngle) * radius
}
}
func addPath(into path: CGMutablePath, fromFraction: CGFloat, toFraction: CGFloat) {
switch self {
case let .line(start, end):
if fromFraction != 0.0 {
path.move(to: CGPoint(
x: start.x * (1.0 - fromFraction) + end.x * fromFraction,
y: start.y * (1.0 - fromFraction) + end.y * fromFraction
))
}
path.addLine(to: CGPoint(
x: start.x * (1.0 - toFraction) + end.x * toFraction,
y: start.y * (1.0 - toFraction) + end.y * toFraction
))
case let .halfCircle(center, radius, startAngle, endAngle):
path.addArc(center: center, radius: radius, startAngle: startAngle + fromFraction * (endAngle - startAngle), endAngle: startAngle + toFraction * (endAngle - startAngle), clockwise: false)
}
}
} }
activityIndicator.frame = CGRect(origin: CGPoint(), size: availableSize) let segments: [Segment] = [
activityIndicator.isHidden = false .halfCircle(center: CGPoint(x: size.width - radius, y: radius), radius: radius, startAngle: -CGFloat.pi * 0.5, endAngle: CGFloat.pi * 0.5),
.line(start: CGPoint(x: size.width - radius, y: size.height), end: CGPoint(x: radius, y: size.height)),
.halfCircle(center: CGPoint(x: radius, y: radius), radius: radius, startAngle: CGFloat.pi * 0.5, endAngle: CGFloat.pi * 1.5),
.line(start: CGPoint(x: radius, y: 0.0), end: CGPoint(x: size.width - radius, y: 0.0)),
]
var totalLength: CGFloat = 0.0
for segment in segments {
totalLength += segment.length(radius: radius)
}
let startOffset: CGFloat = self.animationPhase
let endOffset: CGFloat = startOffset + 0.8
var startLength = startOffset * totalLength
var startSegment: (Int, CGFloat)?
while startSegment == nil {
for i in 0 ..< segments.count {
let segment = segments[i]
let segmentLength = segment.length(radius: radius)
if segmentLength <= startLength {
startLength -= segmentLength
} else {
let subOffset = startLength
startSegment = (i, subOffset)
break
}
}
}
var isFirst = true
var pathLength = (endOffset - startOffset) * totalLength
if let (startIndex, startOffset) = startSegment {
var index = startIndex
while pathLength > 0.0 {
let segment = segments[index]
let segmentOffset: CGFloat = isFirst ? startOffset : 0.0
let segmentLength = segment.length(radius: radius)
let segmentSubLength = segmentLength - segmentOffset
if segmentSubLength > 0.0 {
//remainingLength <= segmentRemainingLength -> take remainingLength
//remainingLength > segmentRemainingLength -> take segmentRemainingLength
let pathPart: CGFloat
if pathLength <= segmentSubLength {
pathPart = pathLength
} else {
pathPart = segmentSubLength
}
pathLength -= pathPart
segment.addPath(into: path, fromFraction: segmentOffset / segmentLength, toFraction: (segmentOffset + pathPart) / segmentLength)
}
index = (index + 1) % segments.count
isFirst = false
}
}
/*for segment in segments {
segment.addPath(into: path, fromFraction: 0.0, toFraction: 1.0)
}*/
if let currentPath = self.shapeLayer.path {
if currentPath != path {
self.shapeLayer.path = path
}
} else {
self.shapeLayer.path = path
}
}
func update(component: TitleActivityIndicatorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let isFirstTime = self.component == nil
self.component = component
let _ = isFirstTime
transition.setFrame(layer: self.shapeLayer, frame: CGRect(origin: CGPoint(), size: availableSize))
transition.setShapeLayerPath(layer: self.shapeLayer, path: UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: availableSize), cornerRadius: availableSize.height * 0.5).cgPath)
self.shapeLayer.strokeColor = component.color.cgColor
self.refreshAnimation()
self.updateAnimation()
return availableSize return availableSize
} }

View File

@ -4712,17 +4712,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue
} }
let updatePeerStoryNotifications: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = { peerId, storyNotifications in let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal<Void, NoError> = {
var isMuted: Bool? peerId, mute in
switch storyNotifications { return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue
case .default: }
isMuted = nil
case .show: let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal<Void, NoError> = {
isMuted = false peerId, hideSender in
case .hide: return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue
isMuted = true }
}
return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, isMuted: isMuted) |> deliverOnMainQueue let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
} }
let mode: NotificationExceptionMode let mode: NotificationExceptionMode
@ -4775,11 +4776,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|> deliverOnMainQueue).start(next: { _ in |> deliverOnMainQueue).start(next: { _ in
}) })
}, updatePeerStoryNotifications: { peerId, storyNotifications in }, updatePeerStoriesMuted: { peerId, mute in
let _ = (updatePeerStoryNotifications(peerId, storyNotifications) let _ = (updatePeerStoriesMuted(peerId, mute)
|> deliverOnMainQueue).start(next: { _ in |> deliverOnMainQueue).start()
}, updatePeerStoriesHideSender: { peerId, hideSender in
}) let _ = (updatePeerStoriesHideSender(peerId, hideSender)
|> deliverOnMainQueue).start()
}, updatePeerStorySound: { peerId, sound in
let _ = (updatePeerStorySound(peer.id, sound)
|> deliverOnMainQueue).start()
}, removePeerFromExceptions: { }, removePeerFromExceptions: {
}, modifiedPeer: { }, modifiedPeer: {
}) })