import Foundation import UIKit import Display import SwiftSignalKit import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences import DeviceAccess import ItemListUI import PresentationDataUtils import AccountContext import AlertUI import PresentationDataUtils import TelegramNotices import NotificationSoundSelectionUI import TelegramStringFormatting import NotificationPeerExceptionController private struct CounterTagSettings: OptionSet { var rawValue: Int32 init(rawValue: Int32) { self.rawValue = rawValue } init(summaryTags: PeerSummaryCounterTags) { var result = CounterTagSettings() if summaryTags.contains(.contact) { result.insert(.regularChatsAndGroups) } if summaryTags.contains(.channel) { result.insert(.channels) } self = result } func toSumaryTags() -> PeerSummaryCounterTags { var result = PeerSummaryCounterTags() if self.contains(.regularChatsAndGroups) { result.insert(.contact) result.insert(.nonContact) result.insert(.bot) result.insert(.group) } if self.contains(.channels) { result.insert(.channel) } return result } static let regularChatsAndGroups = CounterTagSettings(rawValue: 1 << 0) static let channels = CounterTagSettings(rawValue: 1 << 1) } private final class NotificationsAndSoundsArguments { let context: AccountContext let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void let pushController: (ViewController) -> Void let soundSelectionDisposable: MetaDisposable let authorizeNotifications: () -> Void let suppressWarning: () -> Void let openPeerCategory: (NotificationsPeerCategory) -> Void let updateInAppSounds: (Bool) -> Void let updateInAppVibration: (Bool) -> Void let updateInAppPreviews: (Bool) -> Void let updateDisplayNameOnLockscreen: (Bool) -> Void let updateIncludeTag: (CounterTagSettings, Bool) -> Void let updateTotalUnreadCountCategory: (Bool) -> Void let updateJoinedNotifications: (Bool) -> Void let resetNotifications: () -> 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, 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.openPeerCategory = openPeerCategory self.updateInAppSounds = updateInAppSounds self.updateInAppVibration = updateInAppVibration self.updateInAppPreviews = updateInAppPreviews self.updateDisplayNameOnLockscreen = updateDisplayNameOnLockscreen self.updateIncludeTag = updateIncludeTag self.updateTotalUnreadCountCategory = updateTotalUnreadCountCategory self.resetNotifications = resetNotifications self.openAppSettings = openAppSettings self.updateJoinedNotifications = updateJoinedNotifications self.updateNotificationsFromAllAccounts = updateNotificationsFromAllAccounts } } private enum NotificationsAndSoundsSection: Int32 { case accounts case permission case categories case inApp case displayNamesOnLockscreen case badge case joinedNotifications case reset } public enum NotificationsAndSoundsEntryTag: ItemListItemTag { case allAccounts case inAppSounds case inAppVibrate case inAppPreviews case displayNamesOnLockscreen case includeChannels case unreadCountCategory case joinedNotifications case reset public func isEqual(to other: ItemListItemTag) -> Bool { if let other = other as? NotificationsAndSoundsEntryTag, self == other { return true } else { return false } } } private enum NotificationsAndSoundsEntry: ItemListNodeEntry { case accountsHeader(PresentationTheme, String) case allAccounts(PresentationTheme, String, Bool) case accountsInfo(PresentationTheme, String) case permissionInfo(PresentationTheme, String, String, Bool) case permissionEnable(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 stories(PresentationTheme, String, String, String) case inAppHeader(PresentationTheme, String) case inAppSounds(PresentationTheme, String, Bool) case inAppVibrate(PresentationTheme, String, Bool) case inAppPreviews(PresentationTheme, String, Bool) case displayNamesOnLockscreen(PresentationTheme, String, Bool) case displayNamesOnLockscreenInfo(PresentationTheme, String) case badgeHeader(PresentationTheme, String) case includeChannels(PresentationTheme, String, Bool) case unreadCountCategory(PresentationTheme, String, Bool) case unreadCountCategoryInfo(PresentationTheme, String) case joinedNotifications(PresentationTheme, String, Bool) case joinedNotificationsInfo(PresentationTheme, String) case reset(PresentationTheme, String) case resetNotice(PresentationTheme, String) var section: ItemListSectionId { switch self { case .accountsHeader, .allAccounts, .accountsInfo: return NotificationsAndSoundsSection.accounts.rawValue case .permissionInfo, .permissionEnable: return NotificationsAndSoundsSection.permission.rawValue case .categoriesHeader, .privateChats, .groupChats, .channels, .stories: return NotificationsAndSoundsSection.categories.rawValue case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews: return NotificationsAndSoundsSection.inApp.rawValue case .displayNamesOnLockscreen, .displayNamesOnLockscreenInfo: return NotificationsAndSoundsSection.displayNamesOnLockscreen.rawValue case .badgeHeader, .includeChannels, .unreadCountCategory, .unreadCountCategoryInfo: return NotificationsAndSoundsSection.badge.rawValue case .joinedNotifications, .joinedNotificationsInfo: return NotificationsAndSoundsSection.joinedNotifications.rawValue case .reset, .resetNotice: return NotificationsAndSoundsSection.reset.rawValue } } var stableId: Int32 { switch self { case .accountsHeader: return 0 case .allAccounts: return 1 case .accountsInfo: return 2 case .permissionInfo: return 3 case .permissionEnable: return 4 case .categoriesHeader: return 5 case .privateChats: return 6 case .groupChats: return 7 case .channels: return 8 case .stories: return 9 case .inAppHeader: return 14 case .inAppSounds: return 15 case .inAppVibrate: return 16 case .inAppPreviews: return 17 case .displayNamesOnLockscreen: return 18 case .displayNamesOnLockscreenInfo: return 19 case .badgeHeader: return 20 case .includeChannels: return 21 case .unreadCountCategory: return 22 case .unreadCountCategoryInfo: return 23 case .joinedNotifications: return 24 case .joinedNotificationsInfo: return 25 case .reset: return 26 case .resetNotice: return 27 } } var tag: ItemListItemTag? { switch self { case .allAccounts: return NotificationsAndSoundsEntryTag.allAccounts case .inAppSounds: return NotificationsAndSoundsEntryTag.inAppSounds case .inAppVibrate: return NotificationsAndSoundsEntryTag.inAppVibrate case .inAppPreviews: return NotificationsAndSoundsEntryTag.inAppPreviews case .displayNamesOnLockscreen: return NotificationsAndSoundsEntryTag.displayNamesOnLockscreen case .includeChannels: return NotificationsAndSoundsEntryTag.includeChannels case .unreadCountCategory: return NotificationsAndSoundsEntryTag.unreadCountCategory case .joinedNotifications: return NotificationsAndSoundsEntryTag.joinedNotifications case .reset: return NotificationsAndSoundsEntryTag.reset default: return nil } } static func ==(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool { switch lhs { case let .accountsHeader(lhsTheme, lhsText): if case let .accountsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .allAccounts(lhsTheme, lhsText, lhsValue): if case let .allAccounts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .accountsInfo(lhsTheme, lhsText): if case let .accountsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .permissionInfo(lhsTheme, lhsTitle, lhsText, lhsSuppressed): if case let .permissionInfo(rhsTheme, rhsTitle, rhsText, rhsSuppressed) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsSuppressed == rhsSuppressed { return true } else { return false } case let .permissionEnable(lhsTheme, lhsText): if case let .permissionEnable(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .categoriesHeader(lhsTheme, lhsText): if case let .categoriesHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } 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 .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 .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 } case let .stories(lhsTheme, lhsTitle, lhsSubtitle, lhsLabel): if case let .stories(rhsTheme, rhsTitle, rhsSubtitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel { return true } else { return false } case let .inAppHeader(lhsTheme, lhsText): if case let .inAppHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .inAppSounds(lhsTheme, lhsText, lhsValue): if case let .inAppSounds(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .inAppVibrate(lhsTheme, lhsText, lhsValue): if case let .inAppVibrate(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .inAppPreviews(lhsTheme, lhsText, lhsValue): if case let .inAppPreviews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .displayNamesOnLockscreen(lhsTheme, lhsText, lhsValue): if case let .displayNamesOnLockscreen(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .displayNamesOnLockscreenInfo(lhsTheme, lhsText): if case let .displayNamesOnLockscreenInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .badgeHeader(lhsTheme, lhsText): if case let .badgeHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .includeChannels(lhsTheme, lhsText, lhsValue): if case let .includeChannels(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .unreadCountCategory(lhsTheme, lhsText, lhsValue): if case let .unreadCountCategory(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .unreadCountCategoryInfo(lhsTheme, lhsText): if case let .unreadCountCategoryInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .joinedNotifications(lhsTheme, lhsText, lhsValue): if case let .joinedNotifications(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .joinedNotificationsInfo(lhsTheme, lhsText): if case let .joinedNotificationsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .reset(lhsTheme, lhsText): if case let .reset(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } case let .resetNotice(lhsTheme, lhsText): if case let .resetNotice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } } } static func <(lhs: NotificationsAndSoundsEntry, rhs: NotificationsAndSoundsEntry) -> Bool { return lhs.stableId < rhs.stableId } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! NotificationsAndSoundsArguments switch self { case let .accountsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .allAccounts(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateNotificationsFromAllAccounts(updatedValue) }, tag: self.tag) case let .accountsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .permissionInfo(_, title, text, suppressed): return ItemListInfoItem(presentationData: presentationData, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: suppressed ? nil : { arguments.suppressWarning() }) case let .permissionEnable(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.authorizeNotifications() }) case let .categoriesHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) 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 .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 .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 .stories(_, title, subtitle, label): return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Stories"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openPeerCategory(.stories) }) case let .inAppHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .inAppSounds(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppSounds(updatedValue) }, tag: self.tag) case let .inAppVibrate(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppVibration(updatedValue) }, tag: self.tag) case let .inAppPreviews(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppPreviews(updatedValue) }, tag: self.tag) case let .displayNamesOnLockscreen(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateDisplayNameOnLockscreen(updatedValue) }, tag: self.tag) case let .displayNamesOnLockscreenInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text.replacingOccurrences(of: "]", with: "]()")), sectionId: self.section, linkAction: { _ in arguments.openAppSettings() }) case let .badgeHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .includeChannels(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateIncludeTag(.channels, updatedValue) }, tag: self.tag) case let .unreadCountCategory(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateTotalUnreadCountCategory(updatedValue) }, tag: self.tag) case let .unreadCountCategoryInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .joinedNotifications(_, text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateJoinedNotifications(updatedValue) }, tag: self.tag) case let .joinedNotificationsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .reset(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.resetNotifications() }, tag: self.tag) case let .resetNotice(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound { if case .default = sound { return defaultCloudPeerNotificationSound } else { return sound } } private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warningSuppressed: Bool, globalSettings: GlobalNotificationSettingsSet, inAppSettings: InAppNotificationSettings, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode, stories: NotificationExceptionMode), presentationData: PresentationData, hasMoreThanOneAccount: Bool) -> [NotificationsAndSoundsEntry] { var entries: [NotificationsAndSoundsEntry] = [] if hasMoreThanOneAccount { entries.append(.accountsHeader(presentationData.theme, presentationData.strings.NotificationSettings_ShowNotificationsFromAccountsSection)) entries.append(.allAccounts(presentationData.theme, presentationData.strings.NotificationSettings_ShowNotificationsAllAccounts, inAppSettings.displayNotificationsFromAllAccounts)) entries.append(.accountsInfo(presentationData.theme, inAppSettings.displayNotificationsFromAllAccounts ? presentationData.strings.NotificationSettings_ShowNotificationsAllAccountsInfoOn : presentationData.strings.NotificationSettings_ShowNotificationsAllAccountsInfoOff)) } if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { let title: String let text: String if case .unreachable = authorizationStatus { title = presentationData.strings.Notifications_PermissionsUnreachableTitle text = presentationData.strings.Notifications_PermissionsUnreachableText } else { title = presentationData.strings.Notifications_PermissionsTitle text = presentationData.strings.Notifications_PermissionsText } switch (authorizationStatus, warningSuppressed) { case (.denied, _): entries.append(.permissionInfo(presentationData.theme, title, text, true)) entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Notifications_PermissionsAllowInSettings)) case (.unreachable, false): entries.append(.permissionInfo(presentationData.theme, title, text, false)) entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Notifications_PermissionsOpenSettings)) case (.notDetermined, _): entries.append(.permissionInfo(presentationData.theme, title, text, true)) entries.append(.permissionEnable(presentationData.theme, presentationData.strings.Notifications_PermissionsAllow)) default: break } } 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)) //TODO:localize entries.append(.stories(presentationData.theme, "Stories", !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", globalSettings.privateChats.storiesMuted != true ? presentationData.strings.Notifications_On : presentationData.strings.Notifications_Off)) entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased())) entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds)) entries.append(.inAppVibrate(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsVibrate, inAppSettings.vibrate)) entries.append(.inAppPreviews(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsPreview, inAppSettings.displayPreviews)) entries.append(.displayNamesOnLockscreen(presentationData.theme, presentationData.strings.Notifications_DisplayNamesOnLockScreen, inAppSettings.displayNameOnLockscreen)) entries.append(.displayNamesOnLockscreenInfo(presentationData.theme, presentationData.strings.Notifications_DisplayNamesOnLockScreenInfoWithLink)) entries.append(.badgeHeader(presentationData.theme, presentationData.strings.Notifications_Badge.uppercased())) let counterTagSettings = CounterTagSettings(summaryTags: inAppSettings.totalUnreadCountIncludeTags) entries.append(.includeChannels(presentationData.theme, presentationData.strings.Notifications_Badge_IncludeChannels, counterTagSettings.contains(.channels))) entries.append(.unreadCountCategory(presentationData.theme, presentationData.strings.Notifications_Badge_CountUnreadMessages, inAppSettings.totalUnreadCountDisplayCategory == .messages)) entries.append(.unreadCountCategoryInfo(presentationData.theme, inAppSettings.totalUnreadCountDisplayCategory == .chats ? presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOff : presentationData.strings.Notifications_Badge_CountUnreadMessages_InfoOn)) entries.append(.joinedNotifications(presentationData.theme, presentationData.strings.NotificationSettings_ContactJoined, globalSettings.contactsJoined)) entries.append(.joinedNotificationsInfo(presentationData.theme, presentationData.strings.NotificationSettings_ContactJoinedInfo)) entries.append(.reset(presentationData.theme, presentationData.strings.Notifications_ResetAllNotifications)) entries.append(.resetNotice(presentationData.theme, presentationData.strings.Notifications_ResetAllNotificationsHelp)) return entries } public func notificationsAndSoundsController(context: AccountContext, exceptionsList: NotificationExceptionsList?, focusOnItemTag: NotificationsAndSoundsEntryTag? = nil) -> ViewController { var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? let notificationExceptions: Promise<(users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode, stories: NotificationExceptionMode)> = Promise() let updateNotificationExceptions:((users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode, stories: NotificationExceptionMode)) -> Void = { value in notificationExceptions.set(.single(value)) } let arguments = NotificationsAndSoundsArguments(context: context, presentController: { controller, arguments in presentControllerImpl?(controller, arguments) }, pushController: { controller in pushControllerImpl?(controller) }, soundSelectionDisposable: MetaDisposable(), authorizeNotifications: { let _ = (DeviceAccess.authorizationStatus(applicationInForeground: context.sharedContext.applicationBindings.applicationInForeground, subject: .notifications) |> take(1) |> deliverOnMainQueue).start(next: { status in switch status { case .notDetermined: DeviceAccess.authorizeAccess(to: .notifications, registerForNotifications: { result in context.sharedContext.applicationBindings.registerForNotifications(result) }) case .denied, .restricted: context.sharedContext.applicationBindings.openSettings() case .unreachable: ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .notifications, value: Int32(Date().timeIntervalSince1970)) context.sharedContext.applicationBindings.openSettings() default: break } }) }, suppressWarning: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Notifications_PermissionsSuppressWarningTitle, text: presentationData.strings.Notifications_PermissionsSuppressWarningText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Notifications_PermissionsKeepDisabled, action: { ApplicationSpecificNotice.setPermissionWarning(accountManager: context.sharedContext.accountManager, permission: .notifications, value: Int32(Date().timeIntervalSince1970)) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Notifications_PermissionsEnable, action: { context.sharedContext.applicationBindings.openSettings() })]), nil) }, openPeerCategory: { category in _ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in let mode: NotificationExceptionMode switch category { case .privateChat: mode = users case .group: mode = groups case .channel: mode = channels case .stories: mode = stories } pushControllerImpl?(notificationsPeerCategoryController(context: context, category: category, mode: mode, updatedMode: { mode in _ = (notificationExceptions.get() |> take(1) |> deliverOnMainQueue).start(next: { (users, groups, channels, stories) in switch mode { case .users: updateNotificationExceptions((mode, groups, channels, stories)) case .groups: updateNotificationExceptions((users, mode, channels, stories)) case .channels: updateNotificationExceptions((users, groups, mode, stories)) case .stories: updateNotificationExceptions((users, groups, channels, mode)) } }) }, focusOnItemTag: nil)) }) }, updateInAppSounds: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.playSounds = value return settings }).start() }, updateInAppVibration: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.vibrate = value return settings }).start() }, updateInAppPreviews: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.displayPreviews = value return settings }).start() }, updateDisplayNameOnLockscreen: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.displayNameOnLockscreen = value return settings }).start() }, updateIncludeTag: { tag, value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var currentSettings = CounterTagSettings(summaryTags: settings.totalUnreadCountIncludeTags) if !value { currentSettings.remove(tag) } else { currentSettings.insert(tag) } var settings = settings settings.totalUnreadCountIncludeTags = currentSettings.toSumaryTags() return settings }).start() }, updateTotalUnreadCountCategory: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.totalUnreadCountDisplayCategory = value ? .messages : .chats return settings }).start() }, resetNotifications: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: presentationData.strings.Notifications_ResetAllNotificationsText), ActionSheetButtonItem(title: presentationData.strings.Notifications_Reset, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() let modifyPeers = context.engine.peers.resetAllPeerNotificationSettings() let updateGlobal = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { _ in return GlobalNotificationSettingsSet.defaultSettings }) let reset = resetPeerNotificationSettings(network: context.account.network) let signal = combineLatest(modifyPeers, updateGlobal, reset) let _ = signal.start() }) ]), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) presentControllerImpl?(actionSheet, nil) }, openAppSettings: { context.sharedContext.applicationBindings.openSettings() }, updateJoinedNotifications: { value in let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings settings.contactsJoined = value return settings }).start() }, updateNotificationsFromAllAccounts: { value in let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in var settings = settings settings.displayNotificationsFromAllAccounts = value return settings }).start() }) let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) let exceptionsSignal = Signal.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init)) notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in var users:[PeerId : NotificationExceptionWrapper] = [:] var groups: [PeerId : NotificationExceptionWrapper] = [:] var channels: [PeerId : NotificationExceptionWrapper] = [:] var stories: [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 { if value.storiesMuted != nil { stories[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } 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: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } default: switch key.namespace { case Namespaces.Peer.CloudUser: users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } } } } return (.users(users), .groups(groups), .channels(channels), .stories(stories)) }) let notificationsWarningSuppressed = Promise(true) if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { notificationsWarningSuppressed.set(.single(true) |> then( context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .notifications)!) |> map { noticeView -> Bool in let timestamp = noticeView.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) }) if let timestamp = timestamp, timestamp > 0 { return true } else { return false } })) } let hasMoreThanOneAccount = context.sharedContext.activeAccountContexts |> map { _, contexts, _ -> Bool in return contexts.count > 1 } |> distinctUntilChanged let signal = combineLatest(context.sharedContext.presentationData, sharedData, preferences, notificationExceptions.get(), DeviceAccess.authorizationStatus(applicationInForeground: context.sharedContext.applicationBindings.applicationInForeground, subject: .notifications), notificationsWarningSuppressed.get(), hasMoreThanOneAccount) |> map { presentationData, sharedData, view, exceptions, authorizationStatus, warningSuppressed, hasMoreThanOneAccount -> (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 inAppSettings: InAppNotificationSettings if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings]?.get(InAppNotificationSettings.self) { inAppSettings = settings } else { inAppSettings = InAppNotificationSettings.defaultSettings } let entries = notificationsAndSoundsEntries(authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, globalSettings: viewSettings, inAppSettings: inAppSettings, exceptions: exceptions, presentationData: presentationData, hasMoreThanOneAccount: hasMoreThanOneAccount) 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 controller = ItemListController(context: context, state: signal) presentControllerImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) } pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) } return controller }