mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ff5307a254
@ -232,7 +232,7 @@ public final class ItemListControllerNodeView: UITracingLayerView {
|
|||||||
weak var controller: ItemListController?
|
weak var controller: ItemListController?
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ItemListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||||
private weak var controller: ItemListController?
|
private weak var controller: ItemListController?
|
||||||
|
|
||||||
private var _ready = ValuePromise<Bool>()
|
private var _ready = ValuePromise<Bool>()
|
||||||
@ -495,7 +495,7 @@ open class ItemListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
return directions
|
return directions
|
||||||
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
||||||
panRecognizer.delegate = self
|
panRecognizer.delegate = self.wrappedGestureRecognizerDelegate
|
||||||
panRecognizer.delaysTouchesBegan = false
|
panRecognizer.delaysTouchesBegan = false
|
||||||
panRecognizer.cancelsTouchesInView = true
|
panRecognizer.cancelsTouchesInView = true
|
||||||
self.panRecognizer = panRecognizer
|
self.panRecognizer = panRecognizer
|
||||||
|
@ -14,10 +14,16 @@ public enum ItemListSwitchItemNodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||||
|
public enum TextColor {
|
||||||
|
case primary
|
||||||
|
case accent
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let icon: UIImage?
|
let icon: UIImage?
|
||||||
let title: String
|
let title: String
|
||||||
let text: String?
|
let text: String?
|
||||||
|
let textColor: TextColor
|
||||||
let titleBadgeComponent: AnyComponent<Empty>?
|
let titleBadgeComponent: AnyComponent<Empty>?
|
||||||
let value: Bool
|
let value: Bool
|
||||||
let type: ItemListSwitchItemNodeType
|
let type: ItemListSwitchItemNodeType
|
||||||
@ -31,13 +37,15 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
|||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let updated: (Bool) -> Void
|
let updated: (Bool) -> Void
|
||||||
let activatedWhileDisabled: () -> Void
|
let activatedWhileDisabled: () -> Void
|
||||||
|
let action: (() -> Void)?
|
||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, titleBadgeComponent: AnyComponent<Empty>? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
|
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, textColor: TextColor = .primary, titleBadgeComponent: AnyComponent<Empty>? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.textColor = textColor
|
||||||
self.titleBadgeComponent = titleBadgeComponent
|
self.titleBadgeComponent = titleBadgeComponent
|
||||||
self.value = value
|
self.value = value
|
||||||
self.type = type
|
self.type = type
|
||||||
@ -51,6 +59,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
|||||||
self.style = style
|
self.style = style
|
||||||
self.updated = updated
|
self.updated = updated
|
||||||
self.activatedWhileDisabled = activatedWhileDisabled
|
self.activatedWhileDisabled = activatedWhileDisabled
|
||||||
|
self.action = action
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +99,17 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var selectable: Bool {
|
||||||
|
return self.action != nil && self.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
public func selected(listView: ListView){
|
||||||
|
listView.clearHighlightAnimated(true)
|
||||||
|
if self.enabled {
|
||||||
|
self.action?()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol ItemListSwitchNodeImpl {
|
protocol ItemListSwitchNodeImpl {
|
||||||
@ -132,7 +152,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let textNode: TextNode
|
private var textNode: TextNode?
|
||||||
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
|
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
|
||||||
private let switchGestureNode: ASDisplayNode
|
private let switchGestureNode: ASDisplayNode
|
||||||
private var disabledOverlayNode: ASDisplayNode?
|
private var disabledOverlayNode: ASDisplayNode?
|
||||||
@ -168,11 +188,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
|
self.titleNode.anchorPoint = CGPoint()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.textNode = TextNode()
|
|
||||||
self.textNode.isUserInteractionEnabled = false
|
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .regular:
|
case .regular:
|
||||||
self.switchNode = SwitchNode()
|
self.switchNode = SwitchNode()
|
||||||
@ -190,7 +208,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
|
||||||
self.addSubnode(self.switchNode)
|
self.addSubnode(self.switchNode)
|
||||||
self.addSubnode(self.switchGestureNode)
|
self.addSubnode(self.switchGestureNode)
|
||||||
self.addSubnode(self.activateArea)
|
self.addSubnode(self.activateArea)
|
||||||
@ -230,7 +247,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let itemSeparatorColor: UIColor
|
let itemSeparatorColor: UIColor
|
||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
let textFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0)
|
let textFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)
|
||||||
|
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||||
@ -274,11 +291,21 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: item.maximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 64.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: item.maximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 64.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text ?? "", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 84.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
|
||||||
|
|
||||||
contentSize.height = max(contentSize.height, titleLayout.size.height + topInset * 2.0)
|
contentSize.height = max(contentSize.height, titleLayout.size.height + topInset * 2.0)
|
||||||
if item.text != nil {
|
|
||||||
|
var textLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||||
|
|
||||||
|
if let text = item.text {
|
||||||
|
let textColor: UIColor
|
||||||
|
switch item.textColor {
|
||||||
|
case .primary:
|
||||||
|
textColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
|
case .accent:
|
||||||
|
textColor = item.presentationData.theme.list.itemAccentColor
|
||||||
|
}
|
||||||
|
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: textFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 84.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
contentSize.height += -1.0 + textLayout.size.height
|
contentSize.height += -1.0 + textLayout.size.height
|
||||||
|
textLayoutAndApply = (textLayout, textApply)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !item.enabled {
|
if !item.enabled {
|
||||||
@ -294,6 +321,13 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] animated in
|
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
|
|
||||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||||
@ -309,7 +343,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
strongSelf.activateArea.accessibilityTraits = accessibilityTraits
|
strongSelf.activateArea.accessibilityTraits = accessibilityTraits
|
||||||
|
|
||||||
if let icon = item.icon {
|
if let icon = item.icon {
|
||||||
|
var iconTransition = transition
|
||||||
if strongSelf.iconNode.supernode == nil {
|
if strongSelf.iconNode.supernode == nil {
|
||||||
|
iconTransition = .immediate
|
||||||
strongSelf.addSubnode(strongSelf.iconNode)
|
strongSelf.addSubnode(strongSelf.iconNode)
|
||||||
}
|
}
|
||||||
if updateIcon {
|
if updateIcon {
|
||||||
@ -321,19 +357,12 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
} else {
|
} else {
|
||||||
iconY = max(0.0, floor(topInset + titleLayout.size.height + 1.0 - icon.size.height * 0.5))
|
iconY = max(0.0, floor(topInset + titleLayout.size.height + 1.0 - icon.size.height * 0.5))
|
||||||
}
|
}
|
||||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size)
|
iconTransition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - icon.size.width) / 2.0), y: iconY), size: icon.size))
|
||||||
} else if strongSelf.iconNode.supernode != nil {
|
} else if strongSelf.iconNode.supernode != nil {
|
||||||
strongSelf.iconNode.image = nil
|
strongSelf.iconNode.image = nil
|
||||||
strongSelf.iconNode.removeFromSupernode()
|
strongSelf.iconNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition
|
|
||||||
if animated {
|
|
||||||
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
|
||||||
} else {
|
|
||||||
transition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentDisabledOverlayNode = currentDisabledOverlayNode {
|
if let currentDisabledOverlayNode = currentDisabledOverlayNode {
|
||||||
if currentDisabledOverlayNode != strongSelf.disabledOverlayNode {
|
if currentDisabledOverlayNode != strongSelf.disabledOverlayNode {
|
||||||
strongSelf.disabledOverlayNode = currentDisabledOverlayNode
|
strongSelf.disabledOverlayNode = currentDisabledOverlayNode
|
||||||
@ -367,7 +396,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = textApply()
|
|
||||||
|
|
||||||
switch item.style {
|
switch item.style {
|
||||||
case .plain:
|
case .plain:
|
||||||
@ -383,7 +411,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
if strongSelf.maskNode.supernode != nil {
|
if strongSelf.maskNode.supernode != nil {
|
||||||
strongSelf.maskNode.removeFromSupernode()
|
strongSelf.maskNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
|
||||||
case .blocks:
|
case .blocks:
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
@ -421,15 +449,39 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))))
|
||||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
transition.updateFrame(node: strongSelf.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0))
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
|
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size)
|
||||||
|
transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin)
|
||||||
|
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||||
|
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 2.0), size: textLayout.size)
|
if let (textLayout, textApply) = textLayoutAndApply {
|
||||||
|
let textNode = textApply()
|
||||||
|
if strongSelf.textNode !== textNode {
|
||||||
|
strongSelf.textNode?.removeFromSupernode()
|
||||||
|
strongSelf.textNode = textNode
|
||||||
|
textNode.isUserInteractionEnabled = false
|
||||||
|
strongSelf.addSubnode(textNode)
|
||||||
|
|
||||||
|
if transition.isAnimated {
|
||||||
|
textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 2.0), size: textLayout.size)
|
||||||
|
} else if let textNode = strongSelf.textNode {
|
||||||
|
strongSelf.textNode = nil
|
||||||
|
if transition.isAnimated {
|
||||||
|
textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textNode] _ in
|
||||||
|
textNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
textNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let switchView = strongSelf.switchNode.view as? UISwitch {
|
if let switchView = strongSelf.switchNode.view as? UISwitch {
|
||||||
if strongSelf.switchNode.bounds.size.width.isZero {
|
if strongSelf.switchNode.bounds.size.width.isZero {
|
||||||
@ -437,7 +489,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
}
|
}
|
||||||
let switchSize = switchView.bounds.size
|
let switchSize = switchView.bounds.size
|
||||||
|
|
||||||
strongSelf.switchNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - switchSize.width - 15.0, y: floor((contentSize.height - switchSize.height) / 2.0)), size: switchSize)
|
transition.updateFrame(node: strongSelf.switchNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - switchSize.width - 15.0, y: floor((contentSize.height - switchSize.height) / 2.0)), size: switchSize))
|
||||||
strongSelf.switchGestureNode.frame = strongSelf.switchNode.frame
|
strongSelf.switchGestureNode.frame = strongSelf.switchNode.frame
|
||||||
if switchView.isOn != item.value {
|
if switchView.isOn != item.value {
|
||||||
switchView.setOn(item.value, animated: animated)
|
switchView.setOn(item.value, animated: animated)
|
||||||
@ -447,6 +499,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
strongSelf.switchGestureNode.isHidden = item.enableInteractiveChanges && item.enabled
|
strongSelf.switchGestureNode.isHidden = item.enableInteractiveChanges && item.enabled
|
||||||
|
|
||||||
if item.displayLocked {
|
if item.displayLocked {
|
||||||
|
var lockedIconTransition = transition
|
||||||
var updateLockedIconImage = false
|
var updateLockedIconImage = false
|
||||||
if let _ = updatedTheme {
|
if let _ = updatedTheme {
|
||||||
updateLockedIconImage = true
|
updateLockedIconImage = true
|
||||||
@ -456,6 +509,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
if let current = strongSelf.lockedIconNode {
|
if let current = strongSelf.lockedIconNode {
|
||||||
lockedIconNode = current
|
lockedIconNode = current
|
||||||
} else {
|
} else {
|
||||||
|
lockedIconTransition = .immediate
|
||||||
updateLockedIconImage = true
|
updateLockedIconImage = true
|
||||||
lockedIconNode = ASImageNode()
|
lockedIconNode = ASImageNode()
|
||||||
strongSelf.lockedIconNode = lockedIconNode
|
strongSelf.lockedIconNode = lockedIconNode
|
||||||
@ -469,7 +523,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let switchFrame = strongSelf.switchNode.frame
|
let switchFrame = strongSelf.switchNode.frame
|
||||||
|
|
||||||
if let icon = lockedIconNode.image {
|
if let icon = lockedIconNode.image {
|
||||||
lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size)
|
lockedIconTransition.updateFrame(node: lockedIconNode, frame: CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size))
|
||||||
}
|
}
|
||||||
} else if let lockedIconNode = strongSelf.lockedIconNode {
|
} else if let lockedIconNode = strongSelf.lockedIconNode {
|
||||||
strongSelf.lockedIconNode = nil
|
strongSelf.lockedIconNode = nil
|
||||||
@ -492,17 +546,19 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
containerSize: contentSize
|
containerSize: contentSize
|
||||||
)
|
)
|
||||||
if let view = componentView.view {
|
if let view = componentView.view {
|
||||||
|
var titleBadgeTransition = transition
|
||||||
if view.superview == nil {
|
if view.superview == nil {
|
||||||
|
titleBadgeTransition = .immediate
|
||||||
strongSelf.view.addSubview(view)
|
strongSelf.view.addSubview(view)
|
||||||
}
|
}
|
||||||
view.frame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + 7.0, y: floor((contentSize.height - badgeSize.height) / 2.0)), size: badgeSize)
|
titleBadgeTransition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + 7.0, y: floor((contentSize.height - badgeSize.height) / 2.0)), size: badgeSize))
|
||||||
}
|
}
|
||||||
} else if let componentView = strongSelf.titleBadgeComponentView {
|
} else if let componentView = strongSelf.titleBadgeComponentView {
|
||||||
strongSelf.titleBadgeComponentView = nil
|
strongSelf.titleBadgeComponentView = nil
|
||||||
componentView.view?.removeFromSuperview()
|
componentView.view?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
transition.updateFrame(node: strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layoutSize.height + UIScreenPixel + UIScreenPixel)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ private final class NotificationsAndSoundsArguments {
|
|||||||
let suppressWarning: () -> Void
|
let suppressWarning: () -> Void
|
||||||
|
|
||||||
let openPeerCategory: (NotificationsPeerCategory) -> Void
|
let openPeerCategory: (NotificationsPeerCategory) -> Void
|
||||||
|
let openReactions: () -> Void
|
||||||
|
|
||||||
let updateInAppSounds: (Bool) -> Void
|
let updateInAppSounds: (Bool) -> Void
|
||||||
let updateInAppVibration: (Bool) -> Void
|
let updateInAppVibration: (Bool) -> Void
|
||||||
@ -80,7 +81,7 @@ private final class NotificationsAndSoundsArguments {
|
|||||||
|
|
||||||
let updateNotificationsFromAllAccounts: (Bool) -> 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) {
|
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeerCategory: @escaping (NotificationsPeerCategory) -> Void, openReactions: @escaping () -> 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.context = context
|
||||||
self.presentController = presentController
|
self.presentController = presentController
|
||||||
self.pushController = pushController
|
self.pushController = pushController
|
||||||
@ -88,6 +89,7 @@ private final class NotificationsAndSoundsArguments {
|
|||||||
self.authorizeNotifications = authorizeNotifications
|
self.authorizeNotifications = authorizeNotifications
|
||||||
self.suppressWarning = suppressWarning
|
self.suppressWarning = suppressWarning
|
||||||
self.openPeerCategory = openPeerCategory
|
self.openPeerCategory = openPeerCategory
|
||||||
|
self.openReactions = openReactions
|
||||||
self.updateInAppSounds = updateInAppSounds
|
self.updateInAppSounds = updateInAppSounds
|
||||||
self.updateInAppVibration = updateInAppVibration
|
self.updateInAppVibration = updateInAppVibration
|
||||||
self.updateInAppPreviews = updateInAppPreviews
|
self.updateInAppPreviews = updateInAppPreviews
|
||||||
@ -145,6 +147,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
case groupChats(PresentationTheme, String, String, String)
|
case groupChats(PresentationTheme, String, String, String)
|
||||||
case channels(PresentationTheme, String, String, String)
|
case channels(PresentationTheme, String, String, String)
|
||||||
case stories(PresentationTheme, String, String, String)
|
case stories(PresentationTheme, String, String, String)
|
||||||
|
case reactions(PresentationTheme, String, String, String)
|
||||||
|
|
||||||
case inAppHeader(PresentationTheme, String)
|
case inAppHeader(PresentationTheme, String)
|
||||||
case inAppSounds(PresentationTheme, String, Bool)
|
case inAppSounds(PresentationTheme, String, Bool)
|
||||||
@ -171,7 +174,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
return NotificationsAndSoundsSection.accounts.rawValue
|
return NotificationsAndSoundsSection.accounts.rawValue
|
||||||
case .permissionInfo, .permissionEnable:
|
case .permissionInfo, .permissionEnable:
|
||||||
return NotificationsAndSoundsSection.permission.rawValue
|
return NotificationsAndSoundsSection.permission.rawValue
|
||||||
case .categoriesHeader, .privateChats, .groupChats, .channels, .stories:
|
case .categoriesHeader, .privateChats, .groupChats, .channels, .stories, .reactions:
|
||||||
return NotificationsAndSoundsSection.categories.rawValue
|
return NotificationsAndSoundsSection.categories.rawValue
|
||||||
case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews:
|
case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews:
|
||||||
return NotificationsAndSoundsSection.inApp.rawValue
|
return NotificationsAndSoundsSection.inApp.rawValue
|
||||||
@ -208,6 +211,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
return 8
|
return 8
|
||||||
case .stories:
|
case .stories:
|
||||||
return 9
|
return 9
|
||||||
|
case .reactions:
|
||||||
|
return 10
|
||||||
case .inAppHeader:
|
case .inAppHeader:
|
||||||
return 14
|
return 14
|
||||||
case .inAppSounds:
|
case .inAppSounds:
|
||||||
@ -326,6 +331,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .reactions(lhsTheme, lhsTitle, lhsSubtitle, lhsLabel):
|
||||||
|
if case let .reactions(rhsTheme, rhsTitle, rhsSubtitle, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .inAppHeader(lhsTheme, lhsText):
|
case let .inAppHeader(lhsTheme, lhsText):
|
||||||
if case let .inAppHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .inAppHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -451,9 +462,13 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
arguments.openPeerCategory(.channel)
|
arguments.openPeerCategory(.channel)
|
||||||
})
|
})
|
||||||
case let .stories(_, title, subtitle, label):
|
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: {
|
return NotificationsCategoryItemListItem(presentationData: presentationData, icon: PresentationResourcesSettings.stories, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: {
|
||||||
arguments.openPeerCategory(.stories)
|
arguments.openPeerCategory(.stories)
|
||||||
})
|
})
|
||||||
|
case let .reactions(_, title, subtitle, label):
|
||||||
|
return NotificationsCategoryItemListItem(presentationData: presentationData, icon: PresentationResourcesSettings.reactions, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: {
|
||||||
|
arguments.openReactions()
|
||||||
|
})
|
||||||
case let .inAppHeader(_, text):
|
case let .inAppHeader(_, text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .inAppSounds(_, text, value):
|
case let .inAppSounds(_, text, value):
|
||||||
@ -564,6 +579,31 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
|
|||||||
|
|
||||||
entries.append(.stories(presentationData.theme, presentationData.strings.Notifications_Stories, !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
|
entries.append(.stories(presentationData.theme, presentationData.strings.Notifications_Stories, !exceptions.stories.isEmpty ? presentationData.strings.Notifications_CategoryExceptions(Int32(exceptions.stories.peerIds.count)) : "", storiesValue))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
var reactionsValue: String = ""
|
||||||
|
var hasReactionNotifications = false
|
||||||
|
switch globalSettings.reactionSettings.messages {
|
||||||
|
case .nobody:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if !reactionsValue.isEmpty {
|
||||||
|
reactionsValue.append(", ")
|
||||||
|
}
|
||||||
|
hasReactionNotifications = true
|
||||||
|
reactionsValue.append("Messages")
|
||||||
|
}
|
||||||
|
switch globalSettings.reactionSettings.stories {
|
||||||
|
case .nobody:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
if !reactionsValue.isEmpty {
|
||||||
|
reactionsValue.append(", ")
|
||||||
|
}
|
||||||
|
hasReactionNotifications = true
|
||||||
|
reactionsValue.append("Stories")
|
||||||
|
}
|
||||||
|
entries.append(.reactions(presentationData.theme, "Reactions", reactionsValue, hasReactionNotifications ? "On" : "Off"))
|
||||||
|
|
||||||
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
|
entries.append(.inAppHeader(presentationData.theme, presentationData.strings.Notifications_InAppNotifications.uppercased()))
|
||||||
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
entries.append(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
||||||
entries.append(.inAppVibrate(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsVibrate, inAppSettings.vibrate))
|
entries.append(.inAppVibrate(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsVibrate, inAppSettings.vibrate))
|
||||||
@ -655,6 +695,10 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
|
|||||||
})
|
})
|
||||||
}, focusOnItemTag: nil))
|
}, focusOnItemTag: nil))
|
||||||
})
|
})
|
||||||
|
}, openReactions: {
|
||||||
|
pushControllerImpl?(reactionNotificationSettingsController(
|
||||||
|
context: context
|
||||||
|
))
|
||||||
}, updateInAppSounds: { value in
|
}, updateInAppSounds: { value in
|
||||||
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
|
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
|
||||||
var settings = settings
|
var settings = settings
|
||||||
|
@ -92,6 +92,7 @@ public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
|||||||
|
|
||||||
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||||
enum StableId: Hashable {
|
enum StableId: Hashable {
|
||||||
|
case enableHeader
|
||||||
case enable
|
case enable
|
||||||
case enableImportant
|
case enableImportant
|
||||||
case importantInfo
|
case importantInfo
|
||||||
@ -104,6 +105,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
case removeAllExceptions
|
case removeAllExceptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case enableHeader(String)
|
||||||
case enable(PresentationTheme, String, Bool)
|
case enable(PresentationTheme, String, Bool)
|
||||||
case enableImportant(PresentationTheme, String, Bool)
|
case enableImportant(PresentationTheme, String, Bool)
|
||||||
case importantInfo(PresentationTheme, String)
|
case importantInfo(PresentationTheme, String)
|
||||||
@ -118,7 +120,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var section: ItemListSectionId {
|
var section: ItemListSectionId {
|
||||||
switch self {
|
switch self {
|
||||||
case .enable, .enableImportant, .importantInfo:
|
case .enableHeader, .enable, .enableImportant, .importantInfo:
|
||||||
return NotificationsPeerCategorySection.enable.rawValue
|
return NotificationsPeerCategorySection.enable.rawValue
|
||||||
case .optionsHeader, .previews, .sound:
|
case .optionsHeader, .previews, .sound:
|
||||||
return NotificationsPeerCategorySection.options.rawValue
|
return NotificationsPeerCategorySection.options.rawValue
|
||||||
@ -129,6 +131,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var stableId: StableId {
|
var stableId: StableId {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .enableHeader:
|
||||||
|
return .enableHeader
|
||||||
case .enable:
|
case .enable:
|
||||||
return .enable
|
return .enable
|
||||||
case .enableImportant:
|
case .enableImportant:
|
||||||
@ -154,6 +158,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
var sortIndex: Int32 {
|
var sortIndex: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .enableHeader:
|
||||||
|
return -1
|
||||||
case .enable:
|
case .enable:
|
||||||
return 0
|
return 0
|
||||||
case .enableImportant:
|
case .enableImportant:
|
||||||
@ -192,6 +198,12 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
static func ==(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
static func ==(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
|
case let .enableHeader(text):
|
||||||
|
if case .enableHeader(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .enable(lhsTheme, lhsText, lhsValue):
|
case let .enable(lhsTheme, lhsText, lhsValue):
|
||||||
if case let .enable(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
if case let .enable(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||||
return true
|
return true
|
||||||
@ -263,6 +275,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
|||||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||||
let arguments = arguments as! NotificationsPeerCategoryControllerArguments
|
let arguments = arguments as! NotificationsPeerCategoryControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
|
case let .enableHeader(text):
|
||||||
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .enable(_, text, value):
|
case let .enable(_, text, value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||||
arguments.updateEnabled(updatedValue)
|
arguments.updateEnabled(updatedValue)
|
||||||
@ -330,6 +344,9 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case .stories = category {
|
if case .stories = category {
|
||||||
|
//TODO:localize
|
||||||
|
entries.append(.enableHeader("NOTIFY ME ABOUT..."))
|
||||||
|
|
||||||
var allEnabled = false
|
var allEnabled = false
|
||||||
var importantEnabled = false
|
var importantEnabled = false
|
||||||
|
|
||||||
@ -345,16 +362,16 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
|||||||
importantEnabled = true
|
importantEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.enable(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowAll, allEnabled))
|
entries.append(.enable(presentationData.theme, "New Stories", allEnabled))
|
||||||
if !allEnabled {
|
if !allEnabled {
|
||||||
entries.append(.enableImportant(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportant, importantEnabled))
|
entries.append(.enableImportant(presentationData.theme, "Important Stories", importantEnabled))
|
||||||
entries.append(.importantInfo(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportantFooter))
|
entries.append(.importantInfo(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowImportantFooter))
|
||||||
}
|
}
|
||||||
|
|
||||||
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
||||||
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
|
entries.append(.optionsHeader(presentationData.theme, presentationData.strings.Notifications_Options.uppercased()))
|
||||||
|
|
||||||
entries.append(.previews(presentationData.theme, presentationData.strings.NotificationSettings_Stories_DisplayAuthorName, notificationSettings.storySettings.hideSender != .hide))
|
entries.append(.previews(presentationData.theme, "Show Sender's Name", notificationSettings.storySettings.hideSender != .hide))
|
||||||
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
|
entries.append(.sound(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsSound, localizedPeerNotificationSoundString(strings: presentationData.strings, notificationSoundList: notificationSoundList, sound: filteredGlobalSound(notificationSettings.storySettings.sound)), filteredGlobalSound(notificationSettings.storySettings.sound)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,418 @@
|
|||||||
|
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] = []
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
entries.append(.categoriesHeader("NOTIFY ME ABOUT..."))
|
||||||
|
|
||||||
|
let messagesText: String?
|
||||||
|
let messagesValue: Bool
|
||||||
|
switch globalSettings.reactionSettings.messages {
|
||||||
|
case .nobody:
|
||||||
|
messagesText = nil
|
||||||
|
messagesValue = false
|
||||||
|
case .contacts:
|
||||||
|
messagesText = "From My Contacts"
|
||||||
|
messagesValue = true
|
||||||
|
case .everyone:
|
||||||
|
messagesText = "From Everyone"
|
||||||
|
messagesValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let storiesText: String?
|
||||||
|
let storiesValue: Bool
|
||||||
|
switch globalSettings.reactionSettings.stories {
|
||||||
|
case .nobody:
|
||||||
|
storiesText = nil
|
||||||
|
storiesValue = false
|
||||||
|
case .contacts:
|
||||||
|
storiesText = "From My Contacts"
|
||||||
|
storiesValue = true
|
||||||
|
case .everyone:
|
||||||
|
storiesText = "From Everyone"
|
||||||
|
storiesValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.messages(title: "Reactions to my Messages", text: messagesText, value: messagesValue))
|
||||||
|
entries.append(.stories(title: "Reactions to my Stories", text: storiesText, value: storiesValue))
|
||||||
|
|
||||||
|
if messagesValue || storiesValue {
|
||||||
|
entries.append(.optionsHeader(presentationData.strings.Notifications_Options.uppercased()))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
entries.append(.previews("Show Sender's Name", 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
|
||||||
|
//TODO:localize
|
||||||
|
let text: String
|
||||||
|
if isMessages {
|
||||||
|
text = "Notify about reactions to my messages from"
|
||||||
|
} else {
|
||||||
|
text = "Notify about reactions to my stories from"
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetTextItem(title: text),
|
||||||
|
ActionSheetButtonItem(title: "Everyone", 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: "My Contacts", 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
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let title: String = "Reactions"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -270,7 +270,9 @@ private func fetchedNotificationSettings(network: Network) -> Signal<GlobalNotif
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, contactsJoined: contactsJoinedMuted == .boolFalse)
|
let reactionSettings: PeerReactionNotificationSettings = .default
|
||||||
|
|
||||||
|
return GlobalNotificationSettingsSet(privateChats: userSettings, groupChats: chatsSettings, channels: channelSettings, reactionSettings: reactionSettings, contactsJoined: contactsJoinedMuted == .boolFalse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,16 +42,18 @@ public struct GlobalNotificationSettingsSet: Codable, Equatable {
|
|||||||
public var privateChats: MessageNotificationSettings
|
public var privateChats: MessageNotificationSettings
|
||||||
public var groupChats: MessageNotificationSettings
|
public var groupChats: MessageNotificationSettings
|
||||||
public var channels: MessageNotificationSettings
|
public var channels: MessageNotificationSettings
|
||||||
|
public var reactionSettings: PeerReactionNotificationSettings
|
||||||
public var contactsJoined: Bool
|
public var contactsJoined: Bool
|
||||||
|
|
||||||
public static var defaultSettings: GlobalNotificationSettingsSet {
|
public static var defaultSettings: GlobalNotificationSettingsSet {
|
||||||
return GlobalNotificationSettingsSet(privateChats: MessageNotificationSettings.defaultSettings, groupChats: .defaultSettings, channels: .defaultSettings, contactsJoined: true)
|
return GlobalNotificationSettingsSet(privateChats: MessageNotificationSettings.defaultSettings, groupChats: .defaultSettings, channels: .defaultSettings, reactionSettings: .default, contactsJoined: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(privateChats: MessageNotificationSettings, groupChats: MessageNotificationSettings, channels: MessageNotificationSettings, contactsJoined: Bool) {
|
public init(privateChats: MessageNotificationSettings, groupChats: MessageNotificationSettings, channels: MessageNotificationSettings, reactionSettings: PeerReactionNotificationSettings, contactsJoined: Bool) {
|
||||||
self.privateChats = privateChats
|
self.privateChats = privateChats
|
||||||
self.groupChats = groupChats
|
self.groupChats = groupChats
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
|
self.reactionSettings = reactionSettings
|
||||||
self.contactsJoined = contactsJoined
|
self.contactsJoined = contactsJoined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +63,7 @@ public struct GlobalNotificationSettingsSet: Codable, Equatable {
|
|||||||
self.privateChats = try container.decode(MessageNotificationSettings.self, forKey: "p")
|
self.privateChats = try container.decode(MessageNotificationSettings.self, forKey: "p")
|
||||||
self.groupChats = try container.decode(MessageNotificationSettings.self, forKey: "g")
|
self.groupChats = try container.decode(MessageNotificationSettings.self, forKey: "g")
|
||||||
self.channels = try container.decode(MessageNotificationSettings.self, forKey: "c")
|
self.channels = try container.decode(MessageNotificationSettings.self, forKey: "c")
|
||||||
|
self.reactionSettings = try container.decodeIfPresent(PeerReactionNotificationSettings.self, forKey: "reactionSettings") ?? PeerReactionNotificationSettings.default
|
||||||
|
|
||||||
self.contactsJoined = (try container.decode(Int32.self, forKey: "contactsJoined")) != 0
|
self.contactsJoined = (try container.decode(Int32.self, forKey: "contactsJoined")) != 0
|
||||||
}
|
}
|
||||||
@ -71,6 +74,7 @@ public struct GlobalNotificationSettingsSet: Codable, Equatable {
|
|||||||
try container.encode(self.privateChats, forKey: "p")
|
try container.encode(self.privateChats, forKey: "p")
|
||||||
try container.encode(self.groupChats, forKey: "g")
|
try container.encode(self.groupChats, forKey: "g")
|
||||||
try container.encode(self.channels, forKey: "c")
|
try container.encode(self.channels, forKey: "c")
|
||||||
|
try container.encode(self.reactionSettings, forKey: "reactionSettings")
|
||||||
try container.encode((self.contactsJoined ? 1 : 0) as Int32, forKey: "contactsJoined")
|
try container.encode((self.contactsJoined ? 1 : 0) as Int32, forKey: "contactsJoined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,6 +454,93 @@ public struct PeerStoryNotificationSettings: Codable, Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct PeerReactionNotificationSettings: Codable, Equatable {
|
||||||
|
public enum CodingError: Error {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
public static var `default`: PeerReactionNotificationSettings {
|
||||||
|
return PeerReactionNotificationSettings(
|
||||||
|
messages: .contacts,
|
||||||
|
stories: .contacts,
|
||||||
|
hideSender: .show,
|
||||||
|
sound: .default
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case messages
|
||||||
|
case stories
|
||||||
|
case hideSender
|
||||||
|
case sound
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Sources: Int32, Codable {
|
||||||
|
case nobody = 0
|
||||||
|
case contacts = 1
|
||||||
|
case everyone = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Mute: Int32, Codable {
|
||||||
|
case `default` = 0
|
||||||
|
case unmuted = 1
|
||||||
|
case muted = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HideSender: Int32, Codable {
|
||||||
|
case `default` = 0
|
||||||
|
case hide = 1
|
||||||
|
case show = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public var messages: Sources
|
||||||
|
public var stories: Sources
|
||||||
|
public var hideSender: HideSender
|
||||||
|
public var sound: PeerMessageSound
|
||||||
|
|
||||||
|
public init(
|
||||||
|
messages: Sources,
|
||||||
|
stories: Sources,
|
||||||
|
hideSender: HideSender,
|
||||||
|
sound: PeerMessageSound
|
||||||
|
) {
|
||||||
|
self.messages = messages
|
||||||
|
self.stories = stories
|
||||||
|
self.hideSender = hideSender
|
||||||
|
self.sound = sound
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
if let messages = Sources(rawValue: try container.decode(Int32.self, forKey: .messages)) {
|
||||||
|
self.messages = messages
|
||||||
|
} else {
|
||||||
|
throw CodingError.generic
|
||||||
|
}
|
||||||
|
if let stories = Sources(rawValue: try container.decode(Int32.self, forKey: .stories)) {
|
||||||
|
self.stories = stories
|
||||||
|
} else {
|
||||||
|
throw CodingError.generic
|
||||||
|
}
|
||||||
|
if let hideSender = HideSender(rawValue: try container.decode(Int32.self, forKey: .hideSender)) {
|
||||||
|
self.hideSender = hideSender
|
||||||
|
} else {
|
||||||
|
throw CodingError.generic
|
||||||
|
}
|
||||||
|
self.sound = try container.decode(PeerMessageSound.self, forKey: .sound)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
try container.encode(self.messages.rawValue, forKey: .messages)
|
||||||
|
try container.encode(self.stories.rawValue, forKey: .stories)
|
||||||
|
try container.encode(self.hideSender.rawValue, forKey: .hideSender)
|
||||||
|
try container.encode(self.sound, forKey: .sound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Codable, Equatable {
|
public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Codable, Equatable {
|
||||||
public let muteState: PeerMuteState
|
public let muteState: PeerMuteState
|
||||||
public let messageSound: PeerMessageSound
|
public let messageSound: PeerMessageSound
|
||||||
|
@ -225,20 +225,37 @@ public struct EngineGlobalNotificationSettings: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct ReactionSettings: Equatable {
|
||||||
|
public var messages: PeerReactionNotificationSettings.Sources
|
||||||
|
public var stories: PeerReactionNotificationSettings.Sources
|
||||||
|
public var hideSender: PeerReactionNotificationSettings.HideSender
|
||||||
|
public var sound: EnginePeer.NotificationSettings.MessageSound
|
||||||
|
|
||||||
|
public init(messages: PeerReactionNotificationSettings.Sources, stories: PeerReactionNotificationSettings.Sources, hideSender: PeerReactionNotificationSettings.HideSender, sound: EnginePeer.NotificationSettings.MessageSound) {
|
||||||
|
self.messages = messages
|
||||||
|
self.stories = stories
|
||||||
|
self.hideSender = hideSender
|
||||||
|
self.sound = sound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var privateChats: CategorySettings
|
public var privateChats: CategorySettings
|
||||||
public var groupChats: CategorySettings
|
public var groupChats: CategorySettings
|
||||||
public var channels: CategorySettings
|
public var channels: CategorySettings
|
||||||
|
public var reactionSettings: ReactionSettings
|
||||||
public var contactsJoined: Bool
|
public var contactsJoined: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
privateChats: CategorySettings,
|
privateChats: CategorySettings,
|
||||||
groupChats: CategorySettings,
|
groupChats: CategorySettings,
|
||||||
channels: CategorySettings,
|
channels: CategorySettings,
|
||||||
|
reactionSettings: ReactionSettings,
|
||||||
contactsJoined: Bool
|
contactsJoined: Bool
|
||||||
) {
|
) {
|
||||||
self.privateChats = privateChats
|
self.privateChats = privateChats
|
||||||
self.groupChats = groupChats
|
self.groupChats = groupChats
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
|
self.reactionSettings = reactionSettings
|
||||||
self.contactsJoined = contactsJoined
|
self.contactsJoined = contactsJoined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -632,12 +649,33 @@ public extension EngineGlobalNotificationSettings.CategorySettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension EngineGlobalNotificationSettings.ReactionSettings {
|
||||||
|
init(_ reactionSettings: PeerReactionNotificationSettings) {
|
||||||
|
self.init(
|
||||||
|
messages: reactionSettings.messages,
|
||||||
|
stories: reactionSettings.stories,
|
||||||
|
hideSender: reactionSettings.hideSender,
|
||||||
|
sound: EnginePeer.NotificationSettings.MessageSound(reactionSettings.sound)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _asReactionSettings() -> PeerReactionNotificationSettings {
|
||||||
|
return PeerReactionNotificationSettings(
|
||||||
|
messages: self.messages,
|
||||||
|
stories: self.stories,
|
||||||
|
hideSender: self.hideSender,
|
||||||
|
sound: self.sound._asMessageSound()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public extension EngineGlobalNotificationSettings {
|
public extension EngineGlobalNotificationSettings {
|
||||||
init(_ globalNotificationSettings: GlobalNotificationSettingsSet) {
|
init(_ globalNotificationSettings: GlobalNotificationSettingsSet) {
|
||||||
self.init(
|
self.init(
|
||||||
privateChats: CategorySettings(globalNotificationSettings.privateChats),
|
privateChats: CategorySettings(globalNotificationSettings.privateChats),
|
||||||
groupChats: CategorySettings(globalNotificationSettings.groupChats),
|
groupChats: CategorySettings(globalNotificationSettings.groupChats),
|
||||||
channels: CategorySettings(globalNotificationSettings.channels),
|
channels: CategorySettings(globalNotificationSettings.channels),
|
||||||
|
reactionSettings: ReactionSettings(globalNotificationSettings.reactionSettings),
|
||||||
contactsJoined: globalNotificationSettings.contactsJoined
|
contactsJoined: globalNotificationSettings.contactsJoined
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -647,6 +685,7 @@ public extension EngineGlobalNotificationSettings {
|
|||||||
privateChats: self.privateChats._asMessageNotificationSettings(),
|
privateChats: self.privateChats._asMessageNotificationSettings(),
|
||||||
groupChats: self.groupChats._asMessageNotificationSettings(),
|
groupChats: self.groupChats._asMessageNotificationSettings(),
|
||||||
channels: self.channels._asMessageNotificationSettings(),
|
channels: self.channels._asMessageNotificationSettings(),
|
||||||
|
reactionSettings: self.reactionSettings._asReactionSettings(),
|
||||||
contactsJoined: self.contactsJoined
|
contactsJoined: self.contactsJoined
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloa
|
|||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func renderIcon(name: String, backgroundColors: [UIColor]? = nil) -> UIImage? {
|
private func renderIcon(name: String, scaleFactor: CGFloat = 1.0, backgroundColors: [UIColor]? = nil) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
context.clear(bounds)
|
context.clear(bounds)
|
||||||
@ -44,12 +44,14 @@ private func renderIcon(name: String, backgroundColors: [UIColor]? = nil) -> UII
|
|||||||
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: size.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
|
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: size.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
|
||||||
|
|
||||||
context.resetClip()
|
context.resetClip()
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: name), color: .white)?.cgImage {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: name), color: .white), let cgImage = image.cgImage {
|
||||||
context.draw(image, in: bounds)
|
let imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
|
||||||
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (bounds.width - imageSize.width) * 0.5, y: (bounds.height - imageSize.height) * 0.5), size: imageSize))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let image = UIImage(bundleImageName: name)?.cgImage {
|
if let image = UIImage(bundleImageName: name), let cgImage = image.cgImage {
|
||||||
context.draw(image, in: bounds)
|
let imageSize = CGSize(width: image.size.width * scaleFactor, height: image.size.height * scaleFactor)
|
||||||
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (bounds.width - imageSize.width) * 0.5, y: (bounds.height - imageSize.height) * 0.5), size: imageSize))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -70,10 +72,11 @@ public struct PresentationResourcesSettings {
|
|||||||
public static let language = renderIcon(name: "Settings/Menu/Language")
|
public static let language = renderIcon(name: "Settings/Menu/Language")
|
||||||
public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon")
|
public static let deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon")
|
||||||
public static let powerSaving = renderIcon(name: "Settings/Menu/PowerSaving")
|
public static let powerSaving = renderIcon(name: "Settings/Menu/PowerSaving")
|
||||||
public static let stories = renderIcon(name: "Settings/Menu/Stories")
|
public static let stories = renderIcon(name: "Premium/Perk/Stories", scaleFactor: 0.97, backgroundColors: [UIColor(rgb: 0x5856D6)])
|
||||||
public static let premiumGift = renderIcon(name: "Settings/Menu/Gift")
|
public static let premiumGift = renderIcon(name: "Settings/Menu/Gift")
|
||||||
public static let business = renderIcon(name: "Settings/Menu/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)])
|
public static let business = renderIcon(name: "Settings/Menu/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)])
|
||||||
public static let myProfile = renderIcon(name: "Settings/Menu/Profile")
|
public static let myProfile = renderIcon(name: "Settings/Menu/Profile")
|
||||||
|
public static let reactions = renderIcon(name: "Settings/Menu/Reactions")
|
||||||
|
|
||||||
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
@ -598,6 +598,7 @@ private final class PeerInfoInteraction {
|
|||||||
let openBioContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
let openBioContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||||
let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||||
let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||||
|
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||||
let getController: () -> ViewController?
|
let getController: () -> ViewController?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -664,6 +665,7 @@ private final class PeerInfoInteraction {
|
|||||||
openBioContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
openBioContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||||
openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||||
openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||||
|
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||||
getController: @escaping () -> ViewController?
|
getController: @escaping () -> ViewController?
|
||||||
) {
|
) {
|
||||||
self.openUsername = openUsername
|
self.openUsername = openUsername
|
||||||
@ -729,6 +731,7 @@ private final class PeerInfoInteraction {
|
|||||||
self.openBioContextMenu = openBioContextMenu
|
self.openBioContextMenu = openBioContextMenu
|
||||||
self.openWorkingHoursContextMenu = openWorkingHoursContextMenu
|
self.openWorkingHoursContextMenu = openWorkingHoursContextMenu
|
||||||
self.openBusinessLocationContextMenu = openBusinessLocationContextMenu
|
self.openBusinessLocationContextMenu = openBusinessLocationContextMenu
|
||||||
|
self.openBirthdayContextMenu = openBirthdayContextMenu
|
||||||
self.getController = getController
|
self.getController = getController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1188,7 +1191,7 @@ private enum InfoSection: Int, CaseIterable {
|
|||||||
case peerMembers
|
case peerMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] {
|
private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] {
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -1210,6 +1213,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
let businessLocationContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in
|
let businessLocationContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in
|
||||||
interaction.openBusinessLocationContextMenu(node, gesture)
|
interaction.openBusinessLocationContextMenu(node, gesture)
|
||||||
}
|
}
|
||||||
|
let birthdayContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in
|
||||||
|
interaction.openBirthdayContextMenu(node, gesture)
|
||||||
|
}
|
||||||
|
|
||||||
if let user = data.peer as? TelegramUser {
|
if let user = data.peer as? TelegramUser {
|
||||||
if !callMessages.isEmpty {
|
if !callMessages.isEmpty {
|
||||||
@ -1293,13 +1299,21 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
|
||||||
hasBirthdayToday = true
|
hasBirthdayToday = true
|
||||||
}
|
}
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: hasBirthdayToday ? { _, _ in
|
|
||||||
|
var birthdayAction: ((ASDisplayNode, Promise<Bool>?) -> Void)?
|
||||||
|
if isMyProfile {
|
||||||
|
birthdayAction = { node, _ in
|
||||||
|
birthdayContextAction(node, nil, nil)
|
||||||
|
}
|
||||||
|
} else if hasBirthdayToday {
|
||||||
|
birthdayAction = { _, _ in
|
||||||
|
interaction.openPremiumGift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 400, context: context, label: hasBirthdayToday ? presentationData.strings.UserInfo_BirthdayToday : presentationData.strings.UserInfo_Birthday, text: stringForCompactBirthday(birthday, strings: presentationData.strings, showAge: true), textColor: .primary, leftIcon: hasBirthdayToday ? .birthday : nil, icon: hasBirthdayToday ? .premiumGift : nil, action: birthdayAction, longTapAction: nil, iconAction: {
|
||||||
interaction.openPremiumGift()
|
interaction.openPremiumGift()
|
||||||
} : nil, longTapAction: { sourceNode in
|
}, contextAction: birthdayContextAction, requestLayout: {
|
||||||
interaction.openPeerInfoContextMenu(.birthday, sourceNode, nil)
|
|
||||||
}, iconAction: {
|
|
||||||
interaction.openPremiumGift()
|
|
||||||
}, contextAction: nil, requestLayout: {
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1312,7 +1326,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
interaction.requestLayout(false)
|
interaction.requestLayout(false)
|
||||||
}))
|
}))
|
||||||
} else if let about = cachedData.about, !about.isEmpty {
|
} else if let about = cachedData.about, !about.isEmpty {
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: isMyProfile ? { node, _ in
|
||||||
|
bioContextAction(node, nil, nil)
|
||||||
|
} : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
||||||
interaction.requestLayout(false)
|
interaction.requestLayout(false)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1546,7 +1562,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
enabledEntities = enabledPrivateBioEntities
|
enabledEntities = enabledPrivateBioEntities
|
||||||
}
|
}
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: isMyProfile ? { node, _ in
|
||||||
|
bioContextAction(node, nil, nil)
|
||||||
|
} : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
||||||
interaction.requestLayout(true)
|
interaction.requestLayout(true)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1596,7 +1614,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let aboutText = aboutText {
|
if let aboutText = aboutText {
|
||||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: isMyProfile ? { node, _ in
|
||||||
|
bioContextAction(node, nil, nil)
|
||||||
|
} : nil, linkItemAction: bioLinkAction, contextAction: bioContextAction, requestLayout: {
|
||||||
interaction.requestLayout(true)
|
interaction.requestLayout(true)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -2780,6 +2800,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.openBusinessLocationContextMenu(node: node, gesture: gesture)
|
self.openBusinessLocationContextMenu(node: node, gesture: gesture)
|
||||||
|
}, openBirthdayContextMenu: { [weak self] node, gesture in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openBirthdayContextMenu(node: node, gesture: gesture)
|
||||||
},
|
},
|
||||||
getController: { [weak self] in
|
getController: { [weak self] in
|
||||||
return self?.controller
|
return self?.controller
|
||||||
@ -7160,6 +7185,65 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
self.controller?.present(contextController, in: .window(.root))
|
self.controller?.present(contextController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func openBirthdayContextMenu(node: ASDisplayNode, gesture: ContextGesture?) {
|
||||||
|
guard let sourceNode = node as? ContextExtractedContentContainingNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let cachedData = self.data?.cachedData else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var birthday: TelegramBirthday?
|
||||||
|
if let cachedData = cachedData as? CachedUserData {
|
||||||
|
birthday = cachedData.birthday
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let birthday else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let copyAction = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let presentationData = self.presentationData
|
||||||
|
let text = stringForCompactBirthday(birthday, strings: presentationData.strings)
|
||||||
|
|
||||||
|
UIPasteboard.general.string = text
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: "Birthday copied."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||||
|
}
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
if self.isMyProfile {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Edit Birthday", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
|
c.dismiss {
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = self.state.withIsEditingBirthDate(true)
|
||||||
|
self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil)
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Copy", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||||
|
c.dismiss {
|
||||||
|
copyAction()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
|
||||||
|
let actions = ContextController.Items(content: .list(items))
|
||||||
|
|
||||||
|
let contextController = ContextController(presentationData: self.presentationData, source: .extracted(PeerInfoContextExtractedContentSource(sourceNode: sourceNode)), items: .single(actions), gesture: gesture)
|
||||||
|
self.controller?.present(contextController, in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
private func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?, progress: Promise<Bool>?) {
|
private func openPhone(value: String, node: ASDisplayNode, gesture: ContextGesture?, progress: Promise<Bool>?) {
|
||||||
guard let sourceNode = node as? ContextExtractedContentContainingNode else {
|
guard let sourceNode = node as? ContextExtractedContentContainingNode else {
|
||||||
return
|
return
|
||||||
@ -10826,7 +10910,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
insets.left += sectionInset
|
insets.left += sectionInset
|
||||||
insets.right += sectionInset
|
insets.right += sectionInset
|
||||||
|
|
||||||
let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat)
|
let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat, isMyProfile: self.isMyProfile)
|
||||||
|
|
||||||
contentHeight += headerHeight
|
contentHeight += headerHeight
|
||||||
if !((self.isSettings || self.isMyProfile) && self.state.isEditing) {
|
if !((self.isSettings || self.isMyProfile) && self.state.isEditing) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user