import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData import TelegramUIPreferences import DeviceAccess import ItemListUI import PresentationDataUtils import AccountContext import AlertUI import PresentationDataUtils import TelegramNotices import NotificationSoundSelectionUI import TelegramStringFormatting private final class ReactionNotificationSettingsControllerArguments { let context: AccountContext let soundSelectionDisposable: MetaDisposable let openMessages: () -> Void let openStories: () -> Void let toggleMessages: (Bool) -> Void let toggleStories: (Bool) -> Void let updatePreviews: (Bool) -> Void let openSound: (PeerMessageSound) -> Void init( context: AccountContext, soundSelectionDisposable: MetaDisposable, openMessages: @escaping () -> Void, openStories: @escaping () -> Void, toggleMessages: @escaping (Bool) -> Void, toggleStories: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void ) { self.context = context self.soundSelectionDisposable = soundSelectionDisposable self.openMessages = openMessages self.openStories = openStories self.toggleMessages = toggleMessages self.toggleStories = toggleStories self.updatePreviews = updatePreviews self.openSound = openSound } } private enum ReactionNotificationSettingsSection: Int32 { case categories case options } private enum ReactionNotificationSettingsEntry: ItemListNodeEntry { enum StableId: Hashable { case categoriesHeader case messages case stories case optionsHeader case previews case sound } case categoriesHeader(String) case messages(title: String, text: String?, value: Bool) case stories(title: String, text: String?, value: Bool) case optionsHeader(String) case previews(String, Bool) case sound(String, String, PeerMessageSound) var section: ItemListSectionId { switch self { case .categoriesHeader, .messages, .stories: return ReactionNotificationSettingsSection.categories.rawValue case .optionsHeader, .previews, .sound: return ReactionNotificationSettingsSection.options.rawValue } } var stableId: StableId { switch self { case .categoriesHeader: return .categoriesHeader case .messages: return .messages case .stories: return .stories case .optionsHeader: return .optionsHeader case .previews: return .previews case .sound: return .sound } } var sortIndex: Int32 { switch self { case .categoriesHeader: return 0 case .messages: return 1 case .stories: return 2 case .optionsHeader: return 3 case .previews: return 4 case .sound: return 5 } } static func ==(lhs: ReactionNotificationSettingsEntry, rhs: ReactionNotificationSettingsEntry) -> Bool { switch lhs { case let .categoriesHeader(lhsText): if case let .categoriesHeader(rhsText) = rhs, lhsText == rhsText { return true } else { return false } case let .messages(title, text, value): if case .messages(title, text, value) = rhs { return true } else { return false } case let .stories(title, text, value): if case .stories(title, text, value) = rhs { return true } else { return false } case let .optionsHeader(lhsText): if case let .optionsHeader(rhsText) = rhs, lhsText == rhsText { return true } else { return false } case let .previews(lhsText, lhsValue): if case let .previews(rhsText, rhsValue) = rhs, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false } case let .sound(lhsText, lhsValue, lhsSound): if case let .sound(rhsText, rhsValue, rhsSound) = rhs, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound { return true } else { return false } } } static func <(lhs: ReactionNotificationSettingsEntry, rhs: ReactionNotificationSettingsEntry) -> Bool { return lhs.sortIndex < rhs.sortIndex } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ReactionNotificationSettingsControllerArguments switch self { case let .categoriesHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .messages(title, text, value): return ItemListSwitchItem(presentationData: presentationData, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleMessages(value) }, action: { arguments.openMessages() }) case let .stories(title, text, value): return ItemListSwitchItem(presentationData: presentationData, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleStories(value) }, action: { arguments.openStories() }) case let .optionsHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .previews(text, value): return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updatePreviews(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) } } } private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound { if case .default = sound { return defaultCloudPeerNotificationSound } else { return sound } } private func reactionNotificationSettingsEntries( globalSettings: GlobalNotificationSettingsSet, state: ReactionNotificationSettingsState, presentationData: PresentationData, notificationSoundList: NotificationSoundList? ) -> [ReactionNotificationSettingsEntry] { var entries: [ReactionNotificationSettingsEntry] = [] entries.append(.categoriesHeader(presentationData.strings.Notifications_Reactions_SettingsHeader)) let messagesText: String? let messagesValue: Bool switch globalSettings.reactionSettings.messages { case .nobody: messagesText = nil messagesValue = false case .contacts: messagesText = presentationData.strings.Notifications_Reactions_SubtitleContacts messagesValue = true case .everyone: messagesText = presentationData.strings.Notifications_Reactions_SubtitleEveryone messagesValue = true } let storiesText: String? let storiesValue: Bool switch globalSettings.reactionSettings.stories { case .nobody: storiesText = nil storiesValue = false case .contacts: storiesText = presentationData.strings.Notifications_Reactions_SubtitleContacts storiesValue = true case .everyone: storiesText = presentationData.strings.Notifications_Reactions_SubtitleEveryone storiesValue = true } entries.append(.messages(title: presentationData.strings.Notifications_Reactions_ItemMessages, text: messagesText, value: messagesValue)) entries.append(.stories(title: presentationData.strings.Notifications_Reactions_ItemStories, text: storiesText, value: storiesValue)) if messagesValue || storiesValue { entries.append(.optionsHeader(presentationData.strings.Notifications_Options.uppercased())) entries.append(.previews(presentationData.strings.Notifications_Stories_DisplayName, globalSettings.reactionSettings.hideSender != .hide)) entries.append(.sound(presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(globalSettings.reactionSettings.sound)), filteredGlobalSound(globalSettings.reactionSettings.sound))) } return entries } private struct ReactionNotificationSettingsState: Equatable { init() { } } public func reactionNotificationSettingsController( context: AccountContext ) -> ViewController { var presentControllerImpl: ((ViewController, Any?) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? let stateValue = Atomic(value: ReactionNotificationSettingsState()) let statePromise: ValuePromise = ValuePromise(ignoreRepeated: true) statePromise.set(stateValue.with { $0 }) let updateState: ((ReactionNotificationSettingsState) -> ReactionNotificationSettingsState) -> Void = { f in let result = stateValue.modify { f($0) } statePromise.set(result) } let _ = updateState let openCategory: (Bool) -> Void = { isMessages in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text: String if isMessages { text = presentationData.strings.Notifications_Reactions_SheetTitleMessages } else { text = presentationData.strings.Notifications_Reactions_SheetTitleStories } let actionSheet = ActionSheetController(presentationData: presentationData) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ ActionSheetTextItem(title: text), ActionSheetButtonItem(title: presentationData.strings.Notifications_Reactions_SheetValueEveryone, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings if isMessages { settings.reactionSettings.messages = .everyone } else { settings.reactionSettings.stories = .everyone } return settings }).start() }), ActionSheetButtonItem(title: presentationData.strings.Notifications_Reactions_SheetValueContacts, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings if isMessages { settings.reactionSettings.messages = .contacts } else { settings.reactionSettings.stories = .contacts } return settings }).start() }) ]), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in actionSheet?.dismissAnimated() }) ])]) presentControllerImpl?(actionSheet, nil) } let arguments = ReactionNotificationSettingsControllerArguments( context: context, soundSelectionDisposable: MetaDisposable(), openMessages: { openCategory(true) }, openStories: { openCategory(false) }, toggleMessages: { value in let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings if value { settings.reactionSettings.messages = .contacts } else { settings.reactionSettings.messages = .nobody } return settings }).start() }, toggleStories: { value in let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings if value { settings.reactionSettings.stories = .contacts } else { settings.reactionSettings.stories = .nobody } return settings }).start() }, updatePreviews: { value in let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings settings.reactionSettings.hideSender = value ? .show : .hide 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 settings.reactionSettings.sound = value return settings }).start() }) pushControllerImpl?(controller) } ) let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), preferences, statePromise.get() ) |> map { presentationData, notificationSoundList, preferencesView, state -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet if let settings = preferencesView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings } let entries = reactionNotificationSettingsEntries( globalSettings: viewSettings, state: state, presentationData: presentationData, notificationSoundList: notificationSoundList ) let leftNavigationButton: ItemListNavigationButton? let rightNavigationButton: ItemListNavigationButton? leftNavigationButton = nil rightNavigationButton = nil let title: String = presentationData.strings.Notifications_Reactions_Title let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks) 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 }