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?
|
||||
}
|
||||
|
||||
open class ItemListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||
private weak var controller: ItemListController?
|
||||
|
||||
private var _ready = ValuePromise<Bool>()
|
||||
@ -495,7 +495,7 @@ open class ItemListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
return directions
|
||||
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
||||
panRecognizer.delegate = self
|
||||
panRecognizer.delegate = self.wrappedGestureRecognizerDelegate
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = true
|
||||
self.panRecognizer = panRecognizer
|
||||
|
@ -14,10 +14,16 @@ public enum ItemListSwitchItemNodeType {
|
||||
}
|
||||
|
||||
public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||
public enum TextColor {
|
||||
case primary
|
||||
case accent
|
||||
}
|
||||
|
||||
let presentationData: ItemListPresentationData
|
||||
let icon: UIImage?
|
||||
let title: String
|
||||
let text: String?
|
||||
let textColor: TextColor
|
||||
let titleBadgeComponent: AnyComponent<Empty>?
|
||||
let value: Bool
|
||||
let type: ItemListSwitchItemNodeType
|
||||
@ -31,13 +37,15 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||
let style: ItemListStyle
|
||||
let updated: (Bool) -> Void
|
||||
let activatedWhileDisabled: () -> Void
|
||||
let action: (() -> Void)?
|
||||
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.icon = icon
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.titleBadgeComponent = titleBadgeComponent
|
||||
self.value = value
|
||||
self.type = type
|
||||
@ -51,6 +59,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||
self.style = style
|
||||
self.updated = updated
|
||||
self.activatedWhileDisabled = activatedWhileDisabled
|
||||
self.action = action
|
||||
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 {
|
||||
@ -132,7 +152,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
private var textNode: TextNode?
|
||||
private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl
|
||||
private let switchGestureNode: ASDisplayNode
|
||||
private var disabledOverlayNode: ASDisplayNode?
|
||||
@ -168,11 +188,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.anchorPoint = CGPoint()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
switch type {
|
||||
case .regular:
|
||||
self.switchNode = SwitchNode()
|
||||
@ -190,7 +208,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.switchNode)
|
||||
self.addSubnode(self.switchGestureNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
@ -230,7 +247,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let itemSeparatorColor: UIColor
|
||||
|
||||
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?
|
||||
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 (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)
|
||||
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
|
||||
textLayoutAndApply = (textLayout, textApply)
|
||||
}
|
||||
|
||||
if !item.enabled {
|
||||
@ -294,6 +321,13 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] animated in
|
||||
if let strongSelf = self {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if animated {
|
||||
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
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))
|
||||
@ -309,7 +343,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.activateArea.accessibilityTraits = accessibilityTraits
|
||||
|
||||
if let icon = item.icon {
|
||||
var iconTransition = transition
|
||||
if strongSelf.iconNode.supernode == nil {
|
||||
iconTransition = .immediate
|
||||
strongSelf.addSubnode(strongSelf.iconNode)
|
||||
}
|
||||
if updateIcon {
|
||||
@ -321,19 +357,12 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
} else {
|
||||
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 {
|
||||
strongSelf.iconNode.image = nil
|
||||
strongSelf.iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if animated {
|
||||
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
if let currentDisabledOverlayNode = currentDisabledOverlayNode {
|
||||
if currentDisabledOverlayNode != strongSelf.disabledOverlayNode {
|
||||
strongSelf.disabledOverlayNode = currentDisabledOverlayNode
|
||||
@ -367,7 +396,6 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = textApply()
|
||||
|
||||
switch item.style {
|
||||
case .plain:
|
||||
@ -383,7 +411,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
if strongSelf.maskNode.supernode != nil {
|
||||
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:
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
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.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)
|
||||
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.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.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0))
|
||||
transition.updateFrame(node: 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.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 strongSelf.switchNode.bounds.size.width.isZero {
|
||||
@ -437,7 +489,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
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
|
||||
if switchView.isOn != item.value {
|
||||
switchView.setOn(item.value, animated: animated)
|
||||
@ -447,6 +499,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.switchGestureNode.isHidden = item.enableInteractiveChanges && item.enabled
|
||||
|
||||
if item.displayLocked {
|
||||
var lockedIconTransition = transition
|
||||
var updateLockedIconImage = false
|
||||
if let _ = updatedTheme {
|
||||
updateLockedIconImage = true
|
||||
@ -456,6 +509,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
if let current = strongSelf.lockedIconNode {
|
||||
lockedIconNode = current
|
||||
} else {
|
||||
lockedIconTransition = .immediate
|
||||
updateLockedIconImage = true
|
||||
lockedIconNode = ASImageNode()
|
||||
strongSelf.lockedIconNode = lockedIconNode
|
||||
@ -469,7 +523,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let switchFrame = strongSelf.switchNode.frame
|
||||
|
||||
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 {
|
||||
strongSelf.lockedIconNode = nil
|
||||
@ -492,17 +546,19 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
||||
containerSize: contentSize
|
||||
)
|
||||
if let view = componentView.view {
|
||||
var titleBadgeTransition = transition
|
||||
if view.superview == nil {
|
||||
titleBadgeTransition = .immediate
|
||||
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 {
|
||||
strongSelf.titleBadgeComponentView = nil
|
||||
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 openPeerCategory: (NotificationsPeerCategory) -> Void
|
||||
let openReactions: () -> Void
|
||||
|
||||
let updateInAppSounds: (Bool) -> Void
|
||||
let updateInAppVibration: (Bool) -> Void
|
||||
@ -80,7 +81,7 @@ private final class NotificationsAndSoundsArguments {
|
||||
|
||||
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.presentController = presentController
|
||||
self.pushController = pushController
|
||||
@ -88,6 +89,7 @@ private final class NotificationsAndSoundsArguments {
|
||||
self.authorizeNotifications = authorizeNotifications
|
||||
self.suppressWarning = suppressWarning
|
||||
self.openPeerCategory = openPeerCategory
|
||||
self.openReactions = openReactions
|
||||
self.updateInAppSounds = updateInAppSounds
|
||||
self.updateInAppVibration = updateInAppVibration
|
||||
self.updateInAppPreviews = updateInAppPreviews
|
||||
@ -145,6 +147,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
case groupChats(PresentationTheme, String, String, String)
|
||||
case channels(PresentationTheme, String, String, String)
|
||||
case stories(PresentationTheme, String, String, String)
|
||||
case reactions(PresentationTheme, String, String, String)
|
||||
|
||||
case inAppHeader(PresentationTheme, String)
|
||||
case inAppSounds(PresentationTheme, String, Bool)
|
||||
@ -171,7 +174,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
return NotificationsAndSoundsSection.accounts.rawValue
|
||||
case .permissionInfo, .permissionEnable:
|
||||
return NotificationsAndSoundsSection.permission.rawValue
|
||||
case .categoriesHeader, .privateChats, .groupChats, .channels, .stories:
|
||||
case .categoriesHeader, .privateChats, .groupChats, .channels, .stories, .reactions:
|
||||
return NotificationsAndSoundsSection.categories.rawValue
|
||||
case .inAppHeader, .inAppSounds, .inAppVibrate, .inAppPreviews:
|
||||
return NotificationsAndSoundsSection.inApp.rawValue
|
||||
@ -208,6 +211,8 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
return 8
|
||||
case .stories:
|
||||
return 9
|
||||
case .reactions:
|
||||
return 10
|
||||
case .inAppHeader:
|
||||
return 14
|
||||
case .inAppSounds:
|
||||
@ -326,6 +331,12 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
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):
|
||||
if case let .inAppHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -451,9 +462,13 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
arguments.openPeerCategory(.channel)
|
||||
})
|
||||
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)
|
||||
})
|
||||
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):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
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))
|
||||
|
||||
//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(.inAppSounds(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsSounds, inAppSettings.playSounds))
|
||||
entries.append(.inAppVibrate(presentationData.theme, presentationData.strings.Notifications_InAppNotificationsVibrate, inAppSettings.vibrate))
|
||||
@ -655,6 +695,10 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
|
||||
})
|
||||
}, focusOnItemTag: nil))
|
||||
})
|
||||
}, openReactions: {
|
||||
pushControllerImpl?(reactionNotificationSettingsController(
|
||||
context: context
|
||||
))
|
||||
}, updateInAppSounds: { value in
|
||||
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
|
@ -92,6 +92,7 @@ public enum NotificationsPeerCategoryEntryTag: ItemListItemTag {
|
||||
|
||||
private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
enum StableId: Hashable {
|
||||
case enableHeader
|
||||
case enable
|
||||
case enableImportant
|
||||
case importantInfo
|
||||
@ -104,6 +105,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
case removeAllExceptions
|
||||
}
|
||||
|
||||
case enableHeader(String)
|
||||
case enable(PresentationTheme, String, Bool)
|
||||
case enableImportant(PresentationTheme, String, Bool)
|
||||
case importantInfo(PresentationTheme, String)
|
||||
@ -118,7 +120,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .enable, .enableImportant, .importantInfo:
|
||||
case .enableHeader, .enable, .enableImportant, .importantInfo:
|
||||
return NotificationsPeerCategorySection.enable.rawValue
|
||||
case .optionsHeader, .previews, .sound:
|
||||
return NotificationsPeerCategorySection.options.rawValue
|
||||
@ -129,6 +131,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: StableId {
|
||||
switch self {
|
||||
case .enableHeader:
|
||||
return .enableHeader
|
||||
case .enable:
|
||||
return .enable
|
||||
case .enableImportant:
|
||||
@ -154,6 +158,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
|
||||
var sortIndex: Int32 {
|
||||
switch self {
|
||||
case .enableHeader:
|
||||
return -1
|
||||
case .enable:
|
||||
return 0
|
||||
case .enableImportant:
|
||||
@ -192,6 +198,12 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
|
||||
static func ==(lhs: NotificationsPeerCategoryEntry, rhs: NotificationsPeerCategoryEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .enableHeader(text):
|
||||
if case .enableHeader(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .enable(lhsTheme, lhsText, lhsValue):
|
||||
if case let .enable(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
@ -263,6 +275,8 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! NotificationsPeerCategoryControllerArguments
|
||||
switch self {
|
||||
case let .enableHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .enable(_, text, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateEnabled(updatedValue)
|
||||
@ -330,6 +344,9 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
}
|
||||
|
||||
if case .stories = category {
|
||||
//TODO:localize
|
||||
entries.append(.enableHeader("NOTIFY ME ABOUT..."))
|
||||
|
||||
var allEnabled = false
|
||||
var importantEnabled = false
|
||||
|
||||
@ -345,16 +362,16 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
importantEnabled = true
|
||||
}
|
||||
|
||||
entries.append(.enable(presentationData.theme, presentationData.strings.NotificationSettings_Stories_ShowAll, allEnabled))
|
||||
entries.append(.enable(presentationData.theme, "New Stories", 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))
|
||||
}
|
||||
|
||||
if notificationSettings.enabled || !notificationExceptions.isEmpty {
|
||||
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)))
|
||||
}
|
||||
} 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 groupChats: MessageNotificationSettings
|
||||
public var channels: MessageNotificationSettings
|
||||
public var reactionSettings: PeerReactionNotificationSettings
|
||||
public var contactsJoined: Bool
|
||||
|
||||
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.groupChats = groupChats
|
||||
self.channels = channels
|
||||
self.reactionSettings = reactionSettings
|
||||
self.contactsJoined = contactsJoined
|
||||
}
|
||||
|
||||
@ -61,6 +63,7 @@ public struct GlobalNotificationSettingsSet: Codable, Equatable {
|
||||
self.privateChats = try container.decode(MessageNotificationSettings.self, forKey: "p")
|
||||
self.groupChats = try container.decode(MessageNotificationSettings.self, forKey: "g")
|
||||
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
|
||||
}
|
||||
@ -71,6 +74,7 @@ public struct GlobalNotificationSettingsSet: Codable, Equatable {
|
||||
try container.encode(self.privateChats, forKey: "p")
|
||||
try container.encode(self.groupChats, forKey: "g")
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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 let muteState: PeerMuteState
|
||||
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 groupChats: CategorySettings
|
||||
public var channels: CategorySettings
|
||||
public var reactionSettings: ReactionSettings
|
||||
public var contactsJoined: Bool
|
||||
|
||||
public init(
|
||||
privateChats: CategorySettings,
|
||||
groupChats: CategorySettings,
|
||||
channels: CategorySettings,
|
||||
reactionSettings: ReactionSettings,
|
||||
contactsJoined: Bool
|
||||
) {
|
||||
self.privateChats = privateChats
|
||||
self.groupChats = groupChats
|
||||
self.channels = channels
|
||||
self.reactionSettings = reactionSettings
|
||||
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 {
|
||||
init(_ globalNotificationSettings: GlobalNotificationSettingsSet) {
|
||||
self.init(
|
||||
privateChats: CategorySettings(globalNotificationSettings.privateChats),
|
||||
groupChats: CategorySettings(globalNotificationSettings.groupChats),
|
||||
channels: CategorySettings(globalNotificationSettings.channels),
|
||||
reactionSettings: ReactionSettings(globalNotificationSettings.reactionSettings),
|
||||
contactsJoined: globalNotificationSettings.contactsJoined
|
||||
)
|
||||
}
|
||||
@ -647,6 +685,7 @@ public extension EngineGlobalNotificationSettings {
|
||||
privateChats: self.privateChats._asMessageNotificationSettings(),
|
||||
groupChats: self.groupChats._asMessageNotificationSettings(),
|
||||
channels: self.channels._asMessageNotificationSettings(),
|
||||
reactionSettings: self.reactionSettings._asReactionSettings(),
|
||||
contactsJoined: self.contactsJoined
|
||||
)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloa
|
||||
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
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
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.resetClip()
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: name), color: .white)?.cgImage {
|
||||
context.draw(image, in: bounds)
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: name), color: .white), let cgImage = image.cgImage {
|
||||
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 {
|
||||
if let image = UIImage(bundleImageName: name)?.cgImage {
|
||||
context.draw(image, in: bounds)
|
||||
if let image = UIImage(bundleImageName: name), let cgImage = image.cgImage {
|
||||
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 deleteAccount = renderIcon(name: "Chat/Info/GroupRemovedIcon")
|
||||
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 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 reactions = renderIcon(name: "Settings/Menu/Reactions")
|
||||
|
||||
public static let premium = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
@ -598,6 +598,7 @@ private final class PeerInfoInteraction {
|
||||
let openBioContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||
let openWorkingHoursContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||
let openBusinessLocationContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||
let openBirthdayContextMenu: (ASDisplayNode, ContextGesture?) -> Void
|
||||
let getController: () -> ViewController?
|
||||
|
||||
init(
|
||||
@ -664,6 +665,7 @@ private final class PeerInfoInteraction {
|
||||
openBioContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||
openWorkingHoursContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||
openBusinessLocationContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||
openBirthdayContextMenu: @escaping (ASDisplayNode, ContextGesture?) -> Void,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.openUsername = openUsername
|
||||
@ -729,6 +731,7 @@ private final class PeerInfoInteraction {
|
||||
self.openBioContextMenu = openBioContextMenu
|
||||
self.openWorkingHoursContextMenu = openWorkingHoursContextMenu
|
||||
self.openBusinessLocationContextMenu = openBusinessLocationContextMenu
|
||||
self.openBirthdayContextMenu = openBirthdayContextMenu
|
||||
self.getController = getController
|
||||
}
|
||||
}
|
||||
@ -1188,7 +1191,7 @@ private enum InfoSection: Int, CaseIterable {
|
||||
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 {
|
||||
return []
|
||||
}
|
||||
@ -1210,6 +1213,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
let businessLocationContextAction: (ASDisplayNode, ContextGesture?, CGPoint?) -> Void = { node, gesture, _ in
|
||||
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 !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) {
|
||||
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()
|
||||
} : nil, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.birthday, sourceNode, nil)
|
||||
}, iconAction: {
|
||||
interaction.openPremiumGift()
|
||||
}, contextAction: nil, requestLayout: {
|
||||
}, contextAction: birthdayContextAction, requestLayout: {
|
||||
}))
|
||||
}
|
||||
|
||||
@ -1312,7 +1326,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
interaction.requestLayout(false)
|
||||
}))
|
||||
} 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)
|
||||
}))
|
||||
}
|
||||
@ -1546,7 +1562,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
if case .group = channel.info {
|
||||
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)
|
||||
}))
|
||||
}
|
||||
@ -1596,7 +1614,9 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
}
|
||||
|
||||
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)
|
||||
}))
|
||||
}
|
||||
@ -2780,6 +2800,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
return
|
||||
}
|
||||
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
|
||||
return self?.controller
|
||||
@ -7160,6 +7185,65 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
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>?) {
|
||||
guard let sourceNode = node as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
@ -10826,7 +10910,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
insets.left += 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
|
||||
if !((self.isSettings || self.isMyProfile) && self.state.isEditing) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user