Swiftgram/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift
2024-04-11 14:22:45 +04:00

416 lines
16 KiB
Swift

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<ReactionNotificationSettingsState>(value: ReactionNotificationSettingsState())
let statePromise: ValuePromise<ReactionNotificationSettingsState> = 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
}