Added new call P2P settings

This commit is contained in:
Ilya Laktyushin 2018-11-03 23:32:29 +04:00
parent 3cb29347d1
commit ef533b02a6
28 changed files with 426 additions and 207 deletions

View File

@ -682,6 +682,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
if let continueActionButtonLayout = continueActionButtonLayout { if let continueActionButtonLayout = continueActionButtonLayout {
let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right) let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right)
actionButtonSizeAndApply = (size, apply) actionButtonSizeAndApply = (size, apply)
adjustedBoundingSize.width = max(adjustedBoundingSize.width, insets.left + size.width + insets.right)
adjustedBoundingSize.height += 7.0 + size.height adjustedBoundingSize.height += 7.0 + size.height
} }

View File

@ -22,7 +22,7 @@ final class InstantPageAnchorItem: InstantPageItem {
func drawInTile(context: CGContext) { func drawInTile(context: CGContext) {
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return nil return nil
} }

View File

@ -25,7 +25,7 @@ final class InstantPageArticleItem: InstantPageItem {
self.webpageId = webpageId self.webpageId = webpageId
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageArticleNode(account: account, webPage: self.webPage, strings: strings, theme: theme, title: self.title, description: self.description, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl) return InstantPageArticleNode(account: account, webPage: self.webPage, strings: strings, theme: theme, title: self.title, description: self.description, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
} }

View File

@ -18,7 +18,7 @@ final class InstantPageAudioItem: InstantPageItem {
self.medias = [media] self.medias = [media]
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageAudioNode(account: account, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia) return InstantPageAudioNode(account: account, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
} }

View File

@ -43,8 +43,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var visibleTiles: [Int: InstantPageTileNode] = [:] var visibleTiles: [Int: InstantPageTileNode] = [:]
var visibleItemsWithNodes: [Int: InstantPageNode] = [:] var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
var currentWebEmbedHeights: [Int : Int] = [:] var currentWebEmbedHeights: [Int : CGFloat] = [:]
var currentOpenedDetails: [Int : Bool]? = [:] var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var previousContentOffset: CGPoint? var previousContentOffset: CGPoint?
var isDeceleratingBecauseOfDragging = false var isDeceleratingBecauseOfDragging = false
@ -196,7 +197,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let strongSelf = self { if let strongSelf = self {
if let currentLayout = strongSelf.currentLayout { if let currentLayout = strongSelf.currentLayout {
for item in currentLayout.items { for item in currentLayout.items {
if item.frame.contains(point) { let frame = strongSelf.effectiveFrameForItem(item)
if frame.contains(point) {
if item is InstantPagePeerReferenceItem { if item is InstantPagePeerReferenceItem {
return .fail return .fail
} else if item is InstantPageAudioItem { } else if item is InstantPageAudioItem {
@ -319,10 +321,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width) let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width)
var currentDetailsItems: [InstantPageDetailsItem] = []
var currentLayoutItemsWithNodes: [InstantPageItem] = [] var currentLayoutItemsWithNodes: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int : Int] = [:] var distanceThresholdGroupCount: [Int : Int] = [:]
var openedDetails: [Int : Bool] = [:] var expandedDetails: [Int : Bool] = [:]
var itemIndex = -1 var itemIndex = -1
for item in currentLayout.items { for item in currentLayout.items {
@ -340,26 +343,29 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
if let detailsItem = item as? InstantPageDetailsItem { if let detailsItem = item as? InstantPageDetailsItem {
openedDetails[itemIndex] = detailsItem.open expandedDetails[itemIndex] = detailsItem.initiallyExpanded
} }
} }
if let item = item as? InstantPageDetailsItem {
currentDetailsItems.append(item)
}
} }
if self.currentOpenedDetails == nil { if self.currentExpandedDetails == nil {
self.currentOpenedDetails = openedDetails self.currentExpandedDetails = expandedDetails
} }
self.currentLayout = currentLayout self.currentLayout = currentLayout
self.currentLayoutTiles = currentLayoutTiles self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes
self.currentDetailsItems = currentDetailsItems
self.distanceThresholdGroupCount = distanceThresholdGroupCount self.distanceThresholdGroupCount = distanceThresholdGroupCount
self.scrollNode.view.contentSize = currentLayout.contentSize self.scrollNode.view.contentSize = currentLayout.contentSize
self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0)) self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0))
} }
func updateVisibleItems() { func updateVisibleItems(animated: Bool = false) {
guard let theme = self.theme else { guard let theme = self.theme else {
return return
} }
@ -370,6 +376,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let visibleBounds = self.scrollNode.view.bounds let visibleBounds = self.scrollNode.view.bounds
var topNode: ASDisplayNode? var topNode: ASDisplayNode?
let topTileNode = topNode
if let scrollSubnodes = self.scrollNode.subnodes { if let scrollSubnodes = self.scrollNode.subnodes {
for node in scrollSubnodes.reversed() { for node in scrollSubnodes.reversed() {
if let node = node as? InstantPageTileNode { if let node = node as? InstantPageTileNode {
@ -379,34 +386,27 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
var tileIndex = -1 var collapseOffset: CGFloat = 0.0
for tile in self.currentLayoutTiles { let transition: ContainedViewLayoutTransition
tileIndex += 1 if animated {
var tileVisibleFrame = tile.frame transition = .animated(duration: 0.3, curve: .spring)
tileVisibleFrame.origin.y -= 400.0 } else {
tileVisibleFrame.size.height += 400.0 * 2.0 transition = .immediate
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
tileNode.frame = tile.frame
if let topNode = topNode {
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.scrollNode.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
}
}
} }
var collapseOffset: CGFloat = 0.0
var itemIndex = -1 var itemIndex = -1
var embedIndex = -1
var detailsIndex = -1
for item in self.currentLayoutItemsWithNodes { for item in self.currentLayoutItemsWithNodes {
itemIndex += 1 itemIndex += 1
if item is InstantPageWebEmbedItem {
embedIndex += 1
}
if item is InstantPageDetailsItem {
detailsIndex += 1
}
var itemThreshold: CGFloat = 0.0 var itemThreshold: CGFloat = 0.0
if let group = item.distanceThresholdGroup() { if let group = item.distanceThresholdGroup() {
var count: Int = 0 var count: Int = 0
@ -421,8 +421,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
thresholdedItemFrame.origin.y -= itemThreshold thresholdedItemFrame.origin.y -= itemThreshold
thresholdedItemFrame.size.height += itemThreshold * 2.0 thresholdedItemFrame.size.height += itemThreshold * 2.0
if let opened = self.currentOpenedDetails?[itemIndex], !opened { if let expanded = self.currentExpandedDetails?[detailsIndex], !expanded {
collapseOffset = itemFrame.height - 44.0 collapseOffset += itemFrame.height - 44.0
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: 44.0)) itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: 44.0))
} }
@ -439,16 +439,19 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
if itemNode == nil { if itemNode == nil {
let itemIndex = itemIndex
let embedIndex = embedIndex
let detailsIndex = detailsIndex
if let itemNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in if let itemNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
self?.openMedia(media) self?.openMedia(media)
}, openPeer: { [weak self] peerId in }, openPeer: { [weak self] peerId in
self?.openPeer(peerId) self?.openPeer(peerId)
}, openUrl: { [weak self] url in }, openUrl: { [weak self] url in
self?.openUrl(url) self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] key, height in }, updateWebEmbedHeight: { [weak self] height in
self?.updateWebEmbedHeight(key, height) self?.updateWebEmbedHeight(embedIndex, height)
}, updateDetailsOpened: { [weak self] key, opened in }, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsOpened(key, opened) self?.updateDetailsExpanded(detailsIndex, expanded)
}) { }) {
itemNode.frame = itemFrame itemNode.frame = itemFrame
if let topNode = topNode { if let topNode = topNode {
@ -461,12 +464,62 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} else { } else {
if (itemNode as! ASDisplayNode).frame != itemFrame { if (itemNode as! ASDisplayNode).frame != itemFrame {
let previousFrame = (itemNode as! ASDisplayNode).frame
(itemNode as! ASDisplayNode).frame = itemFrame (itemNode as! ASDisplayNode).frame = itemFrame
transition.animateFrame(node: (itemNode as! ASDisplayNode), from: previousFrame)
} }
} }
} }
} }
topNode = topTileNode
var tileIndex = -1
for tile in self.currentLayoutTiles {
tileIndex += 1
var tileFrame = tile.frame
if tileIndex > 0 {
tileFrame = tileFrame.offsetBy(dx: 0.0, dy: -collapseOffset)
}
var tileVisibleFrame = tileFrame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) || animated {
visibleTileIndices.insert(tileIndex)
if visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
tileNode.frame = tile.frame
if let topNode = topNode {
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.scrollNode.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
} else {
if visibleTiles[tileIndex]!.frame != tileFrame {
let previousFrame = visibleTiles[tileIndex]!.frame
visibleTiles[tileIndex]!.frame = tileFrame
transition.animateFrame(node: visibleTiles[tileIndex]!, from: previousFrame)
}
}
}
}
if let currentLayout = self.currentLayout, collapseOffset > 0.0 {
let effectiveContentHeight = currentLayout.contentSize.height - collapseOffset
if effectiveContentHeight != self.scrollNode.view.contentSize.height {
transition.animateView {
self.scrollNode.view.contentSize = CGSize(width: currentLayout.contentSize.width, height: effectiveContentHeight)
}
let previousFrame = self.scrollNodeFooter.frame
self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: effectiveContentHeight), size: CGSize(width: previousFrame.width, height: 2000.0))
transition.animateFrame(node: self.scrollNodeFooter, from: previousFrame)
}
}
var removeTileIndices: [Int] = [] var removeTileIndices: [Int] = []
for (index, tileNode) in self.visibleTiles { for (index, tileNode) in self.visibleTiles {
if !visibleTileIndices.contains(index) { if !visibleTileIndices.contains(index) {
@ -644,15 +697,40 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return contentOffset return contentOffset
} }
private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
let layoutOrigin = item.frame.origin
var origin = layoutOrigin
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
if !expanded && layoutOrigin.y >= item.frame.maxY {
let offset = 44.0 - item.frame.height
origin.y += offset
}
}
if let item = item as? InstantPageDetailsItem {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: expanded ? item.frame.height : 44.0))
} else {
return CGRect(origin: origin, size: item.frame.size)
}
}
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if let currentLayout = self.currentLayout { if let currentLayout = self.currentLayout {
for item in currentLayout.items { for item in currentLayout.items {
if let item = item as? InstantPageTextItem, item.selectable, item.frame.contains(location) { let frame = self.effectiveFrameForItem(item)
return (item, CGPoint()) if frame.contains(location) {
} else if let item = item as? InstantPageTableItem, item.frame.contains(location) { if let item = item as? InstantPageTextItem, item.selectable {
let contentOffset = tableContentOffset(item: item) return (item, CGPoint())
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -item.frame.minX + contentOffset.x, dy: -item.frame.minY)) { } else if let item = item as? InstantPageTableItem {
return (textItem, item.frame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y)) let contentOffset = tableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -item.frame.minX + contentOffset.x, dy: -item.frame.minY)) {
return (textItem, item.frame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
}
} else if let item = item as? InstantPageDetailsItem {
} }
} }
} }
@ -706,7 +784,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
])]) ])])
self.present(actionSheet, nil) self.present(actionSheet, nil)
} else if let (item, parentOffset) = self.textItemAtLocation(location) { } else if let (item, parentOffset) = self.textItemAtLocation(location) {
let textNodeFrame = item.frame let textNodeFrame = effectiveFrameForItem(item)
var itemRects = item.lineRects() var itemRects = item.lineRects()
for i in 0 ..< itemRects.count { for i in 0 ..< itemRects.count {
itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textNodeFrame.minX, dy: parentOffset.y + textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0) itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textNodeFrame.minX, dy: parentOffset.y + textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
@ -812,7 +890,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
case let .withBotStartPayload(botStart): case let .withBotStartPayload(botStart):
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), botStart: botStart) navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peerId), botStart: botStart, keepStack: .always)
} }
case .info: case .info:
let _ = (strongSelf.account.postbox.loadedPeerWithId(peerId) let _ = (strongSelf.account.postbox.loadedPeerWithId(peerId)
@ -910,13 +988,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
private func updateWebEmbedHeight(_ key: Int, _ height: Int) { private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) {
let currentHeight = self.currentWebEmbedHeights[key] let currentHeight = self.currentWebEmbedHeights[index]
if height != currentHeight { if height != currentHeight {
if let currentHeight = currentHeight, currentHeight > height { if let currentHeight = currentHeight, currentHeight > height {
return return
} }
self.currentWebEmbedHeights[key] = height self.currentWebEmbedHeights[index] = height
let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue())) let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
@ -928,13 +1006,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
} }
} }
private func updateDetailsOpened(_ index: Int, _ opened: Bool) { private func updateDetailsExpanded(_ index: Int, _ expanded: Bool) {
if var currentOpenedDetails = self.currentOpenedDetails { if var currentExpandedDetails = self.currentExpandedDetails {
currentOpenedDetails[index] = opened currentExpandedDetails[index] = expanded
self.currentOpenedDetails = currentOpenedDetails self.currentExpandedDetails = currentExpandedDetails
} }
//self.updateLayout() self.updateVisibleItems(animated: true)
self.updateVisibleItems()
} }
private func presentSettings() { private func presentSettings() {

View File

@ -10,20 +10,23 @@ final class InstantPageDetailsItem: InstantPageItem {
let title: NSAttributedString let title: NSAttributedString
let items: [InstantPageItem] let items: [InstantPageItem]
let safeInset: CGFloat
let rtl: Bool let rtl: Bool
var initiallyExpanded: Bool
let index: Int
var open: Bool init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) {
init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], rtl: Bool, open: Bool) {
self.frame = frame self.frame = frame
self.title = title self.title = title
self.items = items self.items = items
self.safeInset = safeInset
self.rtl = rtl self.rtl = rtl
self.open = open self.initiallyExpanded = initiallyExpanded
self.index = index
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, updateDetailsOpened: updateDetailsOpened) return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, updateDetailsExpanded: updateDetailsExpanded)
} }
func matchesAnchor(_ anchor: String) -> Bool { func matchesAnchor(_ anchor: String) -> Bool {
@ -58,6 +61,6 @@ final class InstantPageDetailsItem: InstantPageItem {
} }
} }
func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, rtl: Bool, open: Bool) -> InstantPageDetailsItem { func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) -> InstantPageDetailsItem {
return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + 44.0), title: title, items: items, rtl: rtl, open: open) return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + 44.0), title: title, items: items, safeInset: safeInset, rtl: rtl, initiallyExpanded: initiallyExpanded, index: index)
} }

View File

@ -140,9 +140,9 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
//self?.openPeer(peerId) //self?.openPeer(peerId)
}, openUrl: { [weak self] url in }, openUrl: { [weak self] url in
//self?.openUrl(url) //self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] key, height in }, updateWebEmbedHeight: { [weak self] height in
//self?.updateWebEmbedHeight(key, height) //self?.updateWebEmbedHeight(key, height)
}, updateDetailsOpened: { _, _ in }, updateDetailsExpanded: { _ in
}) { }) {
itemNode.frame = item.frame itemNode.frame = item.frame
if let topNode = topNode { if let topNode = topNode {
@ -206,16 +206,16 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
private let separatorNode: ASDisplayNode private let separatorNode: ASDisplayNode
private let contentNode: InstantPageDetailsContentNode private let contentNode: InstantPageDetailsContentNode
let updateOpened: (Int, Bool) -> Void private let updateExpanded: (Bool) -> Void
var opened: Bool var expanded: Bool
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, updateDetailsOpened: @escaping (Int, Bool) -> Void) { init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, updateDetailsExpanded: @escaping (Bool) -> Void) {
self.account = account self.account = account
self.strings = strings self.strings = strings
self.theme = theme self.theme = theme
self.item = item self.item = item
self.updateOpened = updateDetailsOpened self.updateExpanded = updateDetailsExpanded
let frame = item.frame let frame = item.frame
@ -241,12 +241,12 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
} }
self.titleTile.items.append(contentsOf: titleItems) self.titleTile.items.append(contentsOf: titleItems)
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: item.open) self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: item.initiallyExpanded)
self.separatorNode = ASDisplayNode() self.separatorNode = ASDisplayNode()
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height)) self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height))
self.opened = item.open self.expanded = item.initiallyExpanded
super.init() super.init()
@ -259,9 +259,6 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.addSubnode(self.separatorNode) self.addSubnode(self.separatorNode)
self.addSubnode(self.contentNode) self.addSubnode(self.contentNode)
let lineSize = CGSize(width: frame.width - detailsInset, height: UIScreenPixel)
self.separatorNode.frame = CGRect(origin: CGPoint(x: item.rtl ? 0.0 : detailsInset, y: detailsHeaderHeight - lineSize.height), size: lineSize)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.buttonNode.highligthedChanged = { [weak self] highlighted in self.buttonNode.highligthedChanged = { [weak self] highlighted in
@ -287,26 +284,38 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
} }
@objc func buttonPressed() { @objc func buttonPressed() {
self.setOpened(!self.opened, animated: true) self.setExpanded(!self.expanded, animated: true)
} }
func setOpened(_ opened: Bool, animated: Bool) { func setExpanded(_ expanded: Bool, animated: Bool) {
self.opened = opened self.expanded = expanded
self.arrowNode.setOpen(opened, animated: animated) self.arrowNode.setOpen(expanded, animated: animated)
self.updateOpened(0, opened) self.updateExpanded(expanded)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let size = layout.size
let inset = detailsInset + self.item.safeInset
let lineSize = CGSize(width: frame.width - inset, height: UIScreenPixel)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: item.rtl ? 0.0 : inset, y: size.height - lineSize.height), size: lineSize))
} }
override func layout() { override func layout() {
super.layout() super.layout()
let size = self.bounds.size let size = self.bounds.size
let inset = detailsInset + self.item.safeInset
self.titleTileNode.frame = self.titleTile.frame self.titleTileNode.frame = self.titleTile.frame
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: detailsHeaderHeight + UIScreenPixel)) self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: detailsHeaderHeight + UIScreenPixel))
self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: detailsHeaderHeight)) self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: detailsHeaderHeight))
self.arrowNode.frame = CGRect(x: detailsInset, y: floorToScreenPixels((detailsHeaderHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0) self.arrowNode.frame = CGRect(x: inset, y: floorToScreenPixels((detailsHeaderHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0)
self.contentNode.frame = CGRect(x: 0.0, y: detailsHeaderHeight, width: size.width, height: self.item.frame.height - detailsHeaderHeight) self.contentNode.frame = CGRect(x: 0.0, y: detailsHeaderHeight, width: size.width, height: self.item.frame.height - detailsHeaderHeight)
let lineSize = CGSize(width: frame.width - inset, height: UIScreenPixel)
self.separatorNode.frame = CGRect(origin: CGPoint(x: item.rtl ? 0.0 : inset, y: size.height - lineSize.height), size: lineSize)
self.contentNode.updateVisibleItems() self.contentNode.updateVisibleItems()
} }
@ -437,9 +446,9 @@ final class InstantPageDetailsArrowNode : ASDisplayNode {
context.setLineCap(.round) context.setLineCap(.round)
context.setLineWidth(2.0) context.setLineWidth(2.0)
context.move(to: CGPoint(x: 1.0, y: 6.0 - 5.0 * parameters.progress)) context.move(to: CGPoint(x: 1.0, y: 1.0 + 5.0 * parameters.progress))
context.addLine(to: CGPoint(x: 6.0, y: 1.0 + 5.0 * parameters.progress)) context.addLine(to: CGPoint(x: 6.0, y: 6.0 - 5.0 * parameters.progress))
context.addLine(to: CGPoint(x: 11.0, y: 6.0 - 5.0 * parameters.progress)) context.addLine(to: CGPoint(x: 11.0, y: 1.0 + 5.0 * parameters.progress))
context.strokePath() context.strokePath()
} }
} }

View File

@ -15,7 +15,7 @@ final class InstantPageFeedbackItem: InstantPageItem {
self.webPage = webPage self.webPage = webPage
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageFeedbackNode(account: account, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl) return InstantPageFeedbackNode(account: account, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
} }

View File

@ -41,7 +41,7 @@ final class InstantPageImageItem: InstantPageItem {
self.fit = fit self.fit = fit
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, url: self.url, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, openUrl: openUrl) return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, url: self.url, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, openUrl: openUrl)
} }

View File

@ -7,7 +7,8 @@ import SwiftSignalKit
final class InstantPageImageNode: ASDisplayNode, InstantPageNode { final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private let account: Account private let account: Account
private let theme: InstantPageTheme private let webPage: TelegramMediaWebpage
private var theme: InstantPageTheme
let media: InstantPageMedia let media: InstantPageMedia
let attributes: [InstantPageImageAttribute] let attributes: [InstantPageImageAttribute]
let url: InstantPageUrlItem? let url: InstantPageUrlItem?
@ -24,9 +25,12 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private var fetchedDisposable = MetaDisposable() private var fetchedDisposable = MetaDisposable()
private var themeUpdated: Bool = false
init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void = { _ in }) { init(account: Account, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void = { _ in }) {
self.account = account self.account = account
self.theme = theme self.theme = theme
self.webPage = webPage
self.media = media self.media = media
self.attributes = attributes self.attributes = attributes
self.url = url self.url = url
@ -88,6 +92,19 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
} }
func update(strings: PresentationStrings, theme: InstantPageTheme) { func update(strings: PresentationStrings, theme: InstantPageTheme) {
if self.theme.imageEmptyColor != theme.imageEmptyColor {
self.theme = theme
self.themeUpdated = true
self.setNeedsLayout()
if let file = self.media.media as? TelegramMediaFile {
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
if file.mimeType.hasPrefix("image/") {
_ = freeMediaFileInteractiveFetched(account: self.account, fileReference: fileReference).start()
self.imageNode.setSignal(chatMessageImageFile(account: self.account, fileReference: fileReference, thumbnail: false, fetched: true))
}
}
}
} }
override func layout() { override func layout() {
@ -95,8 +112,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let size = self.bounds.size let size = self.bounds.size
if self.currentSize != size { if self.currentSize != size || self.themeUpdated {
self.currentSize = size self.currentSize = size
self.themeUpdated = false
self.imageNode.frame = CGRect(origin: CGPoint(), size: size) self.imageNode.frame = CGRect(origin: CGPoint(), size: size)

View File

@ -10,7 +10,7 @@ protocol InstantPageItem {
func matchesAnchor(_ anchor: String) -> Bool func matchesAnchor(_ anchor: String) -> Bool
func drawInTile(context: CGContext) func drawInTile(context: CGContext)
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)?
func matchesNode(_ node: InstantPageNode) -> Bool func matchesNode(_ node: InstantPageNode) -> Bool
func linkSelectionRects(at point: CGPoint) -> [CGRect] func linkSelectionRects(at point: CGPoint) -> [CGRect]

View File

@ -41,7 +41,7 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP
} }
} }
func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToWidthAndHeight: Bool, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : Int] = [:]) -> InstantPageLayout { func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToWidthAndHeight: Bool, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout {
let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in
var items: [InstantPageItem] = [] var items: [InstantPageItem] = []
@ -80,7 +80,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
switch block { switch block {
case let .cover(block): case let .cover(block):
return layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToWidthAndHeight: fillToWidthAndHeight, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) return layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToWidthAndHeight: fillToWidthAndHeight, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
case let .title(text): case let .title(text):
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .header, link: false) setupStyleStack(styleStack, theme: theme, category: .header, link: false)
@ -265,7 +265,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
var previousBlock: InstantPageBlock? var previousBlock: InstantPageBlock?
var originY: CGFloat = 0.0 var originY: CGFloat = 0.0
for subBlock in blocks { for subBlock in blocks {
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let spacing: CGFloat = previousBlock != nil ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0 let spacing: CGFloat = previousBlock != nil ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing)) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing))
@ -454,7 +454,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
nextItemOrigin.x = 0.0 nextItemOrigin.x = 0.0
nextItemOrigin.y += itemSize + spacing nextItemOrigin.y += itemSize + spacing
} }
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subItem, boundingWidth: itemSize, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: true, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subItem, boundingWidth: itemSize, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: true, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin)) items.append(contentsOf: subLayout.flattenedItemsWithOrigin(nextItemOrigin))
nextItemOrigin.x += itemSize + spacing nextItemOrigin.x += itemSize + spacing
} }
@ -522,7 +522,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
var previousBlock: InstantPageBlock? var previousBlock: InstantPageBlock?
for subBlock in blocks { for subBlock in blocks {
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock)
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + lineInset, y: contentSize.height + spacing)) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + lineInset, y: contentSize.height + spacing))
@ -576,6 +576,10 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
if stretchToWidth { if stretchToWidth {
embedBoundingWidth = boundingWidth embedBoundingWidth = boundingWidth
} }
let embedIndex = embedIndexCounter
embedIndexCounter += 1
let size: CGSize let size: CGSize
if let dimensions = dimensions { if let dimensions = dimensions {
if dimensions.width.isLessThanOrEqualTo(0.0) { if dimensions.width.isLessThanOrEqualTo(0.0) {
@ -584,14 +588,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth)) size = dimensions.aspectFitted(CGSize(width: embedBoundingWidth, height: embedBoundingWidth))
} }
} else { } else {
var hash: Int? if let height = webEmbedHeights[embedIndex] {
if let url = url {
hash = url.hashValue
} else if let html = html {
hash = html.hashValue
}
if let hash = hash, let height = webEmbedHeights[hash] {
size = CGSize(width: embedBoundingWidth, height: CGFloat(height)) size = CGSize(width: embedBoundingWidth, height: CGFloat(height))
} else { } else {
size = CGSize(width: embedBoundingWidth, height: 44.0) size = CGSize(width: embedBoundingWidth, height: 44.0)
@ -619,7 +616,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
} }
if let peer = peer { if let peer = peer {
let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, rtl: rtl || previousItemHasRTL) let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, rtl: rtl || previousItemHasRTL)
items.append(item) items.append(item)
contentSize.height += 40.0 contentSize.height += 40.0
} }
@ -670,13 +667,16 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
items.append(tableItem) items.append(tableItem)
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
case let .details(title, blocks, open): case let .details(title, blocks, expanded):
var contentSize = CGSize(width: boundingWidth, height: 0.0) var contentSize = CGSize(width: boundingWidth, height: 0.0)
var subitems: [InstantPageItem] = [] var subitems: [InstantPageItem] = []
let detailsIndex = detailsIndexCounter
detailsIndexCounter += 1
var previousBlock: InstantPageBlock? var previousBlock: InstantPageBlock?
for subBlock in blocks { for subBlock in blocks {
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock)
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset, y: contentSize.height + spacing)) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset, y: contentSize.height + spacing))
@ -690,7 +690,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, rtl: rtl, open: open) let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, safeInset: safeInset, rtl: rtl, initiallyExpanded: expanded, index: detailsIndex)
return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem]) return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem])
case let .relatedArticles(title, articles): case let .relatedArticles(title, articles):
@ -756,7 +756,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
} }
} }
func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : Int] = [:]) -> InstantPageLayout { func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout {
var maybeLoadedContent: TelegramMediaWebpageLoadedContent? var maybeLoadedContent: TelegramMediaWebpageLoadedContent?
if case let .Loaded(content) = webPage.content { if case let .Loaded(content) = webPage.content {
maybeLoadedContent = content maybeLoadedContent = content
@ -778,10 +778,11 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth:
var mediaIndexCounter: Int = 0 var mediaIndexCounter: Int = 0
var embedIndexCounter: Int = 0 var embedIndexCounter: Int = 0
var detailsIndexCounter: Int = 0
var previousBlock: InstantPageBlock? var previousBlock: InstantPageBlock?
for block in pageBlocks { for block in pageBlocks {
let blockLayout = layoutInstantPageBlock(webpage: webPage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights) let blockLayout = layoutInstantPageBlock(webpage: webPage, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block) let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block)
let blockItems = blockLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) let blockItems = blockLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing))
items.append(contentsOf: blockItems) items.append(contentsOf: blockItems)

View File

@ -9,16 +9,18 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
let medias: [InstantPageMedia] = [] let medias: [InstantPageMedia] = []
let initialPeer: Peer let initialPeer: Peer
let safeInset: CGFloat
let rtl: Bool let rtl: Bool
init(frame: CGRect, initialPeer: Peer, rtl: Bool) { init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, rtl: Bool) {
self.frame = frame self.frame = frame
self.initialPeer = initialPeer self.initialPeer = initialPeer
self.safeInset = safeInset
self.rtl = rtl self.rtl = rtl
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, rtl: self.rtl, openPeer: openPeer) return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, rtl: self.rtl, openPeer: openPeer)
} }
func matchesAnchor(_ anchor: String) -> Bool { func matchesAnchor(_ anchor: String) -> Bool {
@ -27,7 +29,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
func matchesNode(_ node: InstantPageNode) -> Bool { func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPagePeerReferenceNode { if let node = node as? InstantPagePeerReferenceNode {
return self.initialPeer.id == node.initialPeer.id return self.initialPeer.id == node.initialPeer.id && self.safeInset == node.safeInset
} else { } else {
return false return false
} }

View File

@ -44,6 +44,7 @@ private enum JoinState: Equatable {
final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private let account: Account private let account: Account
let initialPeer: Peer let initialPeer: Peer
let safeInset: CGFloat
private let rtl: Bool private let rtl: Bool
private var strings: PresentationStrings private var strings: PresentationStrings
private var theme: InstantPageTheme private var theme: InstantPageTheme
@ -63,11 +64,12 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
private var joinState: JoinState = .none private var joinState: JoinState = .none
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, initialPeer: Peer, rtl: Bool, openPeer: @escaping (PeerId) -> Void) { init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, rtl: Bool, openPeer: @escaping (PeerId) -> Void) {
self.account = account self.account = account
self.strings = strings self.strings = strings
self.theme = theme self.theme = theme
self.initialPeer = initialPeer self.initialPeer = initialPeer
self.safeInset = safeInset
self.rtl = rtl self.rtl = rtl
self.openPeer = openPeer self.openPeer = openPeer
@ -219,7 +221,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
super.layout() super.layout()
let size = self.bounds.size let size = self.bounds.size
let inset: CGFloat = 17.0 let inset: CGFloat = 17.0 + safeInset
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
@ -230,15 +232,15 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
let indicatorSize = self.activityIndicator.measure(size) let indicatorSize = self.activityIndicator.measure(size)
if self.rtl { if self.rtl {
self.nameNode.frame = CGRect(origin: CGPoint(x: size.width - 17.0 - nameSize.width, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize) self.nameNode.frame = CGRect(origin: CGPoint(x: size.width - inset - nameSize.width, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize)
self.joinNode.frame = CGRect(origin: CGPoint(x: 17.0, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize) self.joinNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: 17.0, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize) self.checkNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
self.activityIndicator.frame = CGRect(origin: CGPoint(x: 17.0, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize) self.activityIndicator.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
} else { } else {
self.nameNode.frame = CGRect(origin: CGPoint(x: 17.0, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize) self.nameNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - nameSize.height) / 2.0)), size: nameSize)
self.joinNode.frame = CGRect(origin: CGPoint(x: size.width - 17.0 - joinSize.width, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize) self.joinNode.frame = CGRect(origin: CGPoint(x: size.width - inset - joinSize.width, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - 17.0 - checkSize.width, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize) self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - inset - checkSize.width, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
self.activityIndicator.frame = CGRect(origin: CGPoint(x: size.width - 17.0 - indicatorSize.width, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize) self.activityIndicator.frame = CGRect(origin: CGPoint(x: size.width - inset - indicatorSize.width, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
} }
} }

View File

@ -23,7 +23,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
self.interactive = interactive self.interactive = interactive
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, openMedia: openMedia) return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, openMedia: openMedia)
} }

View File

@ -56,7 +56,7 @@ final class InstantPageShapeItem: InstantPageItem {
return false return false
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return nil return nil
} }

View File

@ -15,7 +15,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
self.medias = medias self.medias = medias
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageSlideshowNode(account: account, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia) return InstantPageSlideshowNode(account: account, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia)
} }

View File

@ -166,7 +166,7 @@ final class InstantPageTableItem: InstantPageItem {
return false return false
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageTableNode(item: self, account: account, strings: strings, theme: theme) return InstantPageTableNode(item: self, account: account, strings: strings, theme: theme)
} }
@ -227,7 +227,7 @@ final class InstantPageTableContentNode: ASDisplayNode {
for cell in self.item.cells { for cell in self.item.cells {
for item in cell.additionalItems { for item in cell.additionalItems {
if item.wantsNode { if item.wantsNode {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _, _ in }, updateDetailsOpened: { _, _ in }) { if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }) {
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY) node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
self.addSubnode(node) self.addSubnode(node)
} }

View File

@ -309,7 +309,7 @@ final class InstantPageTextItem: InstantPageItem {
return false return false
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return nil return nil
} }

View File

@ -19,7 +19,7 @@ final class InstantPageWebEmbedItem: InstantPageItem {
self.enableScrolling = enableScrolling self.enableScrolling = enableScrolling
} }
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (Int, Int) -> Void, updateDetailsOpened: @escaping (Int, Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? { func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight) return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
} }

View File

@ -20,11 +20,11 @@ private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessa
final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode {
let url: String? let url: String?
let html: String? let html: String?
let updateWebEmbedHeight: (Int, Int) -> Void let updateWebEmbedHeight: (CGFloat) -> Void
private var webView: WKWebView? private var webView: WKWebView?
init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool, updateWebEmbedHeight: @escaping (Int, Int) -> Void) { init(frame: CGRect, url: String?, html: String?, enableScrolling: Bool, updateWebEmbedHeight: @escaping (CGFloat) -> Void) {
self.url = url self.url = url
self.html = html self.html = html
self.updateWebEmbedHeight = updateWebEmbedHeight self.updateWebEmbedHeight = updateWebEmbedHeight
@ -91,15 +91,7 @@ final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode {
} }
if eventName == "resize_frame", let height = dict["height"] as? Int { if eventName == "resize_frame", let height = dict["height"] as? Int {
var hash: Int? self.updateWebEmbedHeight(CGFloat(height))
if let url = self.url {
hash = url.hashValue
} else if let html = self.html {
hash = html.hashValue
}
if let hash = hash {
self.updateWebEmbedHeight(hash, height)
}
} }
} }

View File

@ -125,7 +125,7 @@ final class OngoingCallContext {
private let audioSessionDisposable = MetaDisposable() private let audioSessionDisposable = MetaDisposable()
private var networkTypeDisposable: Disposable? private var networkTypeDisposable: Disposable?
init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, allowP2P: Bool, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) { init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
let _ = setupLogs let _ = setupLogs
self.internalId = internalId self.internalId = internalId
@ -142,7 +142,7 @@ final class OngoingCallContext {
break break
} }
} }
let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), allowP2P: allowP2P, proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType)) let context = OngoingCallThreadLocalContext(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForType(initialNetworkType))
self.contextRef = Unmanaged.passRetained(context) self.contextRef = Unmanaged.passRetained(context)
context.stateChanged = { [weak self] state in context.stateChanged = { [weak self] state in
self?.contextState.set(.single(state)) self?.contextState.set(.single(state))
@ -186,13 +186,13 @@ final class OngoingCallContext {
} }
} }
func start(key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, audioSessionActive: Signal<Bool, NoError>) { func start(key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, allowP2P: Bool, audioSessionActive: Signal<Bool, NoError>) {
self.audioSessionDisposable.set((audioSessionActive self.audioSessionDisposable.set((audioSessionActive
|> filter { $0 } |> filter { $0 }
|> take(1)).start(next: { [weak self] _ in |> take(1)).start(next: { [weak self] _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.withContext { context in strongSelf.withContext { context in
context.start(withKey: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer) context.start(withKey: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescription(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescription), maxLayer: maxLayer, allowP2P: allowP2P)
} }
} }
})) }))

View File

@ -54,8 +54,8 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) {
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState); @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState);
@property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile); @property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile);
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue allowP2P:(BOOL)allowP2P proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType; - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType;
- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescription *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer; - (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescription *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P;
- (void)stop; - (void)stop;
- (NSString * _Nullable)debugInfo; - (NSString * _Nullable)debugInfo;

View File

@ -127,7 +127,6 @@ static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalConte
NSTimeInterval _callConnectTimeout; NSTimeInterval _callConnectTimeout;
NSTimeInterval _callPacketTimeout; NSTimeInterval _callPacketTimeout;
int32_t _dataSavingMode; int32_t _dataSavingMode;
bool _allowP2P;
tgvoip::VoIPController *_controller; tgvoip::VoIPController *_controller;
@ -181,7 +180,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
TGVoipLoggingFunction = loggingFunction; TGVoipLoggingFunction = loggingFunction;
} }
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue allowP2P:(BOOL)allowP2P proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType { - (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_queue = queue; _queue = queue;
@ -193,7 +192,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
_callConnectTimeout = 30.0; _callConnectTimeout = 30.0;
_callPacketTimeout = 10.0; _callPacketTimeout = 10.0;
_dataSavingMode = 0; _dataSavingMode = 0;
_allowP2P = allowP2P;
_networkType = networkType; _networkType = networkType;
_controller = new tgvoip::VoIPController(); _controller = new tgvoip::VoIPController();
@ -232,7 +230,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
} }
} }
- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescription *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer { - (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray<OngoingCallConnectionDescription *> * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P {
std::vector<tgvoip::Endpoint> endpoints; std::vector<tgvoip::Endpoint> endpoints;
NSArray<OngoingCallConnectionDescription *> *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; NSArray<OngoingCallConnectionDescription *> *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections];
for (OngoingCallConnectionDescription *connection in connections) { for (OngoingCallConnectionDescription *connection in connections) {
@ -261,7 +259,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
_controller->SetConfig(config); _controller->SetConfig(config);
_controller->SetEncryptionKey((char *)key.bytes, isOutgoing); _controller->SetEncryptionKey((char *)key.bytes, isOutgoing);
_controller->SetRemoteEndpoints(endpoints, _allowP2P, maxLayer); _controller->SetRemoteEndpoints(endpoints, allowP2P, maxLayer);
_controller->Start(); _controller->Start();
_controller->Connect(); _controller->Connect();

View File

@ -188,6 +188,7 @@ public class PeerMediaCollectionController: TelegramController {
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in
}, longTap: { [weak self] content in }, longTap: { [weak self] content in
if let strongSelf = self { if let strongSelf = self {
strongSelf.view.endEditing(true)
switch content { switch content {
case let .url(url): case let .url(url):
let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1 let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1

View File

@ -230,7 +230,7 @@ public final class PresentationCall {
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.peer = peer self.peer = peer
self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, allowP2P: allowP2P, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType) self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType)
var didReceiveAudioOutputs = false var didReceiveAudioOutputs = false
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId) self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
@ -413,7 +413,7 @@ public final class PresentationCall {
presentationState = .terminated(reason) presentationState = .terminated(reason)
case let .requesting(ringing): case let .requesting(ringing):
presentationState = .requesting(ringing) presentationState = .requesting(ringing)
case let .active(_, keyVisualHash, _, _): case let .active(_, keyVisualHash, _, _, _):
if let callContextState = callContextState { if let callContextState = callContextState {
switch callContextState { switch callContextState {
case .initializing: case .initializing:
@ -441,10 +441,10 @@ public final class PresentationCall {
if let _ = audioSessionControl { if let _ = audioSessionControl {
self.audioSessionShouldBeActive.set(true) self.audioSessionShouldBeActive.set(true)
} }
case let .active(key, _, connections, maxLayer): case let .active(key, _, connections, maxLayer, allowsP2P):
self.audioSessionShouldBeActive.set(true) self.audioSessionShouldBeActive.set(true)
if let _ = audioSessionControl, !wasActive || previousControl == nil { if let _ = audioSessionControl, !wasActive || previousControl == nil {
self.ongoingContext.start(key: key, isOutgoing: sessionState.isOutgoing, connections: connections, maxLayer: maxLayer, audioSessionActive: self.audioSessionActive.get()) self.ongoingContext.start(key: key, isOutgoing: sessionState.isOutgoing, connections: connections, maxLayer: maxLayer, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get())
if sessionState.isOutgoing { if sessionState.isOutgoing {
self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date()) self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date())
} }

View File

@ -376,7 +376,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: updated, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -399,7 +399,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: updated, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -424,8 +424,8 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
currentInfoDisposable.set((combineLatest(privacySignal, callsSignal) currentInfoDisposable.set((combineLatest(privacySignal, callsSignal)
|> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in |> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in
if let info = info { if let info = info {
pushControllerImpl?(selectivePrivacySettingsController(account: account, kind: .voiceCalls, current: info.voiceCalls, callSettings: callSettings.0, voipConfiguration: callSettings.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in pushControllerImpl?(selectivePrivacySettingsController(account: account, kind: .voiceCalls, current: info.voiceCalls, callSettings: (info.voiceCallsP2P, callSettings.0), voipConfiguration: callSettings.1, callIntegrationAvailable: CallKitIntegration.isAvailable, updated: { updated, updatedCallSettings in
if let currentInfoDisposable = currentInfoDisposable, let updatedCallSettings = updatedCallSettings { if let currentInfoDisposable = currentInfoDisposable, let (updatedCallsPrivacy, updatedCallSettings) = updatedCallSettings {
let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { _ in let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { _ in
return updatedCallSettings return updatedCallSettings
}).start() }).start()
@ -436,7 +436,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, accountRemovalTimeout: value.accountRemovalTimeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: updated, voiceCallsP2P: updatedCallsPrivacy, accountRemovalTimeout: value.accountRemovalTimeout)))
} }
return .complete() return .complete()
} }
@ -485,7 +485,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign
|> deliverOnMainQueue |> deliverOnMainQueue
|> mapToSignal { value -> Signal<Void, NoError> in |> mapToSignal { value -> Signal<Void, NoError> in
if let value = value { if let value = value {
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, accountRemovalTimeout: timeout))) privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, accountRemovalTimeout: timeout)))
} }
return .complete() return .complete()
} }

View File

@ -27,24 +27,29 @@ private enum SelectivePrivacySettingType {
} }
} }
enum SelectivePrivacySettingsPeerTarget {
case main
case callP2P
}
private final class SelectivePrivacySettingsControllerArguments { private final class SelectivePrivacySettingsControllerArguments {
let account: Account let account: Account
let updateType: (SelectivePrivacySettingType) -> Void let updateType: (SelectivePrivacySettingType) -> Void
let openEnableFor: () -> Void let openEnableFor: (SelectivePrivacySettingsPeerTarget) -> Void
let openDisableFor: () -> Void let openDisableFor: (SelectivePrivacySettingsPeerTarget) -> Void
let updateCallsP2PMode: ((VoiceCallP2PMode) -> Void)? let updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?
let updateCallsIntegrationEnabled: ((Bool) -> Void)? let updateCallIntegrationEnabled: ((Bool) -> Void)?
init(account: Account, updateType: @escaping (SelectivePrivacySettingType) -> Void, openEnableFor: @escaping () -> Void, openDisableFor: @escaping () -> Void, updateCallsP2PMode: ((VoiceCallP2PMode) -> Void)?, updateCallsIntegrationEnabled: ((Bool) -> Void)?) { init(account: Account, updateType: @escaping (SelectivePrivacySettingType) -> Void, openEnableFor: @escaping (SelectivePrivacySettingsPeerTarget) -> Void, openDisableFor: @escaping (SelectivePrivacySettingsPeerTarget) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?) {
self.account = account self.account = account
self.updateType = updateType self.updateType = updateType
self.openEnableFor = openEnableFor self.openEnableFor = openEnableFor
self.openDisableFor = openDisableFor self.openDisableFor = openDisableFor
self.updateCallsP2PMode = updateCallsP2PMode self.updateCallP2PMode = updateCallP2PMode
self.updateCallsIntegrationEnabled = updateCallsIntegrationEnabled self.updateCallIntegrationEnabled = updateCallIntegrationEnabled
} }
} }
@ -52,6 +57,7 @@ private enum SelectivePrivacySettingsSection: Int32 {
case setting case setting
case peers case peers
case callsP2P case callsP2P
case callsP2PPeers
case callsIntegrationEnabled case callsIntegrationEnabled
} }
@ -77,6 +83,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case callsP2PContacts(PresentationTheme, String, Bool) case callsP2PContacts(PresentationTheme, String, Bool)
case callsP2PNever(PresentationTheme, String, Bool) case callsP2PNever(PresentationTheme, String, Bool)
case callsP2PInfo(PresentationTheme, String) case callsP2PInfo(PresentationTheme, String)
case callsP2PDisableFor(PresentationTheme, String, String)
case callsP2PEnableFor(PresentationTheme, String, String)
case callsP2PPeersInfo(PresentationTheme, String)
case callsIntegrationEnabled(PresentationTheme, String, Bool) case callsIntegrationEnabled(PresentationTheme, String, Bool)
case callsIntegrationInfo(PresentationTheme, String) case callsIntegrationInfo(PresentationTheme, String)
@ -88,6 +97,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return SelectivePrivacySettingsSection.peers.rawValue return SelectivePrivacySettingsSection.peers.rawValue
case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo: case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo:
return SelectivePrivacySettingsSection.callsP2P.rawValue return SelectivePrivacySettingsSection.callsP2P.rawValue
case .callsP2PDisableFor, .callsP2PEnableFor, .callsP2PPeersInfo:
return SelectivePrivacySettingsSection.callsP2PPeers.rawValue
case .callsIntegrationEnabled, .callsIntegrationInfo: case .callsIntegrationEnabled, .callsIntegrationInfo:
return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue
} }
@ -121,10 +132,16 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return 11 return 11
case .callsP2PInfo: case .callsP2PInfo:
return 12 return 12
case .callsIntegrationEnabled: case .callsP2PDisableFor:
return 13 return 13
case .callsIntegrationInfo: case .callsP2PEnableFor:
return 14 return 14
case .callsP2PPeersInfo:
return 15
case .callsIntegrationEnabled:
return 16
case .callsIntegrationInfo:
return 17
} }
} }
@ -208,6 +225,24 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .callsP2PDisableFor(lhsTheme, lhsText, lhsValue):
if case let .callsP2PDisableFor(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .callsP2PEnableFor(lhsTheme, lhsText, lhsValue):
if case let .callsP2PEnableFor(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .callsP2PPeersInfo(lhsTheme, lhsText):
if case let .callsP2PPeersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .callsIntegrationEnabled(lhsTheme, lhsText, lhsValue): case let .callsIntegrationEnabled(lhsTheme, lhsText, lhsValue):
if case let .callsIntegrationEnabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .callsIntegrationEnabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true return true
@ -247,11 +282,11 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .disableFor(theme, title, value): case let .disableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openDisableFor() arguments.openDisableFor(.main)
}) })
case let .enableFor(theme, title, value): case let .enableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openEnableFor() arguments.openEnableFor(.main)
}) })
case let .peersInfo(theme, text): case let .peersInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
@ -259,21 +294,31 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .callsP2PAlways(theme, text, value): case let .callsP2PAlways(theme, text, value):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateCallsP2PMode?(.always) arguments.updateCallP2PMode?(.everybody)
}) })
case let .callsP2PContacts(theme, text, value): case let .callsP2PContacts(theme, text, value):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateCallsP2PMode?(.contacts) arguments.updateCallP2PMode?(.contacts)
}) })
case let .callsP2PNever(theme, text, value): case let .callsP2PNever(theme, text, value):
return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(theme: theme, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateCallsP2PMode?(.never) arguments.updateCallP2PMode?(.nobody)
}) })
case let .callsP2PInfo(theme, text): case let .callsP2PInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .callsP2PDisableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openDisableFor(.callP2P)
})
case let .callsP2PEnableFor(theme, title, value):
return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openEnableFor(.callP2P)
})
case let .callsP2PPeersInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .callsIntegrationEnabled(theme, text, value): case let .callsIntegrationEnabled(theme, text, value):
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateCallsIntegrationEnabled?(value) arguments.updateCallIntegrationEnabled?(value)
}) })
case let .callsIntegrationInfo(theme, text): case let .callsIntegrationInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
@ -289,17 +334,21 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
let saving: Bool let saving: Bool
let callDataSaving: VoiceCallDataSaving? let callDataSaving: VoiceCallDataSaving?
let callP2PMode: VoiceCallP2PMode? let callP2PMode: SelectivePrivacySettingType?
let callP2PEnableFor: Set<PeerId>?
let callP2PDisableFor: Set<PeerId>?
let callIntegrationAvailable: Bool? let callIntegrationAvailable: Bool?
let callIntegrationEnabled: Bool? let callIntegrationEnabled: Bool?
init(setting: SelectivePrivacySettingType, enableFor: Set<PeerId>, disableFor: Set<PeerId>, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: VoiceCallP2PMode?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) { init(setting: SelectivePrivacySettingType, enableFor: Set<PeerId>, disableFor: Set<PeerId>, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: Set<PeerId>?, callP2PDisableFor: Set<PeerId>?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) {
self.setting = setting self.setting = setting
self.enableFor = enableFor self.enableFor = enableFor
self.disableFor = disableFor self.disableFor = disableFor
self.saving = saving self.saving = saving
self.callDataSaving = callDataSaving self.callDataSaving = callDataSaving
self.callP2PMode = callP2PMode self.callP2PMode = callP2PMode
self.callP2PEnableFor = callP2PEnableFor
self.callP2PDisableFor = callP2PDisableFor
self.callIntegrationAvailable = callIntegrationAvailable self.callIntegrationAvailable = callIntegrationAvailable
self.callIntegrationEnabled = callIntegrationEnabled self.callIntegrationEnabled = callIntegrationEnabled
} }
@ -323,6 +372,12 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
if lhs.callP2PMode != rhs.callP2PMode { if lhs.callP2PMode != rhs.callP2PMode {
return false return false
} }
if lhs.callP2PEnableFor != rhs.callP2PEnableFor {
return false
}
if lhs.callP2PDisableFor != rhs.callP2PDisableFor {
return false
}
if lhs.callIntegrationAvailable != rhs.callIntegrationAvailable { if lhs.callIntegrationAvailable != rhs.callIntegrationAvailable {
return false return false
} }
@ -334,27 +389,35 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
} }
func withUpdatedSetting(_ setting: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { func withUpdatedSetting(_ setting: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedEnableFor(_ enableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedEnableFor(_ enableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedDisableFor(_ disableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState { func withUpdatedDisableFor(_ disableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedSaving(_ saving: Bool) -> SelectivePrivacySettingsControllerState { func withUpdatedSaving(_ saving: Bool) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedCallsP2PMode(_ mode: VoiceCallP2PMode) -> SelectivePrivacySettingsControllerState { func withUpdatedCallP2PMode(_ mode: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
}
func withUpdatedCallP2PEnableFor(_ enableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
}
func withUpdatedCallP2PDisableFor(_ disableFor: Set<PeerId>) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled)
} }
func withUpdatedCallsIntegrationEnabled(_ enabled: Bool) -> SelectivePrivacySettingsControllerState { func withUpdatedCallsIntegrationEnabled(_ enabled: Bool) -> SelectivePrivacySettingsControllerState {
return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled) return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled)
} }
} }
@ -409,12 +472,24 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
if case .voiceCalls = kind, let p2pMode = state.callP2PMode, let integrationAvailable = state.callIntegrationAvailable, let integrationEnabled = state.callIntegrationEnabled { if case .voiceCalls = kind, let p2pMode = state.callP2PMode, let integrationAvailable = state.callIntegrationAvailable, let integrationEnabled = state.callIntegrationEnabled {
entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased())) entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased()))
entries.append(.callsP2PAlways(presentationData.theme, presentationData.strings.Privacy_Calls_P2PAlways, p2pMode == .always)) entries.append(.callsP2PAlways(presentationData.theme, presentationData.strings.Privacy_Calls_P2PAlways, p2pMode == .everybody))
entries.append(.callsP2PContacts(presentationData.theme, presentationData.strings.Privacy_Calls_P2PContacts, p2pMode == .contacts)) entries.append(.callsP2PContacts(presentationData.theme, presentationData.strings.Privacy_Calls_P2PContacts, p2pMode == .contacts))
entries.append(.callsP2PNever(presentationData.theme, presentationData.strings.Privacy_Calls_P2PNever, p2pMode == .never)) entries.append(.callsP2PNever(presentationData.theme, presentationData.strings.Privacy_Calls_P2PNever, p2pMode == .nobody))
entries.append(.callsP2PInfo(presentationData.theme, presentationData.strings.Privacy_Calls_P2PHelp)) entries.append(.callsP2PInfo(presentationData.theme, presentationData.strings.Privacy_Calls_P2PHelp))
if let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor {
switch callP2PMode {
case .everybody:
entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor.count, strings: presentationData.strings)))
case .contacts:
entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor.count, strings: presentationData.strings)))
entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor.count, strings: presentationData.strings)))
case .nobody:
entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor.count, strings: presentationData.strings)))
}
}
entries.append(.callsP2PPeersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp))
if integrationAvailable { if integrationAvailable {
entries.append(.callsIntegrationEnabled(presentationData.theme, presentationData.strings.Privacy_Calls_Integration, integrationEnabled)) entries.append(.callsIntegrationEnabled(presentationData.theme, presentationData.strings.Privacy_Calls_Integration, integrationEnabled))
entries.append(.callsIntegrationInfo(presentationData.theme, presentationData.strings.Privacy_Calls_IntegrationHelp)) entries.append(.callsIntegrationInfo(presentationData.theme, presentationData.strings.Privacy_Calls_IntegrationHelp))
@ -424,7 +499,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
return entries return entries
} }
func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: VoiceCallSettings? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, VoiceCallSettings?) -> Void) -> ViewController { func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: (SelectivePrivacySettings, VoiceCallSettings)? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?) -> Void) -> ViewController {
let strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings let strings = account.telegramApplicationContext.currentPresentationData.with { $0 }.strings
var initialEnableFor = Set<PeerId>() var initialEnableFor = Set<PeerId>()
@ -438,7 +513,23 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
case let .enableEveryone(disableFor): case let .enableEveryone(disableFor):
initialDisableFor = disableFor initialDisableFor = disableFor
} }
let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false, callDataSaving: callSettings?.dataSaving, callP2PMode: callSettings?.p2pMode ?? voipConfiguration?.defaultP2PMode, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.enableSystemIntegration) var initialCallP2PEnableFor: Set<PeerId>?
var initialCallP2PDisableFor: Set<PeerId>?
if let callCurrent = callSettings?.0 {
switch callCurrent {
case let .disableEveryone(enableFor):
initialCallP2PEnableFor = enableFor
initialCallP2PDisableFor = Set<PeerId>()
case let .enableContacts(enableFor, disableFor):
initialCallP2PEnableFor = enableFor
initialCallP2PDisableFor = disableFor
case let .enableEveryone(disableFor):
initialCallP2PEnableFor = Set<PeerId>()
initialCallP2PDisableFor = disableFor
}
}
let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, saving: false, callDataSaving: callSettings?.1.dataSaving, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!.0) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.1.enableSystemIntegration)
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -458,7 +549,7 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
updateState { updateState {
$0.withUpdatedSetting(type) $0.withUpdatedSetting(type)
} }
}, openEnableFor: { }, openEnableFor: { target in
let title: String let title: String
switch kind { switch kind {
case .presence: case .presence:
@ -475,10 +566,15 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
} }
pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in
updateState { state in updateState { state in
return state.withUpdatedEnableFor(Set(updatedPeerIds)).withUpdatedDisableFor(state.disableFor.subtracting(Set(updatedPeerIds))) switch target {
case .main:
return state.withUpdatedEnableFor(Set(updatedPeerIds)).withUpdatedDisableFor(state.disableFor.subtracting(Set(updatedPeerIds)))
case .callP2P:
return state.withUpdatedCallP2PEnableFor(Set(updatedPeerIds)).withUpdatedCallP2PDisableFor(state.disableFor.subtracting(Set(updatedPeerIds)))
}
} }
})) }))
}, openDisableFor: { }, openDisableFor: { target in
let title: String let title: String
switch kind { switch kind {
case .presence: case .presence:
@ -495,19 +591,19 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
} }
pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds in
updateState { state in updateState { state in
return state.withUpdatedDisableFor(Set(updatedPeerIds)).withUpdatedEnableFor(state.enableFor.subtracting(Set(updatedPeerIds))) switch target {
case .main:
return state.withUpdatedDisableFor(Set(updatedPeerIds)).withUpdatedEnableFor(state.enableFor.subtracting(Set(updatedPeerIds)))
case .callP2P:
return state.withUpdatedCallP2PDisableFor(Set(updatedPeerIds)).withUpdatedCallP2PEnableFor(state.enableFor.subtracting(Set(updatedPeerIds)))
}
} }
})) }))
}, updateCallsP2PMode: { mode in }, updateCallP2PMode: { mode in
updateState { state in updateState { state in
return state.withUpdatedCallsP2PMode(mode) return state.withUpdatedCallP2PMode(mode)
} }
let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { settings in }, updateCallIntegrationEnabled: { enabled in
var settings = settings
settings.p2pMode = mode
return settings
}).start()
}, updateCallsIntegrationEnabled: { enabled in
updateState { state in updateState { state in
return state.withUpdatedCallsIntegrationEnabled(enabled) return state.withUpdatedCallsIntegrationEnabled(enabled)
} }
@ -532,6 +628,7 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
var wasSaving = false var wasSaving = false
var settings: SelectivePrivacySettings? var settings: SelectivePrivacySettings?
var callP2PSettings: SelectivePrivacySettings?
updateState { state in updateState { state in
wasSaving = state.saving wasSaving = state.saving
switch state.setting { switch state.setting {
@ -542,6 +639,18 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
case .nobody: case .nobody:
settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor) settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor)
} }
if case .voiceCalls = kind, let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor {
switch callP2PMode {
case .everybody:
callP2PSettings = SelectivePrivacySettings.enableEveryone(disableFor: disableFor)
case .contacts:
callP2PSettings = SelectivePrivacySettings.enableContacts(enableFor: enableFor, disableFor: disableFor)
case .nobody:
callP2PSettings = SelectivePrivacySettings.disableEveryone(enableFor: enableFor)
}
}
return state.withUpdatedSaving(true) return state.withUpdatedSaving(true)
} }
@ -556,12 +665,18 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy
type = .voiceCalls type = .voiceCalls
} }
updateSettingsDisposable.set((updateSelectiveAccountPrivacySettings(account: account, type: type, settings: settings) |> deliverOnMainQueue).start(completed: { let updateSettingsSignal = updateSelectiveAccountPrivacySettings(account: account, type: type, settings: settings)
var updateCallP2PSettingsSignal: Signal<Void, NoError> = Signal.complete()
if let callP2PSettings = callP2PSettings {
updateCallP2PSettingsSignal = updateSelectiveAccountPrivacySettings(account: account, type: .voiceCallsP2P, settings: callP2PSettings)
}
updateSettingsDisposable.set((combineLatest(updateSettingsSignal, updateCallP2PSettingsSignal) |> deliverOnMainQueue).start(completed: {
updateState { state in updateState { state in
return state.withUpdatedSaving(false) return state.withUpdatedSaving(false)
} }
if case .voiceCalls = kind, let dataSaving = state.callDataSaving, let p2pMode = state.callP2PMode, let systemIntegrationEnabled = state.callIntegrationEnabled { if case .voiceCalls = kind, let dataSaving = state.callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = state.callIntegrationEnabled {
updated(settings, VoiceCallSettings(dataSaving: dataSaving, p2pMode: p2pMode, enableSystemIntegration: systemIntegrationEnabled)) updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, p2pMode: nil, enableSystemIntegration: systemIntegrationEnabled)))
} else { } else {
updated(settings, nil) updated(settings, nil)
} }