[WIP] API update

This commit is contained in:
Ali 2022-10-17 17:16:50 +04:00
parent 79799804f1
commit df35dcf3c4
8 changed files with 617 additions and 510 deletions

View File

@ -536,17 +536,44 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si
return signal
}
func _internal_forumChannelTopicNotificationExceptions(account: Account, id: EnginePeer.Id) -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(id).flatMap(apiInputPeer)
public extension EngineMessageHistoryThread {
struct NotificationException: Equatable {
public var threadId: Int64
public var info: EngineMessageHistoryThread.Info
public var notificationSettings: EnginePeer.NotificationSettings
public init(
threadId: Int64,
info: EngineMessageHistoryThread.Info,
notificationSettings: EnginePeer.NotificationSettings
) {
self.threadId = threadId
self.info = info
self.notificationSettings = notificationSettings
}
}
|> mapToSignal { inputPeer -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> in
guard let inputPeer = inputPeer else {
}
func _internal_forumChannelTopicNotificationExceptions(account: Account, id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> {
return account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(id)
}
|> mapToSignal { peer -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> in
guard let inputPeer = peer.flatMap(apiInputPeer), let inputChannel = peer.flatMap(apiInputChannel) else {
return .single([])
}
return account.network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 0, peer: Api.InputNotifyPeer.inputNotifyPeer(peer: inputPeer)))
|> map { result -> [(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)] in
var list: [(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)] = []
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
}
|> map { result -> [(threadId: Int64, notificationSettings: EnginePeer.NotificationSettings)] in
guard let result = result else {
return []
}
var list: [(threadId: Int64, notificationSettings: EnginePeer.NotificationSettings)] = []
for update in result.allUpdates {
switch update {
case let .updateNotifySettings(peer, notifySettings):
@ -562,8 +589,34 @@ func _internal_forumChannelTopicNotificationExceptions(account: Account, id: Eng
}
return list
}
|> `catch` { _ -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> in
return .single([])
|> mapToSignal { list -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> in
return account.network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: list.map { Int32(clamping: $0.threadId) }))
|> map { result -> [EngineMessageHistoryThread.NotificationException] in
var infoMapping: [Int64: EngineMessageHistoryThread.Info] = [:]
switch result {
case let .forumTopics(_, _, topics, _, _, _, _):
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconColor, iconEmojiId, _, _, _, _, _, _, _, _, _):
infoMapping[Int64(id)] = EngineMessageHistoryThread.Info(title: title, icon: iconEmojiId, iconColor: iconColor)
case .forumTopicDeleted:
break
}
}
}
return list.compactMap { item -> EngineMessageHistoryThread.NotificationException? in
if let info = infoMapping[item.threadId] {
return EngineMessageHistoryThread.NotificationException(threadId: item.threadId, info: info, notificationSettings: item.notificationSettings)
} else {
return nil
}
}
}
|> `catch` { _ -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> in
return .single([])
}
}
}
}

View File

@ -840,7 +840,7 @@ public extension TelegramEngine {
return _internal_setForumChannelTopicPinned(account: self.account, id: id, threadId: threadId, isPinned: isPinned)
}
public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> {
public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> {
return _internal_forumChannelTopicNotificationExceptions(account: self.account, id: id)
}
}

View File

@ -297,7 +297,7 @@ swift_library(
"//submodules/Media/LocalAudioTranscription:LocalAudioTranscription",
"//submodules/Components/PagerComponent:PagerComponent",
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen",
"//submodules/TelegramUI/Components/NotificationExceptionsScreen:NotificationExceptionsScreen",
"//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen",
"//submodules/TelegramUI/Components/ChatTitleView",
"//submodules/InviteLinksUI:InviteLinksUI",

View File

@ -1,494 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import AnimationCache
import MultiAnimationRenderer
import ComponentDisplayAdapters
import TelegramPresentationData
import AccountContext
import Postbox
import TelegramCore
private final class ForumTopicListItemComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let item: ForumChannelTopics.Item
let action: () -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
item: ForumChannelTopics.Item,
action: @escaping () -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.item = item
self.action = action
}
static func ==(lhs: ForumTopicListItemComponent, rhs: ForumTopicListItemComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.item != rhs.item {
return false
}
return true
}
final class View: HighlightTrackingButton {
private var highlightedBackgroundLayer: SimpleLayer?
private let title: ComponentView<Empty>
private var component: ForumTopicListItemComponent?
override init(frame: CGRect) {
self.title = ComponentView()
super.init(frame: frame)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.highligthedChanged = { [weak self] highlighted in
if let self, let component = self.component {
if highlighted {
if let superview = self.superview {
superview.bringSubviewToFront(self)
}
let highlightedBackgroundLayer: SimpleLayer
if let current = self.highlightedBackgroundLayer {
highlightedBackgroundLayer = current
} else {
highlightedBackgroundLayer = SimpleLayer()
self.highlightedBackgroundLayer = highlightedBackgroundLayer
highlightedBackgroundLayer.backgroundColor = component.theme.list.itemHighlightedBackgroundColor.cgColor
highlightedBackgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: self.bounds.width, height: self.bounds.height + UIScreenPixel))
self.layer.insertSublayer(highlightedBackgroundLayer, at: 0)
}
highlightedBackgroundLayer.removeAllAnimations()
highlightedBackgroundLayer.opacity = 1.0
} else {
if let highlightedBackgroundLayer = self.highlightedBackgroundLayer {
self.highlightedBackgroundLayer = nil
highlightedBackgroundLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak highlightedBackgroundLayer] _ in
highlightedBackgroundLayer?.removeFromSuperlayer()
})
}
}
}
}
}
required init?(coder: NSCoder) {
preconditionFailure()
}
@objc private func pressed() {
self.component?.action()
}
func update(component: ForumTopicListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(Text(
text: component.item.info.title,
font: Font.regular(17.0),
color: component.theme.list.itemPrimaryTextColor
)),
environment: {},
containerSize: availableSize
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
titleView.isUserInteractionEnabled = false
}
transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: 11.0, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize))
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class ForumTopicListComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let items: [ForumChannelTopics.Item]
let navigationHeight: CGFloat
let action: (ForumChannelTopics.Item) -> Void
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
items: [ForumChannelTopics.Item],
navigationHeight: CGFloat,
action: @escaping (ForumChannelTopics.Item) -> Void
) {
self.context = context
self.theme = theme
self.strings = strings
self.items = items
self.navigationHeight = navigationHeight
self.action = action
}
static func ==(lhs: ForumTopicListComponent, rhs: ForumTopicListComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.items != rhs.items {
return false
}
if lhs.navigationHeight != rhs.navigationHeight {
return false
}
return true
}
final class View: UIView, UIScrollViewDelegate {
private struct ItemLayout {
let containerSize: CGSize
let itemHeight: CGFloat
let contentSize: CGSize
let itemsInsets: UIEdgeInsets
init(containerSize: CGSize, navigationHeight: CGFloat, itemCount: Int) {
self.itemHeight = 44.0
self.containerSize = containerSize
self.itemsInsets = UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
self.contentSize = CGSize(width: containerSize.width, height: self.itemsInsets.top + self.itemsInsets.bottom + CGFloat(itemCount) * self.itemHeight)
}
func frame(at index: Int) -> CGRect {
return CGRect(origin: CGPoint(x: 0.0, y: self.itemsInsets.top + CGFloat(index) * self.itemHeight), size: CGSize(width: self.containerSize.width, height: self.itemHeight))
}
}
private final class ItemView {
let host: ComponentView<Empty>
let separatorLayer: SimpleLayer
init() {
self.host = ComponentView()
self.separatorLayer = SimpleLayer()
}
}
private let scrollView: UIScrollView
private var component: ForumTopicListComponent?
private var itemLayout: ItemLayout?
private var ignoreScrolling: Bool = false
private var visibleItemViews: [Int64: ItemView] = [:]
override init(frame: CGRect) {
self.scrollView = UIScrollView()
super.init(frame: frame)
self.scrollView.layer.anchorPoint = CGPoint()
self.scrollView.delaysContentTouches = true
self.scrollView.clipsToBounds = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
if #available(iOS 13.0, *) {
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
}
self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.alwaysBounceVertical = true
self.scrollView.scrollsToTop = false
self.scrollView.delegate = self
self.scrollView.clipsToBounds = true
self.scrollView.canCancelContentTouches = true
self.addSubview(self.scrollView)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.ignoreScrolling {
self.updateVisibleItems(transition: .immediate, synchronous: false)
}
}
private func updateVisibleItems(transition: Transition, synchronous: Bool) {
guard let component = self.component, let itemLayout = self.itemLayout else {
return
}
var validIds = Set<Int64>()
let visibleBounds = self.scrollView.bounds
for index in 0 ..< component.items.count {
let itemFrame = itemLayout.frame(at: index)
if !visibleBounds.intersects(itemFrame) {
continue
}
let item = component.items[index]
validIds.insert(item.id)
let itemView: ItemView
var itemTransition = transition
if let current = self.visibleItemViews[item.id] {
itemView = current
} else {
itemTransition = .immediate
itemView = ItemView()
self.visibleItemViews[item.id] = itemView
}
let id = item.id
let _ = itemView.host.update(
transition: itemTransition,
component: AnyComponent(ForumTopicListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
item: item,
action: { [weak self] in
guard let strongSelf = self, let component = strongSelf.component else {
return
}
for item in component.items {
if item.id == id {
component.action(item)
break
}
}
}
)),
environment: {},
containerSize: itemFrame.size
)
if let itemComponentView = itemView.host.view {
if itemComponentView.superview == nil {
self.scrollView.addSubview(itemComponentView)
self.scrollView.layer.addSublayer(itemView.separatorLayer)
}
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
let separatorInset: CGFloat
if index == component.items.count - 1 {
separatorInset = 0.0
} else {
separatorInset = 16.0
}
itemView.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
itemTransition.setFrame(layer: itemView.separatorLayer, frame: CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: itemLayout.contentSize.width - separatorInset, height: UIScreenPixel)))
}
}
var removedIds: [Int64] = []
for (id, itemView) in self.visibleItemViews {
if !validIds.contains(id) {
itemView.host.view?.removeFromSuperview()
itemView.separatorLayer.removeFromSuperlayer()
removedIds.append(id)
}
}
for id in removedIds {
self.visibleItemViews.removeValue(forKey: id)
}
}
func update(component: ForumTopicListComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
let itemLayout = ItemLayout(containerSize: availableSize, navigationHeight: component.navigationHeight, itemCount: component.items.count)
self.itemLayout = itemLayout
self.ignoreScrolling = true
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
if self.scrollView.contentSize != itemLayout.contentSize {
self.scrollView.contentSize = itemLayout.contentSize
}
if self.scrollView.scrollIndicatorInsets != itemLayout.itemsInsets {
self.scrollView.scrollIndicatorInsets = itemLayout.itemsInsets
}
self.ignoreScrolling = false
self.updateVisibleItems(transition: transition, synchronous: false)
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class ForumTopicListScreen: ViewController {
private final class Node: ViewControllerTracingNode {
private weak var controller: ForumTopicListScreen?
private let context: AccountContext
private let id: EnginePeer.Id
private var presentationData: PresentationData
private let topicList: ComponentView<Empty>
private let forumChannelContext: ForumChannelTopics
private var stateDisposable: Disposable?
private var currentState: ForumChannelTopics.State?
private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
init(controller: ForumTopicListScreen, context: AccountContext, id: EnginePeer.Id) {
self.controller = controller
self.context = context
self.id = id
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.topicList = ComponentView()
self.forumChannelContext = ForumChannelTopics(account: self.context.account, peerId: self.id)
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.stateDisposable = (self.forumChannelContext.state
|> deliverOnMainQueue).start(next: { [weak self] state in
guard let strongSelf = self else {
return
}
strongSelf.currentState = state
strongSelf.update(transition: .immediate)
})
}
deinit {
self.stateDisposable?.dispose()
}
func createPressed() {
}
private func update(transition: Transition) {
if let currentLayout = self.currentLayout {
self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: transition)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) {
self.currentLayout = (layout, navigationHeight)
let _ = self.topicList.update(
transition: transition,
component: AnyComponent(ForumTopicListComponent(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
items: self.currentState?.items ?? [],
navigationHeight: navigationHeight,
action: { [weak self] item in
guard let strongSelf = self else {
return
}
strongSelf.controller?.openTopic(item)
}
)),
environment: {},
containerSize: layout.size
)
if let topicListView = self.topicList.view {
if topicListView.superview == nil {
if let navigationBar = self.controller?.navigationBar {
self.view.insertSubview(topicListView, belowSubview: navigationBar.view)
} else {
self.view.addSubview(topicListView)
}
}
transition.setFrame(view: topicListView, frame: CGRect(origin: CGPoint(), size: layout.size))
}
}
}
private var node: Node {
return self.displayNode as! Node
}
private let context: AccountContext
private let id: EnginePeer.Id
private var presentationData: PresentationData
private let openTopic: (ForumChannelTopics.Item) -> Void
public init(context: AccountContext, id: EnginePeer.Id, openTopic: @escaping (ForumChannelTopics.Item) -> Void) {
self.context = context
self.id = id
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.openTopic = openTopic
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
//TODO:localize
self.title = "Forum"
self.navigationItem.setRightBarButton(UIBarButtonItem(title: "Create", style: .plain, target: self, action: #selector(self.createPressed)), animated: false)
}
public required init(coder aDecoder: NSCoder) {
preconditionFailure()
}
@objc private func createPressed() {
self.node.createPressed()
}
override public func loadDisplayNode() {
self.displayNode = Node(controller: self, context: self.context, id: self.id)
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: Transition(transition))
}
}

View File

@ -1,8 +1,8 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ForumTopicListScreen",
module_name = "ForumTopicListScreen",
name = "NotificationExceptionsScreen",
module_name = "NotificationExceptionsScreen",
srcs = glob([
"Sources/**/*.swift",
]),
@ -26,6 +26,10 @@ swift_library(
"//submodules/AppBundle:AppBundle",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/ItemListUI",
"//submodules/ItemListPeerItem",
"//submodules/ItemListPeerActionItem",
"//submodules/NotificationSoundSelectionUI",
],
visibility = [
"//visibility:public",

View File

@ -0,0 +1,535 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import AlertUI
import PresentationDataUtils
import NotificationSoundSelectionUI
import TelegramStringFormatting
import ItemListPeerItem
import ItemListPeerActionItem
private extension EnginePeer.NotificationSettings.MuteState {
var timeInterval: Int32? {
switch self {
case .default:
return nil
case .unmuted:
return 0
case let .muted(until):
return until
}
}
}
private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound {
if case .default = sound {
return defaultCloudPeerNotificationSound
} else {
return sound
}
}
private final class NotificationsPeerCategoryControllerArguments {
let context: AccountContext
let soundSelectionDisposable: MetaDisposable
let updateEnabled: (Bool) -> Void
let updatePreviews: (Bool) -> Void
let openSound: (PeerMessageSound) -> Void
let addException: () -> Void
let openException: (Int64) -> Void
let removeAllExceptions: () -> Void
let updateRevealedThreadId: (Int64?) -> Void
let removeThread: (Int64) -> Void
init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (Int64) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedThreadId: @escaping (Int64?) -> Void, removeThread: @escaping (Int64) -> Void) {
self.context = context
self.soundSelectionDisposable = soundSelectionDisposable
self.updateEnabled = updateEnabled
self.updatePreviews = updatePreviews
self.openSound = openSound
self.addException = addException
self.openException = openException
self.removeAllExceptions = removeAllExceptions
self.updateRevealedThreadId = updateRevealedThreadId
self.removeThread = removeThread
}
}
private enum NotificationsPeerCategorySection: Int32 {
case enable
case options
case exceptions
}
private 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(String, Bool)
case optionsHeader(String)
case previews(String, Bool)
case sound(String, String, PeerMessageSound)
case exceptionsHeader(String)
case addException(String)
case exception(Int32, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessageHistoryThread.Info, String, TelegramPeerNotificationSettings, Bool, Bool)
case removeAllExceptions(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(lhsText, lhsValue):
if case let .enable(rhsText, rhsValue) = rhs, lhsText == rhsText, lhsValue == rhsValue {
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
}
case let .exceptionsHeader(lhsText):
if case let .exceptionsHeader(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .addException(lhsText):
if case let .addException(rhsText) = rhs, lhsText == rhsText {
return true
} else {
return false
}
case let .exception(lhsIndex, lhsDateTimeFormat, lhsDisplayNameOrder, lhsInfo, lhsDescription, lhsSettings, lhsEditing, lhsRevealed):
if case let .exception(rhsIndex, rhsDateTimeFormat, rhsDisplayNameOrder, rhsInfo, rhsDescription, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, lhsInfo == rhsInfo, lhsDescription == rhsDescription, lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .removeAllExceptions(lhsText):
if case let .removeAllExceptions(rhsText) = rhs, 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, 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)
case let .addException(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .peerList, color: .accent, editing: false, action: {
arguments.addException()
})
//case let .exception(_, dateTimeFormat, nameDisplayOrder, info, description, _, editing, revealed):
case .exception:
preconditionFailure()
/*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(text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
arguments.removeAllExceptions()
})
}
}
}
private func notificationsPeerCategoryEntries(notificationSettings: EnginePeer.NotificationSettings, state: NotificationExceptionState, presentationData: PresentationData, notificationSoundList: NotificationSoundList?) -> [NotificationsPeerCategoryEntry] {
var entries: [NotificationsPeerCategoryEntry] = []
var notificationsEnabled = true
if case .muted = notificationSettings.muteState {
notificationsEnabled = false
}
var displayPreviews = true
switch notificationSettings.displayPreviews {
case .hide:
displayPreviews = false
default:
break
}
entries.append(.enable(presentationData.strings.Notifications_MessageNotificationsAlert, notificationsEnabled))
if notificationsEnabled || !state.notificationExceptions.isEmpty {
entries.append(.optionsHeader(presentationData.strings.Notifications_Options.uppercased()))
entries.append(.previews(presentationData.strings.Notifications_MessageNotificationsPreview, displayPreviews))
entries.append(.sound(presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.messageSound._asMessageSound())), filteredGlobalSound(notificationSettings.messageSound._asMessageSound())))
}
entries.append(.exceptionsHeader(presentationData.strings.Notifications_MessageNotificationsExceptions.uppercased()))
entries.append(.addException(presentationData.strings.Notification_Exceptions_AddException))
var existingThreadIds = Set<Int64>()
var index: Int = 0
for value in state.notificationExceptions {
var title: String
var muted = false
switch value.notificationSettings.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.notificationSettings.messageSound {
case .default:
break
default:
if !title.isEmpty {
title.append(", ")
}
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
}
switch value.notificationSettings.displayPreviews {
case .default:
break
default:
if !title.isEmpty {
title += ", "
}
if case .show = value.notificationSettings.displayPreviews {
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOn
} else {
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOff
}
}
}
existingThreadIds.insert(value.threadId)
entries.append(.exception(Int32(index), presentationData.dateTimeFormat, presentationData.nameDisplayOrder, value.info, title, value.notificationSettings._asNotificationSettings(), state.editing, state.revealedThreadId == value.threadId))
index += 1
}
if state.notificationExceptions.count > 0 {
entries.append(.removeAllExceptions(presentationData.strings.Notifications_DeleteAllExceptions))
}
return entries
}
private struct NotificationExceptionState: Equatable {
var revealedThreadId: Int64? = nil
var editing: Bool = false
var notificationExceptions: [EngineMessageHistoryThread.NotificationException] = []
}
public func threadNotificationExceptionsScreen(context: AccountContext, peerId: EnginePeer.Id, notificationExceptions: [EngineMessageHistoryThread.NotificationException], updated: @escaping ([EngineMessageHistoryThread.NotificationException]) -> Void) -> ViewController {
var presentControllerImpl: ((ViewController, Any?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let initialState = NotificationExceptionState(notificationExceptions: notificationExceptions)
let stateValue = Atomic<NotificationExceptionState>(value: initialState)
let statePromise: ValuePromise<NotificationExceptionState> = ValuePromise(ignoreRepeated: true)
statePromise.set(initialState)
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, threadId: nil, sound: sound)
|> deliverOnMainQueue
}
let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal<Void, NoError> = { peerId, muteInterval in
return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: nil, muteInterval: muteInterval)
|> deliverOnMainQueue
}
let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal<Void, NoError> = {
peerId, displayPreviews in
return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue
}*/
let _ = presentControllerImpl
let arguments = NotificationsPeerCategoryControllerArguments(context: context, soundSelectionDisposable: MetaDisposable(), updateEnabled: { _ in
let _ = context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil).start()
}, updatePreviews: { value in
let _ = context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: value ? .show : .hide).start()
}, openSound: { sound in
let controller = notificationSoundSelectionController(context: context, isModal: true, currentSound: sound, defaultSound: nil, completion: { value in
let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: value).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.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { EnginePeer($0.peer) })
|> 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)*/
}, updateRevealedThreadId: { threadId in
updateState { current in
var current = current
current.revealedThreadId = threadId
return current
}
}, removeThread: { threadId in
/*let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [EnginePeer(peer)])
|> 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({})
})
})*/
})
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
context.engine.peers.notificationSoundList(),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId)),
statePromise.get()
)
|> map { presentationData, notificationSoundList, peer, notificationSettings, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
let entries = notificationsPeerCategoryEntries(notificationSettings: notificationSettings, state: state, presentationData: presentationData, notificationSoundList: notificationSoundList)
var scrollToItem: ListViewScrollToItem?
scrollToItem = nil
/*var index = 0
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 leftNavigationButton: ItemListNavigationButton?
let rightNavigationButton: ItemListNavigationButton?
if !state.notificationExceptions.isEmpty {
if state.editing {
leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {})
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
updateState { value in
var value = value
value.editing = false
return value
}
})
} else {
leftNavigationButton = nil
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: {
updateState { value in
var value = value
value.editing = true
return value
}
})
}
} else {
leftNavigationButton = nil
rightNavigationButton = nil
}
let title: String
if let peer = peer {
title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
} else {
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, ensureVisibleItemTag: nil, 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
}

View File

@ -12,7 +12,6 @@ import PeerAvatarGalleryUI
import SettingsUI
import ChatPresentationInterfaceState
import AttachmentUI
import ForumTopicListScreen
import ForumCreateTopicScreen
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {

View File

@ -78,6 +78,7 @@ import ComponentFlow
import EmojiStatusComponent
import ChatTitleView
import ForumCreateTopicScreen
import NotificationExceptionsScreen
protocol PeerInfoScreenItem: AnyObject {
var id: AnyHashable { get }
@ -1874,7 +1875,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
private weak var copyProtectionTooltipController: TooltipController?
weak var emojiStatusSelectionController: ViewController?
private var forumTopicNotificationExceptions: [(threadId: Int64, EnginePeer.NotificationSettings)] = []
private var forumTopicNotificationExceptions: [EngineMessageHistoryThread.NotificationException] = []
private var forumTopicNotificationExceptionsDisposable: Disposable?
private let _ready = Promise<Bool>()
@ -4148,7 +4149,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} else {
text = "There are \(self.forumTopicNotificationExceptions.count) topics that are listed as exceptions."
}
tip = .notificationTopicExceptions(text: text, action: {
tip = .notificationTopicExceptions(text: text, action: { [weak self] in
guard let self else {
return
}
self.controller?.present(threadNotificationExceptionsScreen(context: self.context, peerId: self.peerId, notificationExceptions: self.forumTopicNotificationExceptions, updated: { [weak self] value in
guard let self else {
return
}
self.forumTopicNotificationExceptions = value
}), in: .window(.root))
})
}