mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Various settings UI improvements
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
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 ItemListPeerItem
|
||||
import ItemListPeerActionItem
|
||||
|
||||
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 addException: () -> Void
|
||||
let openException: (Peer) -> Void
|
||||
let removeAllExceptions: () -> Void
|
||||
let updateRevealedPeerId: (PeerId?) -> Void
|
||||
let removePeer: (Peer) -> Void
|
||||
|
||||
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) {
|
||||
self.context = context
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.soundSelectionDisposable = soundSelectionDisposable
|
||||
|
||||
self.updateEnabled = updateEnabled
|
||||
self.updatePreviews = updatePreviews
|
||||
self.updateSound = updateSound
|
||||
|
||||
self.addException = addException
|
||||
self.openException = openException
|
||||
self.removeAllExceptions = removeAllExceptions
|
||||
|
||||
self.updateRevealedPeerId = updateRevealedPeerId
|
||||
self.removePeer = removePeer
|
||||
|
||||
self.updatedExceptionMode = updatedExceptionMode
|
||||
}
|
||||
}
|
||||
|
||||
private enum NotificationsPeerCategorySection: Int32 {
|
||||
case enable
|
||||
case options
|
||||
case exceptions
|
||||
}
|
||||
|
||||
public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
||||
case enable
|
||||
case previews
|
||||
case sound
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? NotificationsPeerCategoryEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
case enable(PresentationTheme, String, Bool)
|
||||
case optionsHeader(PresentationTheme, String)
|
||||
case previews(PresentationTheme, String, Bool)
|
||||
case sound(PresentationTheme, String, String, PeerMessageSound)
|
||||
|
||||
case exceptionsHeader(PresentationTheme, String)
|
||||
case addException(PresentationTheme, String)
|
||||
case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, TelegramPeerNotificationSettings, Bool, Bool)
|
||||
case removeAllExceptions(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .enable:
|
||||
return NotificationsPeerCategorySection.enable.rawValue
|
||||
case .optionsHeader, .previews, .sound:
|
||||
return NotificationsPeerCategorySection.options.rawValue
|
||||
case .exceptionsHeader, .addException, .exception, .removeAllExceptions:
|
||||
return NotificationsPeerCategorySection.exceptions.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .enable:
|
||||
return 0
|
||||
case .optionsHeader:
|
||||
return 1
|
||||
case .previews:
|
||||
return 2
|
||||
case .sound:
|
||||
return 3
|
||||
case .exceptionsHeader:
|
||||
return 4
|
||||
case .addException:
|
||||
return 5
|
||||
case let .exception(index, _, _, _, _, _, _, _, _):
|
||||
return 6 + index
|
||||
case .removeAllExceptions:
|
||||
return 100000
|
||||
}
|
||||
}
|
||||
|
||||
var tag: ItemListItemTag? {
|
||||
switch self {
|
||||
case .enable:
|
||||
return NotificationsPeerCategoryEntryTag.enable
|
||||
case .previews:
|
||||
return NotificationsPeerCategoryEntryTag.previews
|
||||
case .sound:
|
||||
return NotificationsPeerCategoryEntryTag.sound
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .enable(lhsTheme, lhsText, lhsValue):
|
||||
if case let .enable(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .optionsHeader(lhsTheme, lhsText):
|
||||
if case let .optionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .previews(lhsTheme, lhsText, lhsValue):
|
||||
if case let .previews(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sound(lhsTheme, lhsText, lhsValue, lhsSound):
|
||||
if case let .sound(rhsTheme, rhsText, rhsValue, rhsSound) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsSound == rhsSound {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .exceptionsHeader(lhsTheme, lhsText):
|
||||
if case let .exceptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .addException(lhsTheme, lhsText):
|
||||
if case let .addException(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} 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 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .removeAllExceptions(lhsTheme, lhsText):
|
||||
if case let .removeAllExceptions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! NotificationsPeerCategoryControllerArguments
|
||||
switch self {
|
||||
case let .enable(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateEnabled(updatedValue)
|
||||
}, tag: self.tag)
|
||||
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, _):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
|
||||
|
||||
}, tag: self.tag)
|
||||
case let .exceptionsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .addException(theme, text):
|
||||
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: {
|
||||
arguments.openException(peer)
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
arguments.updateRevealedPeerId(peerId)
|
||||
}, removePeer: { peerId in
|
||||
arguments.removePeer(peer)
|
||||
}, hasTopStripe: false, hasTopGroupInset: false, noInsets: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound {
|
||||
if case .default = sound {
|
||||
return .bundledModern(id: 0)
|
||||
} else {
|
||||
return sound
|
||||
}
|
||||
}
|
||||
|
||||
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, exceptions: (users: NotificationExceptionMode, groups: NotificationExceptionMode, channels: NotificationExceptionMode), presentationData: PresentationData) -> [NotificationsPeerCategoryEntry] {
|
||||
var entries: [NotificationsPeerCategoryEntry] = []
|
||||
|
||||
let notificationSettings: MessageNotificationSettings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
notificationSettings = globalSettings.privateChats
|
||||
case .group:
|
||||
notificationSettings = globalSettings.groupChats
|
||||
case .channel:
|
||||
notificationSettings = globalSettings.channels
|
||||
}
|
||||
|
||||
entries.append(.enable(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsAlert, notificationSettings.enabled))
|
||||
|
||||
if notificationSettings.enabled || !exceptions.users.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()))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public enum NotificationsPeerCategory {
|
||||
case privateChat
|
||||
case group
|
||||
case channel
|
||||
}
|
||||
|
||||
public func notificationsPeerCategoryController(context: AccountContext, category: NotificationsPeerCategory, exceptionsList: NotificationExceptionsList?, focusOnItemTag: NotificationsPeerCategoryEntryTag? = nil) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
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 _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
settings.privateChats.enabled = value
|
||||
case .group:
|
||||
settings.groupChats.enabled = value
|
||||
case .channel:
|
||||
settings.channels.enabled = value
|
||||
}
|
||||
return settings
|
||||
}).start()
|
||||
}, updatePreviews: { value in
|
||||
let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
switch category {
|
||||
case .privateChat:
|
||||
settings.privateChats.displayPreviews = value
|
||||
case .group:
|
||||
settings.groupChats.displayPreviews = value
|
||||
case .channel:
|
||||
settings.channels.displayPreviews = value
|
||||
}
|
||||
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()
|
||||
}, addException: {
|
||||
|
||||
}, openException: { peer in
|
||||
|
||||
}, removeAllExceptions: {
|
||||
|
||||
}, updateRevealedPeerId: { peerId in
|
||||
|
||||
}, removePeer: { peer in
|
||||
|
||||
}, 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))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
Reference in New Issue
Block a user