Various settings UI improvements

This commit is contained in:
Ilya Laktyushin 2021-10-26 17:30:12 +04:00
parent aadf9ceedf
commit 4fef982671
11 changed files with 1256 additions and 510 deletions

View File

@ -6873,7 +6873,7 @@ Sorry for the inconvenience.";
"InviteLink.Create.RequestApprovalOnInfoChannel" = "New users will be able to join the channel only after having been approved by the admins.";
"InviteLink.Create.LinkName" = "Link Name (Optional)";
"InviteLink.Create.LinkNameInfo" = "You can provide an optional name for this link.";
"InviteLink.Create.LinkNameInfo" = "Only you and other admins will see this name.";
"MemberRequests.Title" = "Member Requests";
"MemberRequests.DescriptionGroup" = "Some [additional links]() are set up to accept requests to join the group.";
@ -6962,3 +6962,19 @@ Sorry for the inconvenience.";
"Time.AtDate" = "last seen %@";
"Stickers.ShowMore" = "Show More";
"Notifications.PrivateChats" = "Private Chats";
"Notifications.GroupChats" = "Group Chats";
"Notifications.Channels" = "Channels";
"Notifications.CategoryExceptions_0" = "%@ exceptions";
"Notifications.CategoryExceptions_1" = "%@ exception";
"Notifications.CategoryExceptions_2" = "%@ exceptions";
"Notifications.CategoryExceptions_3_10" = "%@ exceptions";
"Notifications.CategoryExceptions_many" = "%@ exceptions";
"Notifications.CategoryExceptions_any" = "%@ exceptions";
"Notifications.DeleteAllExceptions" = "Delete All Exceptions";
"Notifications.On" = "On";
"Notifications.Off" = "Off";

View File

@ -328,7 +328,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
strongSelf.insertSubnode(strongSelf.maskNode, aboveSubnode: strongSelf.switchGestureNode)
}
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noCorners

View File

@ -61,18 +61,8 @@ private final class NotificationsAndSoundsArguments {
let authorizeNotifications: () -> Void
let suppressWarning: () -> Void
let updateMessageAlerts: (Bool) -> Void
let updateMessagePreviews: (Bool) -> Void
let updateMessageSound: (PeerMessageSound) -> Void
let updateGroupAlerts: (Bool) -> Void
let updateGroupPreviews: (Bool) -> Void
let updateGroupSound: (PeerMessageSound) -> Void
let updateChannelAlerts: (Bool) -> Void
let updateChannelPreviews: (Bool) -> Void
let updateChannelSound: (PeerMessageSound) -> Void
let openPeerCategory: (NotificationsPeerCategory) -> Void
let updateInAppSounds: (Bool) -> Void
let updateInAppVibration: (Bool) -> Void
let updateInAppPreviews: (Bool) -> Void
@ -84,29 +74,19 @@ private final class NotificationsAndSoundsArguments {
let updateJoinedNotifications: (Bool) -> Void
let resetNotifications: () -> Void
let updatedExceptionMode: (NotificationExceptionMode) -> Void
let openAppSettings: () -> Void
let updateNotificationsFromAllAccounts: (Bool) -> Void
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, suppressWarning: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateIncludeTag: @escaping (CounterTagSettings, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void, updateJoinedNotifications: @escaping (Bool) -> Void, updateNotificationsFromAllAccounts: @escaping (Bool) -> Void) {
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeerCategory: @escaping (NotificationsPeerCategory) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateIncludeTag: @escaping (CounterTagSettings, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, openAppSettings: @escaping () -> Void, updateJoinedNotifications: @escaping (Bool) -> Void, updateNotificationsFromAllAccounts: @escaping (Bool) -> Void) {
self.context = context
self.presentController = presentController
self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable
self.authorizeNotifications = authorizeNotifications
self.suppressWarning = suppressWarning
self.updateMessageAlerts = updateMessageAlerts
self.updateMessagePreviews = updateMessagePreviews
self.updateMessageSound = updateMessageSound
self.updateGroupAlerts = updateGroupAlerts
self.updateGroupPreviews = updateGroupPreviews
self.updateGroupSound = updateGroupSound
self.updateChannelAlerts = updateChannelAlerts
self.updateChannelPreviews = updateChannelPreviews
self.updateChannelSound = updateChannelSound
self.openPeerCategory = openPeerCategory
self.updateInAppSounds = updateInAppSounds
self.updateInAppVibration = updateInAppVibration
self.updateInAppPreviews = updateInAppPreviews
@ -114,7 +94,6 @@ private final class NotificationsAndSoundsArguments {
self.updateIncludeTag = updateIncludeTag
self.updateTotalUnreadCountCategory = updateTotalUnreadCountCategory
self.resetNotifications = resetNotifications
self.updatedExceptionMode = updatedExceptionMode
self.openAppSettings = openAppSettings
self.updateJoinedNotifications = updateJoinedNotifications
self.updateNotificationsFromAllAccounts = updateNotificationsFromAllAccounts
@ -124,9 +103,7 @@ private final class NotificationsAndSoundsArguments {
private enum NotificationsAndSoundsSection: Int32 {
case accounts
case permission
case messages
case groups
case channels
case categories
case inApp
case displayNamesOnLockscreen
case badge
@ -136,12 +113,6 @@ private enum NotificationsAndSoundsSection: Int32 {
public enum NotificationsAndSoundsEntryTag: ItemListItemTag {
case allAccounts
case messageAlerts
case messagePreviews
case groupAlerts
case groupPreviews
case channelAlerts
case channelPreviews
case inAppSounds
case inAppVibrate
case inAppPreviews
@ -168,27 +139,10 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
case permissionInfo(PresentationTheme, String, String, Bool)
case permissionEnable(PresentationTheme, String)
case messageHeader(PresentationTheme, String)
case messageAlerts(PresentationTheme, String, Bool)
case messagePreviews(PresentationTheme, String, Bool)
case messageSound(PresentationTheme, String, String, PeerMessageSound)
case userExceptions(PresentationTheme, PresentationStrings, String, NotificationExceptionMode)
case messageNotice(PresentationTheme, String)
case groupHeader(PresentationTheme, String)
case groupAlerts(PresentationTheme, String, Bool)
case groupPreviews(PresentationTheme, String, Bool)
case groupSound(PresentationTheme, String, String, PeerMessageSound)
case groupExceptions(PresentationTheme, PresentationStrings, String, NotificationExceptionMode)
case groupNotice(PresentationTheme, String)
case channelHeader(PresentationTheme, String)
case channelAlerts(PresentationTheme, String, Bool)
case channelPreviews(PresentationTheme, String, Bool)
case channelSound(PresentationTheme, String, String, PeerMessageSound)
case channelExceptions(PresentationTheme, PresentationStrings, String, NotificationExceptionMode)
case channelNotice(PresentationTheme, String)
case categoriesHeader(PresentationTheme, String)
case privateChats(PresentationTheme, String, String, String)
case groupChats(PresentationTheme, String, String, String)
case channels(PresentationTheme, String, String, String)
case inAppHeader(PresentationTheme, String)
case inAppSounds(PresentationTheme, String, Bool)
@ -215,12 +169,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return NotificationsAndSoundsSection.accounts.rawValue
case .permissionInfo, .permissionEnable:
return NotificationsAndSoundsSection.permission.rawValue
case .messageHeader, .messageAlerts, .messagePreviews, .messageSound, .messageNotice, .userExceptions:
return NotificationsAndSoundsSection.messages.rawValue
case .groupHeader, .groupAlerts, .groupPreviews, .groupSound, .groupNotice, .groupExceptions:
return NotificationsAndSoundsSection.groups.rawValue
case .channelHeader, .channelAlerts, .channelPreviews, .channelSound, .channelNotice, .channelExceptions:
return NotificationsAndSoundsSection.channels.rawValue
case .categoriesHeader, .privateChats, .groupChats, .channels:
return NotificationsAndSoundsSection.categories.rawValue
case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews:
return NotificationsAndSoundsSection.inApp.rawValue
case .displayNamesOnLockscreen, .displayNamesOnLockscreenInfo:
@ -246,70 +196,42 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return 3
case .permissionEnable:
return 4
case .messageHeader:
case .categoriesHeader:
return 5
case .messageAlerts:
case .privateChats:
return 6
case .messagePreviews:
case .groupChats:
return 7
case .messageSound:
case .channels:
return 8
case .userExceptions:
return 9
case .messageNotice:
return 10
case .groupHeader:
return 11
case .groupAlerts:
return 12
case .groupPreviews:
return 13
case .groupSound:
return 14
case .groupExceptions:
return 15
case .groupNotice:
return 16
case .channelHeader:
return 17
case .channelAlerts:
return 18
case .channelPreviews:
return 19
case .channelSound:
return 20
case .channelExceptions:
return 21
case .channelNotice:
return 22
case .inAppHeader:
return 23
return 14
case .inAppSounds:
return 24
return 15
case .inAppVibrate:
return 25
return 16
case .inAppPreviews:
return 26
return 17
case .displayNamesOnLockscreen:
return 27
return 18
case .displayNamesOnLockscreenInfo:
return 28
return 19
case .badgeHeader:
return 29
return 20
case .includeChannels:
return 32
return 21
case .unreadCountCategory:
return 33
return 22
case .unreadCountCategoryInfo:
return 34
return 23
case .joinedNotifications:
return 35
return 24
case .joinedNotificationsInfo:
return 36
return 25
case .reset:
return 37
return 26
case .resetNotice:
return 38
return 27
}
}
@ -317,18 +239,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
switch self {
case .allAccounts:
return NotificationsAndSoundsEntryTag.allAccounts
case .messageAlerts:
return NotificationsAndSoundsEntryTag.messageAlerts
case .messagePreviews:
return NotificationsAndSoundsEntryTag.messagePreviews
case .groupAlerts:
return NotificationsAndSoundsEntryTag.groupAlerts
case .groupPreviews:
return NotificationsAndSoundsEntryTag.groupPreviews
case .channelAlerts:
return NotificationsAndSoundsEntryTag.channelAlerts
case .channelPreviews:
return NotificationsAndSoundsEntryTag.channelPreviews
case .inAppSounds:
return NotificationsAndSoundsEntryTag.inAppSounds
case .inAppVibrate:
@ -382,110 +292,26 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
} else {
return false
}
case let .messageHeader(lhsTheme, lhsText):
if case let .messageHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .categoriesHeader(lhsTheme, lhsText):
if case let .categoriesHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .messageAlerts(lhsTheme, lhsText, lhsValue):
if case let .messageAlerts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .privateChats(lhsTheme, lhsTitle, lhsSubtitle, lhsLabel):
if case let .privateChats(rhsTheme, rhsTitle, rhsSubtitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel {
return true
} else {
return false
}
case let .messagePreviews(lhsTheme, lhsText, lhsValue):
if case let .messagePreviews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .groupChats(lhsTheme, lhsTitle, lhsSubtitle, lhsLabel):
if case let .groupChats(rhsTheme, rhsTitle, rhsSubtitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel {
return true
} else {
return false
}
case let .messageSound(lhsTheme, lhsText, lhsValue, lhsSound):
if case let .messageSound(rhsTheme, rhsText, rhsValue, rhsSound) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound {
return true
} else {
return false
}
case let .userExceptions(lhsTheme, lhsStrings, lhsText, lhsValue):
if case let .userExceptions(rhsTheme, rhsStrings, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .messageNotice(lhsTheme, lhsText):
if case let .messageNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .groupHeader(lhsTheme, lhsText):
if case let .groupHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .groupAlerts(lhsTheme, lhsText, lhsValue):
if case let .groupAlerts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .groupPreviews(lhsTheme, lhsText, lhsValue):
if case let .groupPreviews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .groupSound(lhsTheme, lhsText, lhsValue, lhsSound):
if case let .groupSound(rhsTheme, rhsText, rhsValue, rhsSound) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound {
return true
} else {
return false
}
case let .groupExceptions(lhsTheme, lhsStrings, lhsText, lhsValue):
if case let .groupExceptions(rhsTheme, rhsStrings, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .groupNotice(lhsTheme, lhsText):
if case let .groupNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .channelHeader(lhsTheme, lhsText):
if case let .channelHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .channelAlerts(lhsTheme, lhsText, lhsValue):
if case let .channelAlerts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .channelPreviews(lhsTheme, lhsText, lhsValue):
if case let .channelPreviews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .channelSound(lhsTheme, lhsText, lhsValue, lhsSound):
if case let .channelSound(rhsTheme, rhsText, rhsValue, rhsSound) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound {
return true
} else {
return false
}
case let .channelExceptions(lhsTheme, lhsStrings, lhsText, lhsValue):
if case let .channelExceptions(rhsTheme, rhsStrings, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .channelNotice(lhsTheme, lhsText):
if case let .channelNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
case let .channels(lhsTheme, lhsTitle, lhsSubtitle, lhsLabel):
if case let .channels(rhsTheme, rhsTitle, rhsSubtitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel {
return true
} else {
return false
@ -600,81 +426,20 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.authorizeNotifications()
})
case let .messageHeader(_, text):
case let .categoriesHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .messageAlerts(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateMessageAlerts(updatedValue)
}, tag: self.tag)
case let .messagePreviews(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateMessagePreviews(updatedValue)
}, tag: self.tag)
case let .messageSound(_, text, value, sound):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
let controller = notificationSoundSelectionController(context: arguments.context, isModal: true, currentSound: sound, defaultSound: nil, completion: { [weak arguments] value in
arguments?.updateMessageSound(value)
})
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
case let .privateChats(_, title, subtitle, label):
return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/EditProfile"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: {
arguments.openPeerCategory(.privateChat)
})
case let .userExceptions(_, strings, text, value):
let label = value.settings.count > 0 ? strings.Notifications_Exceptions(Int32(value.settings.count)) : strings.Notification_Exceptions_Add
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: label, sectionId: self.section, style: .blocks, action: {
let controller = NotificationExceptionsController(context: arguments.context, mode: value, updatedMode: arguments.updatedExceptionMode)
arguments.pushController(controller)
case let .groupChats(_, title, subtitle, label):
return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/GroupChats"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: {
arguments.openPeerCategory(.group)
})
case let .messageNotice(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .groupHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .groupAlerts(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateGroupAlerts(updatedValue)
}, tag: self.tag)
case let .groupPreviews(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateGroupPreviews(updatedValue)
}, tag: self.tag)
case let .groupSound(_, text, value, sound):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
let controller = notificationSoundSelectionController(context: arguments.context, isModal: true, currentSound: sound, defaultSound: nil, completion: { [weak arguments] value in
arguments?.updateGroupSound(value)
})
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
case let .channels(_, title, subtitle, label):
return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Channels"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: {
arguments.openPeerCategory(.channel)
})
case let .groupExceptions(_, strings, text, value):
let label = value.settings.count > 0 ? strings.Notifications_Exceptions(Int32(value.settings.count)) : strings.Notification_Exceptions_Add
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: label, sectionId: self.section, style: .blocks, action: {
let controller = NotificationExceptionsController(context: arguments.context, mode: value, updatedMode: arguments.updatedExceptionMode)
arguments.pushController(controller)
})
case let .groupNotice(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .channelHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .channelAlerts(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateChannelAlerts(updatedValue)
}, tag: self.tag)
case let .channelPreviews(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
arguments.updateChannelPreviews(updatedValue)
}, tag: self.tag)
case let .channelSound(_, text, value, sound):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
let controller = notificationSoundSelectionController(context: arguments.context, isModal: true, currentSound: sound, defaultSound: nil, completion: { [weak arguments] value in
arguments?.updateChannelSound(value)
})
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
})
case let .channelExceptions(_, strings, text, value):
let label = value.settings.count > 0 ? strings.Notifications_Exceptions(Int32(value.settings.count)) : strings.Notification_Exceptions_Add
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: label, sectionId: self.section, style: .blocks, action: {
let controller = NotificationExceptionsController(context: arguments.context, mode: value, updatedMode: arguments.updatedExceptionMode)
arguments.pushController(controller)
})
case let .channelNotice(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .inAppHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .inAppSounds(_, text, value):
@ -768,32 +533,10 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
}
}
entries.append(.messageHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
entries.append(.messageAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.privateChats.enabled))
if globalSettings.privateChats.enabled || !exceptions.users.isEmpty {
entries.append(.messagePreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.privateChats.displayPreviews))
entries.append(.messageSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.privateChats.sound)), filteredGlobalSound(globalSettings.privateChats.sound)))
}
entries.append(.userExceptions(presentationData.theme, presentationData.strings, presentationData.strings.Notifications_MessageNotificationsExceptions, exceptions.users))
entries.append(.messageNotice(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsExceptionsHelp))
entries.append(.groupHeader(presentationData.theme, presentationData.strings.Notifications_GroupNotifications.uppercased()))
entries.append(.groupAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.groupChats.enabled))
if globalSettings.groupChats.enabled || !exceptions.groups.isEmpty {
entries.append(.groupPreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.groupChats.displayPreviews))
entries.append(.groupSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.groupChats.sound)), filteredGlobalSound(globalSettings.groupChats.sound)))
}
entries.append(.groupExceptions(presentationData.theme, presentationData.strings, presentationData.strings.Notifications_MessageNotificationsExceptions, exceptions.groups))
entries.append(.groupNotice(presentationData.theme, presentationData.strings.Notifications_GroupNotificationsExceptionsHelp))
entries.append(.channelHeader(presentationData.theme, presentationData.strings.Notifications_ChannelNotifications.uppercased()))
entries.append(.channelAlerts(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, globalSettings.channels.enabled))
if globalSettings.channels.enabled || !exceptions.channels.isEmpty {
entries.append(.channelPreviews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, globalSettings.channels.displayPreviews))
entries.append(.channelSound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(globalSettings.channels.sound)), filteredGlobalSound(globalSettings.channels.sound)))
}
entries.append(.channelExceptions(presentationData.theme, presentationData.strings, presentationData.strings.Notifications_MessageNotificationsExceptions, exceptions.channels))
entries.append(.channelNotice(presentationData.theme, presentationData.strings.Notifications_ChannelNotificationsExceptionsHelp))
entries.append(.categoriesHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
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(.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(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
@ -858,60 +601,30 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Notifications_PermissionsEnable, action: {
context.sharedContext.applicationBindings.openSettings()
})]), nil)
}, updateMessageAlerts: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.privateChats.enabled = value
return settings
}).start()
}, updateMessagePreviews: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.privateChats.displayPreviews = value
return settings
}).start()
}, updateMessageSound: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.privateChats.sound = value
return settings
}).start()
}, updateGroupAlerts: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.groupChats.enabled = value
return settings
}).start()
}, updateGroupPreviews: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.groupChats.displayPreviews = value
return settings
}).start()
}, updateGroupSound: {value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.groupChats.sound = value
return settings
}).start()
}, updateChannelAlerts: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.channels.enabled = value
return settings
}).start()
}, updateChannelPreviews: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.channels.displayPreviews = value
return settings
}).start()
}, updateChannelSound: {value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
settings.channels.sound = value
return settings
}).start()
}, openPeerCategory: { category in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels) in
let mode: NotificationExceptionMode
switch category {
case .privateChat:
mode = users
case .group:
mode = groups
case .channel:
mode = channels
}
pushControllerImpl?(notificationsPeerCategoryController(context: context, category: category, mode: mode, updatedMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels) in
switch mode {
case .users:
updateNotificationExceptions((mode, groups, channels))
case .groups:
updateNotificationExceptions((users, mode, channels))
case .channels:
updateNotificationExceptions((users, groups, mode))
}
})
}, focusOnItemTag: nil))
})
}, updateInAppSounds: { value in
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
var settings = settings
@ -977,17 +690,6 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
})
])])
presentControllerImpl?(actionSheet, nil)
}, updatedExceptionMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels) in
switch mode {
case .users:
updateNotificationExceptions((mode, groups, channels))
case .groups:
updateNotificationExceptions((users, mode, channels))
case .channels:
updateNotificationExceptions((users, groups, mode))
}
})
}, openAppSettings: {
context.sharedContext.applicationBindings.openSettings()
}, updateJoinedNotifications: { value in

View File

@ -0,0 +1,442 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import ShimmerEffect
import ItemListUI
public class NotificationsCategoryItemListItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let icon: UIImage?
let title: String
let subtitle: String
let enabled: Bool
let label: String
public let sectionId: ItemListSectionId
let style: ItemListStyle
let action: (() -> Void)?
public let tag: ItemListItemTag?
public let shimmeringIndex: Int?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, subtitle: String, enabled: Bool = true, label: String, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
self.presentationData = presentationData
self.icon = icon
self.title = title
self.subtitle = subtitle
self.enabled = enabled
self.label = label
self.sectionId = sectionId
self.style = style
self.action = action
self.tag = tag
self.shimmeringIndex = shimmeringIndex
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = NotificationsCategoryItemListItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? NotificationsCategoryItemListItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
public var selectable: Bool = true
public func selected(listView: ListView){
listView.clearHighlightAnimated(true)
if self.enabled {
self.action?()
}
}
}
public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
let iconNode: ASImageNode
let titleNode: TextNode
let subtitleNode: TextNode
let labelNode: TextNode
let arrowNode: ASImageNode
private let activateArea: AccessibilityAreaNode
private var item: NotificationsCategoryItemListItem?
override public var canBeSelected: Bool {
if let item = self.item, let _ = item.action {
return true
} else {
return false
}
}
public var tag: ItemListItemTag? {
return self.item?.tag
}
private var placeholderNode: ShimmerEffectNode?
private var absoluteLocation: (CGRect, CGSize)?
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.subtitleNode = TextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
self.arrowNode = ASImageNode()
self.arrowNode.displayWithoutProcessing = true
self.arrowNode.displaysAsynchronously = false
self.arrowNode.isLayerBacked = true
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.activateArea)
}
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
var rect = rect
rect.origin.y += self.insets.top
self.absoluteLocation = (rect, containerSize)
if let shimmerNode = self.placeholderNode {
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
}
}
public func asyncLayout() -> (_ item: NotificationsCategoryItemListItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let currentItem = self.item
return { item, params, neighbors in
let rightInset = 34.0 + params.rightInset
var updateArrowImage: UIImage?
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
}
var updateIcon = false
if currentItem?.icon != item.icon {
updateIcon = true
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
var leftInset = 16.0 + params.leftInset
if let _ = item.icon {
leftInset += 43.0
}
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
let titleColor = item.presentationData.theme.list.itemPrimaryTextColor
let detailFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
let detailColor = item.presentationData.theme.list.itemSecondaryTextColor
let labelFont = titleFont
let labelColor = item.presentationData.theme.list.itemSecondaryTextColor
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 60.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let additionalTextRightInset: CGFloat = labelLayout.size.width
let textConstrain = params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle, font: detailFont, textColor: detailColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let verticalInset: CGFloat = 11.0
let titleSpacing: CGFloat = 1.0
let height: CGFloat
if !item.subtitle.isEmpty {
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height
} else {
height = verticalInset * 2.0 + titleLayout.size.height
}
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsGroupedInsets(neighbors)
}
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
strongSelf.activateArea.accessibilityLabel = item.title
strongSelf.activateArea.accessibilityValue = item.label
if item.enabled {
strongSelf.activateArea.accessibilityTraits = []
} else {
strongSelf.activateArea.accessibilityTraits = .notEnabled
}
if let icon = item.icon {
if strongSelf.iconNode.supernode == nil {
strongSelf.addSubnode(strongSelf.iconNode)
}
if updateIcon {
strongSelf.iconNode.image = icon
}
let iconY = floorToScreenPixels((layout.contentSize.height - icon.size.height) / 2.0)
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size)
} else if strongSelf.iconNode.supernode != nil {
strongSelf.iconNode.image = nil
strongSelf.iconNode.removeFromSupernode()
}
if let updateArrowImage = updateArrowImage {
strongSelf.arrowNode.image = updateArrowImage
}
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
}
let _ = titleApply()
let _ = subtitleApply()
let _ = labelApply()
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
strongSelf.backgroundNode.removeFromSupernode()
}
if strongSelf.topStripeNode.supernode != nil {
strongSelf.topStripeNode.removeFromSupernode()
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: subtitleLayout.size)
strongSelf.subtitleNode.frame = subtitleFrame
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floorToScreenPixels((height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
if let arrowImage = strongSelf.arrowNode.image {
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel))
if let shimmeringIndex = item.shimmeringIndex {
let shimmerNode: ShimmerEffectNode
if let current = strongSelf.placeholderNode {
shimmerNode = current
} else {
shimmerNode = ShimmerEffectNode()
strongSelf.placeholderNode = shimmerNode
if strongSelf.backgroundNode.supernode != nil {
strongSelf.insertSubnode(shimmerNode, aboveSubnode: strongSelf.backgroundNode)
} else {
strongSelf.addSubnode(shimmerNode)
}
}
shimmerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if let (rect, size) = strongSelf.absoluteLocation {
shimmerNode.updateAbsoluteRect(rect, within: size)
}
var shapes: [ShimmerEffectNode.Shape] = []
let titleLineWidth: CGFloat = (shimmeringIndex % 2 == 0) ? 120.0 : 80.0
let lineDiameter: CGFloat = 8.0
let titleFrame = strongSelf.titleNode.frame
shapes.append(.roundedRectLine(startPoint: CGPoint(x: titleFrame.minX, y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: titleLineWidth, diameter: lineDiameter))
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: contentSize)
} else if let shimmerNode = strongSelf.placeholderNode {
strongSelf.placeholderNode = nil
shimmerNode.removeFromSupernode()
}
}
})
}
}
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted && (self.item?.enabled ?? false) {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
var anchorNode: ASDisplayNode?
if self.bottomStripeNode.supernode != nil {
anchorNode = self.bottomStripeNode
} else if self.topStripeNode.supernode != nil {
anchorNode = self.topStripeNode
} else if self.backgroundNode.supernode != nil {
anchorNode = self.backgroundNode
}
if let anchorNode = anchorNode {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
} else {
self.addSubnode(self.highlightedBackgroundNode)
}
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
if animated {
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
if let strongSelf = self {
if completed {
strongSelf.highlightedBackgroundNode.removeFromSupernode()
}
}
})
self.highlightedBackgroundNode.alpha = 0.0
} else {
self.highlightedBackgroundNode.removeFromSupernode()
}
}
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
}

View File

@ -18,15 +18,27 @@ import TelegramStringFormatting
import ItemListPeerItem
import ItemListPeerActionItem
private extension PeerMuteState {
var timeInterval: Int32? {
switch self {
case .default:
return nil
case .unmuted:
return 0
case let .muted(until):
return until
}
}
}
private final class NotificationsPeerCategoryControllerArguments {
let context: AccountContext
let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
let pushController: (ViewController) -> Void
let soundSelectionDisposable: MetaDisposable
let updateEnabled: (Bool) -> Void
let updatePreviews: (Bool) -> Void
let updateSound: (PeerMessageSound) -> Void
let openSound: (PeerMessageSound) -> Void
let addException: () -> Void
let openException: (Peer) -> Void
@ -36,15 +48,13 @@ private final class NotificationsPeerCategoryControllerArguments {
let updatedExceptionMode: (NotificationExceptionMode) -> Void
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, updateSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (Peer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (PeerId?) -> Void, removePeer: @escaping (Peer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) {
init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (Peer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (PeerId?) -> Void, removePeer: @escaping (Peer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) {
self.context = context
self.presentController = presentController
self.pushController = pushController
self.soundSelectionDisposable = soundSelectionDisposable
self.updateEnabled = updateEnabled
self.updatePreviews = updatePreviews
self.updateSound = updateSound
self.openSound = openSound
self.addException = addException
self.openException = openException
@ -85,7 +95,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
case exceptionsHeader(PresentationTheme, String)
case addException(PresentationTheme, String)
case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, TelegramPeerNotificationSettings, Bool, Bool)
case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, String, TelegramPeerNotificationSettings, Bool, Bool)
case removeAllExceptions(PresentationTheme, String)
var section: ItemListSectionId {
@ -113,7 +123,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
return 4
case .addException:
return 5
case let .exception(index, _, _, _, _, _, _, _, _):
case let .exception(index, _, _, _, _, _, _, _, _, _):
return 6 + index
case .removeAllExceptions:
return 100000
@ -171,8 +181,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
} else {
return false
}
case let .exception(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayNameOrder, lhsPeer, lhsSettings, lhsEditing, lhsRevealed):
if case let .exception(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayNameOrder, rhsPeer, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, arePeersEqual(lhsPeer, rhsPeer), lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
case let .exception(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayNameOrder, lhsPeer, lhsDescription, lhsSettings, lhsEditing, lhsRevealed):
if case let .exception(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayNameOrder, rhsPeer, rhsDescription, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, arePeersEqual(lhsPeer, rhsPeer), lhsDescription == rhsDescription, lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
@ -204,9 +214,9 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updatePreviews(value)
})
case let .sound(_, text, value, _):
case let .sound(_, text, value, sound):
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.openSound(sound)
}, tag: self.tag)
case let .exceptionsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
@ -214,18 +224,18 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.addException()
})
case let .removeAllExceptions(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
arguments.removeAllExceptions()
})
case let .exception(_, _, _, dateTimeFormat, nameDisplayOrder, peer, _, editing, revealed):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text("", .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
case let .exception(_, _, _, dateTimeFormat, nameDisplayOrder, peer, description, _, editing, revealed):
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text(description, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: {
arguments.openException(peer)
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
arguments.updateRevealedPeerId(peerId)
}, removePeer: { peerId in
arguments.removePeer(peer)
}, hasTopStripe: false, hasTopGroupInset: false, noInsets: false)
case let .removeAllExceptions(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
arguments.removeAllExceptions()
})
}
}
}
@ -238,10 +248,11 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
}
}
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsPeerCategoryEntry] {
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, state: NotificationExceptionState, presentationData: PresentationData) -> [NotificationsPeerCategoryEntry] {
var entries: [NotificationsPeerCategoryEntry] = []
let notificationSettings: MessageNotificationSettings
let notificationExceptions = state.mode
switch category {
case .privateChat:
notificationSettings = globalSettings.privateChats
@ -253,14 +264,106 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
entries.append(.enable(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, notificationSettings.enabled))
if notificationSettings.enabled || !exceptions.users.isEmpty {
if notificationSettings.enabled || !notificationExceptions.isEmpty {
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
entries.append(.previews(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsPreview, notificationSettings.displayPreviews))
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, sound: filteredGlobalSound(notificationSettings.sound)), filteredGlobalSound(notificationSettings.sound)))
}
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotifications.uppercased()))
entries.append(.addException(presentationData.theme, presentationData.strings.Notification_Exceptions_AddException))
let sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in
let lhsName = EnginePeer(lhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let rhsName = EnginePeer(rhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
if let lhsDate = lhs.value.date, let rhsDate = rhs.value.date {
return lhsDate > rhsDate
} else if lhs.value.date != nil && rhs.value.date == nil {
return true
} else if lhs.value.date == nil && rhs.value.date != nil {
return false
}
if let lhsPeer = lhs.value.peer as? TelegramUser, let rhsPeer = rhs.value.peer as? TelegramUser {
if lhsPeer.botInfo != nil && rhsPeer.botInfo == nil {
return false
} else if lhsPeer.botInfo == nil && rhsPeer.botInfo != nil {
return true
}
}
return lhsName < rhsName
})
var existingPeerIds = Set<PeerId>()
var index: Int = 0
for (_, value) in sortedExceptions {
if !value.peer.isDeleted {
var title: String
var muted = false
switch value.settings.muteState {
case let .muted(until):
if until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
if until < Int32.max - 1 {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: presentationData.strings.baseLanguageCode)
if Calendar.current.isDateInToday(Date(timeIntervalSince1970: Double(until))) {
formatter.dateFormat = "HH:mm"
} else {
formatter.dateFormat = "E, d MMM HH:mm"
}
let dateString = formatter.string(from: Date(timeIntervalSince1970: Double(until)))
title = presentationData.strings.Notification_Exceptions_MutedUntil(dateString).string
} else {
muted = true
title = presentationData.strings.Notification_Exceptions_AlwaysOff
}
} else {
title = presentationData.strings.Notification_Exceptions_AlwaysOn
}
case .unmuted:
title = presentationData.strings.Notification_Exceptions_AlwaysOn
default:
title = ""
}
if !muted {
switch value.settings.messageSound {
case .default:
break
default:
let soundName = localizedPeerNotificationSoundString(strings: presentationData.strings, sound: value.settings.messageSound)
title += (title.isEmpty ? presentationData.strings.Notification_Exceptions_Sound(soundName).string : ", \(presentationData.strings.Notification_Exceptions_Sound(soundName).string)")
}
switch value.settings.displayPreviews {
case .default:
break
default:
if !title.isEmpty {
title += ", "
}
if case .show = value.settings.displayPreviews {
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOn
} else {
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOff
}
}
}
existingPeerIds.insert(value.peer.id)
entries.append(.exception(Int32(index), presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, value.peer, title, value.settings, state.editing, state.revealedPeerId == value.peer.id))
index += 1
}
}
if notificationExceptions.peerIds.count > 0 {
entries.append(.removeAllExceptions(presentationData.theme, presentationData.strings.Notifications_DeleteAllExceptions))
}
return entries
}
@ -270,21 +373,186 @@ public enum NotificationsPeerCategory {
case channel
}
public func notificationsPeerCategoryController(context: AccountContext, category: NotificationsPeerCategory, exceptionsList: NotificationExceptionsList?, focusOnItemTag: NotificationsPeerCategoryEntryTag? = nil) -> ViewController {
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
private final class NotificationExceptionState : Equatable {
let mode: NotificationExceptionMode
let isSearchMode: Bool
let revealedPeerId: PeerId?
let editing: Bool
init(mode: NotificationExceptionMode, isSearchMode: Bool = false, revealedPeerId: PeerId? = nil, editing: Bool = false) {
self.mode = mode
self.isSearchMode = isSearchMode
self.revealedPeerId = revealedPeerId
self.editing = editing
}
func withUpdatedMode(_ mode: NotificationExceptionMode) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode, isSearchMode: self.isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedSearchMode(_ isSearchMode: Bool) -> NotificationExceptionState {
return NotificationExceptionState(mode: self.mode, isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedEditing(_ editing: Bool) -> NotificationExceptionState {
return NotificationExceptionState(mode: self.mode, isSearchMode: self.isSearchMode, revealedPeerId: self.revealedPeerId, editing: editing)
}
func withUpdatedRevealedPeerId(_ revealedPeerId: PeerId?) -> NotificationExceptionState {
return NotificationExceptionState(mode: self.mode, isSearchMode: self.isSearchMode, revealedPeerId: revealedPeerId, editing: self.editing)
}
func withUpdatedPeerSound(_ peer: Peer, _ sound: PeerMessageSound) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerSound(peer, sound), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerMuteInterval(_ peer: Peer, _ muteInterval: Int32?) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerMuteInterval(peer, muteInterval), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
func withUpdatedPeerDisplayPreviews(_ peer: Peer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionState {
return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing)
}
static func == (lhs: NotificationExceptionState, rhs: NotificationExceptionState) -> Bool {
return lhs.mode == rhs.mode && lhs.isSearchMode == rhs.isSearchMode && lhs.revealedPeerId == rhs.revealedPeerId && lhs.editing == rhs.editing
}
}
public func notificationsPeerCategoryController(context: AccountContext, category: NotificationsPeerCategory, mode: NotificationExceptionMode, updatedMode: @escaping (NotificationExceptionMode) -> Void, focusOnItemTag: NotificationsPeerCategoryEntryTag? = nil) -> ViewController {
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let stateValue = Atomic<NotificationExceptionState>(value: NotificationExceptionState(mode: mode))
let statePromise: ValuePromise<NotificationExceptionState> = ValuePromise(ignoreRepeated: true)
statePromise.set(NotificationExceptionState(mode: mode))
let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)> = Promise()
let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode)) -> Void = { value in
notificationExceptions.set(.single(value))
}
let arguments = NotificationsPeerCategoryControllerArguments(context: context, presentController: { controller, arguments in
presentControllerImpl?(controller, arguments)
}, pushController: { controller in
pushControllerImpl?(controller)
}, soundSelectionDisposable: MetaDisposable(), updateEnabled: { value in
let updateState: ((NotificationExceptionState) -> NotificationExceptionState) -> Void = { f in
let result = stateValue.modify { f($0) }
statePromise.set(result)
updatedMode(result.mode)
}
let updatePeerSound: (PeerId, PeerMessageSound) -> Signal<Void, NoError> = { peerId, sound in
return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue
}
let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue
}
let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
peerId, displayPreviews in
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue
}
var peerIds: Set<PeerId> = Set(mode.peerIds)
let updateNotificationsDisposable = MetaDisposable()
let updateNotificationsView: (@escaping () -> Void) -> Void = { completion in
updateState { current in
peerIds = peerIds.union(current.mode.peerIds)
let key: PostboxViewKey = .peerNotificationSettings(peerIds: peerIds)
updateNotificationsDisposable.set((context.account.postbox.combinedView(keys: [key])
|> deliverOnMainQueue).start(next: { view in
if let view = view.views[key] as? PeerNotificationSettingsView {
_ = context.account.postbox.transaction { transaction in
updateState { current in
var current = current
for (key, value) in view.notificationSettings {
if let value = value as? TelegramPeerNotificationSettings {
if let local = current.mode.settings[key] {
if !value.isEqual(to: local.settings), let peer = transaction.getPeer(key), let settings = transaction.getPeerNotificationSettings(key) as? TelegramPeerNotificationSettings, !settings.isEqual(to: local.settings) {
current = current.withUpdatedPeerSound(peer, settings.messageSound).withUpdatedPeerMuteInterval(peer, settings.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, settings.displayPreviews)
}
} else if let peer = transaction.getPeer(key) {
if case .default = value.messageSound, case .unmuted = value.muteState, case .default = value.displayPreviews {
} else {
current = current.withUpdatedPeerSound(peer, value.messageSound).withUpdatedPeerMuteInterval(peer, value.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, value.displayPreviews)
}
}
}
}
return current
}
}.start(completed: {
completion()
})
} else {
completion()
}
}))
return current
}
}
updateNotificationsView({})
let presentPeerSettings: (PeerId, @escaping () -> Void) -> Void = { peerId, completion in
let _ = (context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> deliverOnMainQueue).start(next: { peer in
completion()
guard let peer = peer else {
return
}
let mode = stateValue.with { $0.mode }
pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer, mode: mode, updatePeerSound: { peerId, sound in
_ = updatePeerSound(peer.id, sound).start(next: { _ in
updateNotificationsDisposable.set(nil)
_ = combineLatest(updatePeerSound(peer.id, sound), context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { _, peer in
updateState { value in
return value.withUpdatedPeerSound(peer, sound)
}
updateNotificationsView({})
})
})
}, updatePeerNotificationInterval: { peerId, muteInterval in
updateNotificationsDisposable.set(nil)
_ = combineLatest(updatePeerNotificationInterval(peerId, muteInterval), context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { _, peer in
updateState { value in
return value.withUpdatedPeerMuteInterval(peer, muteInterval)
}
updateNotificationsView({})
})
}, updatePeerDisplayPreviews: { peerId, displayPreviews in
updateNotificationsDisposable.set(nil)
_ = combineLatest(updatePeerDisplayPreviews(peerId, displayPreviews), context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { _, peer in
updateState { value in
return value.withUpdatedPeerDisplayPreviews(peer, displayPreviews)
}
updateNotificationsView({})
})
}, removePeerFromExceptions: {
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peerId])
|> map { _ -> Peer? in }
|> then(context.account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
})).start(next: { peer in
guard let peer = peer else {
return
}
updateState { value in
return value.withUpdatedPeerDisplayPreviews(peer, .default).withUpdatedPeerSound(peer, .default).withUpdatedPeerMuteInterval(peer, nil)
}
updateNotificationsView({})
})
}, modifiedPeer: {
}))
})
}
let arguments = NotificationsPeerCategoryControllerArguments(context: context, soundSelectionDisposable: MetaDisposable(), updateEnabled: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
switch category {
@ -310,29 +578,104 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
}
return settings
}).start()
}, updateSound: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
switch category {
case .privateChat:
settings.privateChats.sound = value
case .group:
settings.groupChats.sound = value
case .channel:
settings.channels.sound = value
}
return settings
}).start()
}, openSound: { sound in
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: sound, defaultSound: nil, completion: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings
switch category {
case .privateChat:
settings.privateChats.sound = value
case .group:
settings.groupChats.sound = value
case .channel:
settings.channels.sound = value
}
return settings
}).start()
})
pushControllerImpl?(controller)
}, addException: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var filter: ChatListNodePeersFilter = [.excludeRecent, .doNotSearchMessages, .removeSearchHeader]
switch category {
case .privateChat:
filter.insert(.onlyPrivateChats)
filter.insert(.excludeSavedMessages)
filter.insert(.excludeSecretChats)
case .group:
filter.insert(.onlyGroups)
case .channel:
filter.insert(.onlyChannels)
}
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: filter, hasContactSelector: false, title: presentationData.strings.Notifications_AddExceptionTitle))
controller.peerSelected = { [weak controller] peer in
let peerId = peer.id
presentPeerSettings(peerId, {
controller?.dismiss()
})
}
pushControllerImpl?(controller)
}, openException: { peer in
presentPeerSettings(peer.id, {})
}, removeAllExceptions: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.Notification_Exceptions_DeleteAllConfirmation),
ActionSheetButtonItem(title: presentationData.strings.Notification_Exceptions_DeleteAll, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let values = stateValue.with { $0.mode.settings.values }
let _ = (context.account.postbox.transaction { transaction -> Void in
for value in values {
if transaction.getPeer(value.peer.id) == nil {
updatePeers(transaction: transaction, peers: [value.peer], update: { _, updated in
updated
})
}
}
}
|> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil)
updateState { state in
var state = state
for value in values {
state = state.withUpdatedPeerMuteInterval(value.peer, nil).withUpdatedPeerSound(value.peer, .default).withUpdatedPeerDisplayPreviews(value.peer, .default)
}
return state
}
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: values.map(\.peer.id))
|> deliverOnMainQueue).start(completed: {
updateNotificationsView({})
})
})
})
]), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
presentControllerImpl?(actionSheet, nil)
}, updateRevealedPeerId: { peerId in
updateState { current in
return current.withUpdatedRevealedPeerId(peerId)
}
}, removePeer: { peer in
_ = (context.account.postbox.transaction { transaction in
if transaction.getPeer(peer.id) == nil {
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in return updated})
}
} |> deliverOnMainQueue).start(completed: {
updateNotificationsDisposable.set(nil)
updateState { value in
return value.withUpdatedPeerMuteInterval(peer, nil).withUpdatedPeerSound(peer, .default).withUpdatedPeerDisplayPreviews(peer, .default)
}
let _ = (context.engine.peers.removeCustomNotificationSettings(peerIds: [peer.id])
|> deliverOnMainQueue).start(completed: {
updateNotificationsView({})
})
})
}, updatedExceptionMode: { mode in
_ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels) in
switch mode {
@ -349,77 +692,32 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications])
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init))
notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in
var users:[PeerId : NotificationExceptionWrapper] = [:]
var groups: [PeerId : NotificationExceptionWrapper] = [:]
var channels:[PeerId : NotificationExceptionWrapper] = [:]
if let list = list {
for (key, value) in list.settings {
if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId {
switch value.muteState {
case .default:
switch value.messageSound {
case .default:
break
default:
switch key.namespace {
case Namespaces.Peer.CloudUser:
users[key] = NotificationExceptionWrapper(settings: value, peer: peer)
default:
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
channels[key] = NotificationExceptionWrapper(settings: value, peer: peer)
} else {
groups[key] = NotificationExceptionWrapper(settings: value, peer: peer)
}
}
}
default:
switch key.namespace {
case Namespaces.Peer.CloudUser:
users[key] = NotificationExceptionWrapper(settings: value, peer: peer)
default:
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
channels[key] = NotificationExceptionWrapper(settings: value, peer: peer)
} else {
groups[key] = NotificationExceptionWrapper(settings: value, peer: peer)
}
}
}
let signal = combineLatest(context.sharedContext.presentationData, sharedData, preferences, statePromise.get())
|> map { presentationData, sharedData, view, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let viewSettings: GlobalNotificationSettingsSet
if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) {
viewSettings = settings.effective
} else {
viewSettings = GlobalNotificationSettingsSet.defaultSettings
}
let entries = notificationsPeerCategoryEntries(category: category, globalSettings: viewSettings, state: state, presentationData: presentationData)
var index = 0
var scrollToItem: ListViewScrollToItem?
if let focusOnItemTag = focusOnItemTag {
for entry in entries {
if entry.tag?.isEqual(to: focusOnItemTag) ?? false {
scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
}
index += 1
}
}
return (.users(users), .groups(groups), .channels(channels))
})
let signal = combineLatest(context.sharedContext.presentationData, sharedData, preferences, notificationExceptions.get())
|> map { presentationData, sharedData, view, exceptions -> (ItemListControllerState, (ItemListNodeState, Any)) in
let viewSettings: GlobalNotificationSettingsSet
if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) {
viewSettings = settings.effective
} else {
viewSettings = GlobalNotificationSettingsSet.defaultSettings
}
let entries = notificationsPeerCategoryEntries(category: category, globalSettings: viewSettings, exceptions: exceptions, presentationData: presentationData)
var index = 0
var scrollToItem: ListViewScrollToItem?
if let focusOnItemTag = focusOnItemTag {
for entry in entries {
if entry.tag?.isEqual(to: focusOnItemTag) ?? false {
scrollToItem = ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
}
index += 1
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)
return (controllerState, (listState, arguments))
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Notifications_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, ensureVisibleItemTag: focusOnItemTag, initialScrollToItem: scrollToItem)
return (controllerState, (listState, arguments))
}
let controller = ItemListController(context: context, state: signal)

View File

@ -331,12 +331,12 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob
SettingsSearchableItem(id: .notifications(0), title: strings.Settings_NotificationsAndSounds, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_Title), icon: icon, breadcrumbs: [], present: { context, _, present in
presentNotificationSettings(context, present, nil)
}),
SettingsSearchableItem(id: .notifications(1), title: strings.Notifications_MessageNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .messageAlerts)
}),
SettingsSearchableItem(id: .notifications(2), title: strings.Notifications_MessageNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .messagePreviews)
}),
// SettingsSearchableItem(id: .notifications(1), title: strings.Notifications_MessageNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .messageAlerts)
// }),
// SettingsSearchableItem(id: .notifications(2), title: strings.Notifications_MessageNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .messagePreviews)
// }),
SettingsSearchableItem(id: .notifications(3), title: strings.Notifications_MessageNotificationsSound, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsSound), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: filteredGlobalSound(settings.privateChats.sound), defaultSound: nil, completion: { value in
@ -351,12 +351,12 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob
SettingsSearchableItem(id: .notifications(4), title: strings.Notifications_MessageNotificationsExceptions, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_MessageNotificationsExceptions), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_MessageNotifications], present: { context, _, present in
present(.push, NotificationExceptionsController(context: context, mode: exceptions().0, updatedMode: { _ in}))
}),
SettingsSearchableItem(id: .notifications(5), title: strings.Notifications_GroupNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .groupAlerts)
}),
SettingsSearchableItem(id: .notifications(6), title: strings.Notifications_GroupNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .groupPreviews)
}),
// SettingsSearchableItem(id: .notifications(5), title: strings.Notifications_GroupNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .groupAlerts)
// }),
// SettingsSearchableItem(id: .notifications(6), title: strings.Notifications_GroupNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .groupPreviews)
// }),
SettingsSearchableItem(id: .notifications(7), title: strings.Notifications_GroupNotificationsSound, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsSound), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: filteredGlobalSound(settings.groupChats.sound), defaultSound: nil, completion: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
@ -370,12 +370,12 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob
SettingsSearchableItem(id: .notifications(8), title: strings.Notifications_GroupNotificationsExceptions, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_GroupNotificationsExceptions), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_GroupNotifications], present: { context, _, present in
present(.push, NotificationExceptionsController(context: context, mode: exceptions().1, updatedMode: { _ in}))
}),
SettingsSearchableItem(id: .notifications(9), title: strings.Notifications_ChannelNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .channelAlerts)
}),
SettingsSearchableItem(id: .notifications(10), title: strings.Notifications_ChannelNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications], present: { context, _, present in
presentNotificationSettings(context, present, .channelPreviews)
}),
// SettingsSearchableItem(id: .notifications(9), title: strings.Notifications_ChannelNotificationsAlert, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_ChannelNotificationsAlert), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .channelAlerts)
// }),
// SettingsSearchableItem(id: .notifications(10), title: strings.Notifications_ChannelNotificationsPreview, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_ChannelNotificationsPreview), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications], present: { context, _, present in
// presentNotificationSettings(context, present, .channelPreviews)
// }),
SettingsSearchableItem(id: .notifications(11), title: strings.Notifications_ChannelNotificationsSound, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_ChannelNotificationsSound), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_ChannelNotifications], present: { context, _, present in
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: filteredGlobalSound(settings.channels.sound), defaultSound: nil, completion: { value in
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon-35.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,126 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 0.584314 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 6.000000 5.493408 cm
1.000000 1.000000 1.000000 scn
4.000000 6.005815 m
6.634244 6.005815 l
6.966668 6.005815 7.132880 6.005815 7.294138 5.988326 c
7.738360 5.940148 8.166211 5.793349 8.546436 5.558653 c
8.684463 5.473454 8.815662 5.371410 9.078063 5.167321 c
11.835391 3.022733 l
11.835403 3.022722 l
13.492669 1.733738 14.321304 1.089245 15.017017 1.095165 c
15.622235 1.100315 16.192566 1.379257 16.568199 1.853825 c
17.000000 2.399353 17.000000 3.449122 17.000000 5.548661 c
17.000000 14.462967 l
17.000000 16.562506 17.000000 17.612276 16.568199 18.157804 c
16.192566 18.632374 15.622235 18.911314 15.017017 18.916464 c
14.321305 18.922384 13.492673 18.277893 11.835412 16.988913 c
11.835398 16.988903 l
11.835389 16.988895 l
9.078062 14.844307 l
8.815662 14.640219 8.684463 14.538175 8.546436 14.452976 c
8.166211 14.218279 7.738360 14.071481 7.294138 14.023304 c
7.132880 14.005815 6.966668 14.005815 6.634244 14.005815 c
4.000000 14.005815 l
1.790861 14.005815 0.000000 12.214953 0.000000 10.005815 c
0.000000 7.796676 1.790861 6.005815 4.000000 6.005815 c
h
4.094136 4.100051 m
4.000000 3.898727 4.000000 3.639016 4.000000 3.119593 c
4.000000 2.005812 l
4.000000 0.901243 4.895431 0.005812 6.000000 0.005812 c
7.104569 0.005812 8.000000 0.901243 8.000000 2.005812 c
8.000000 3.119593 l
8.000000 3.639016 8.000000 3.898727 7.905864 4.100051 c
7.806588 4.312369 7.635887 4.483070 7.423568 4.582348 c
7.222244 4.676483 6.962533 4.676483 6.443110 4.676483 c
5.556890 4.676483 l
5.037467 4.676483 4.777756 4.676483 4.576432 4.582348 c
4.364113 4.483070 4.193412 4.312369 4.094136 4.100051 c
h
f*
n
Q
endstream
endobj
3 0 obj
2587
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Type /Catalog
/Pages 5 0 R
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002677 00000 n
0000002700 00000 n
0000002873 00000 n
0000002947 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3006
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon-34.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,136 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.203922 0.780392 0.349020 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 2.652100 8.000000 cm
1.000000 1.000000 1.000000 scn
15.347914 11.500000 m
15.347914 9.843145 14.004768 8.500000 12.347914 8.500000 c
10.691060 8.500000 9.347914 9.843145 9.347914 11.500000 c
9.347914 13.156855 10.691060 14.500000 12.347914 14.500000 c
14.004768 14.500000 15.347914 13.156855 15.347914 11.500000 c
h
16.206648 5.096860 m
17.131365 4.487454 17.732334 3.680024 18.122902 2.887923 c
18.464052 2.196046 18.353537 1.522406 17.970762 1.000000 c
17.531406 0.400373 16.733355 -0.000002 15.847914 -0.000002 c
8.847914 -0.000002 l
7.962472 -0.000002 7.164421 0.400373 6.725066 1.000000 c
6.701973 1.031517 6.679871 1.063584 6.658800 1.096172 c
6.330600 1.603753 6.252357 2.237787 6.572926 2.887924 c
6.987937 3.729597 7.640509 4.588578 8.666627 5.208897 c
9.571874 5.756145 10.767847 6.117645 12.347914 6.117645 c
13.927979 6.117645 15.123951 5.756145 16.029198 5.208899 c
16.089634 5.172363 16.148775 5.134999 16.206648 5.096860 c
h
5.347910 6.000000 m
6.038868 6.000000 6.650654 5.923974 7.192287 5.790671 c
6.346507 5.094717 5.770174 4.267296 5.380054 3.476105 c
4.957832 2.619810 4.950275 1.751801 5.228493 1.000000 c
2.054037 1.000000 l
0.595407 1.000000 -0.530585 2.298071 0.261390 3.522970 c
1.064612 4.765267 2.563349 6.000000 5.347910 6.000000 c
h
19.315775 3.476104 m
19.737995 2.619809 19.745552 1.751801 19.467335 1.000000 c
22.641785 1.000000 l
24.100414 1.000000 25.226404 2.298071 24.434431 3.522971 c
23.631208 4.765267 22.132471 6.000000 19.347910 6.000000 c
18.656954 6.000000 18.045172 5.923975 17.503540 5.790672 c
18.349321 5.094717 18.925655 4.267296 19.315775 3.476104 c
h
7.847914 10.500000 m
7.847914 9.119288 6.728626 8.000000 5.347914 8.000000 c
3.967202 8.000000 2.847914 9.119288 2.847914 10.500000 c
2.847914 11.880713 3.967202 13.000000 5.347914 13.000000 c
6.728626 13.000000 7.847914 11.880713 7.847914 10.500000 c
h
21.847914 10.500000 m
21.847914 9.119288 20.728626 8.000000 19.347914 8.000000 c
17.967201 8.000000 16.847914 9.119288 16.847914 10.500000 c
16.847914 11.880713 17.967201 13.000000 19.347914 13.000000 c
20.728626 13.000000 21.847914 11.880713 21.847914 10.500000 c
h
f*
n
Q
endstream
endobj
3 0 obj
3113
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Type /Catalog
/Pages 5 0 R
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003203 00000 n
0000003226 00000 n
0000003399 00000 n
0000003473 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3532
%%EOF

View File

@ -6356,7 +6356,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, callMessages: self.callMessages)
contentHeight += headerHeight
contentHeight += sectionSpacing
if !(self.isSettings && self.state.isEditing) {
contentHeight += sectionSpacing
}
for (sectionId, sectionItems) in items {
validRegularSections.append(sectionId)