diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 6b01901fd1..497e2c79dc 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -682,6 +682,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { if let continueActionButtonLayout = continueActionButtonLayout { let (size, apply) = continueActionButtonLayout(boundingWidth - 13.0 - insets.right) actionButtonSizeAndApply = (size, apply) + adjustedBoundingSize.width = max(adjustedBoundingSize.width, insets.left + size.width + insets.right) adjustedBoundingSize.height += 7.0 + size.height } diff --git a/TelegramUI/InstantPageAnchorItem.swift b/TelegramUI/InstantPageAnchorItem.swift index 19e24c0407..8cc7db3e74 100644 --- a/TelegramUI/InstantPageAnchorItem.swift +++ b/TelegramUI/InstantPageAnchorItem.swift @@ -22,7 +22,7 @@ final class InstantPageAnchorItem: InstantPageItem { 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 } diff --git a/TelegramUI/InstantPageArticleItem.swift b/TelegramUI/InstantPageArticleItem.swift index 739c92a8c8..cd82feae6e 100644 --- a/TelegramUI/InstantPageArticleItem.swift +++ b/TelegramUI/InstantPageArticleItem.swift @@ -25,7 +25,7 @@ final class InstantPageArticleItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageAudioItem.swift b/TelegramUI/InstantPageAudioItem.swift index d6b2b23dad..42f451c048 100644 --- a/TelegramUI/InstantPageAudioItem.swift +++ b/TelegramUI/InstantPageAudioItem.swift @@ -18,7 +18,7 @@ final class InstantPageAudioItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageControllerNode.swift b/TelegramUI/InstantPageControllerNode.swift index 63f219eeea..9da4f95b68 100644 --- a/TelegramUI/InstantPageControllerNode.swift +++ b/TelegramUI/InstantPageControllerNode.swift @@ -43,8 +43,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var visibleTiles: [Int: InstantPageTileNode] = [:] var visibleItemsWithNodes: [Int: InstantPageNode] = [:] - var currentWebEmbedHeights: [Int : Int] = [:] - var currentOpenedDetails: [Int : Bool]? = [:] + var currentWebEmbedHeights: [Int : CGFloat] = [:] + var currentExpandedDetails: [Int : Bool]? + var currentDetailsItems: [InstantPageDetailsItem] = [] var previousContentOffset: CGPoint? var isDeceleratingBecauseOfDragging = false @@ -196,7 +197,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if let strongSelf = self { if let currentLayout = strongSelf.currentLayout { for item in currentLayout.items { - if item.frame.contains(point) { + let frame = strongSelf.effectiveFrameForItem(item) + if frame.contains(point) { if item is InstantPagePeerReferenceItem { return .fail } else if item is InstantPageAudioItem { @@ -319,10 +321,11 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width) + var currentDetailsItems: [InstantPageDetailsItem] = [] var currentLayoutItemsWithNodes: [InstantPageItem] = [] var distanceThresholdGroupCount: [Int : Int] = [:] - var openedDetails: [Int : Bool] = [:] + var expandedDetails: [Int : Bool] = [:] var itemIndex = -1 for item in currentLayout.items { @@ -340,26 +343,29 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } 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 { - self.currentOpenedDetails = openedDetails + if self.currentExpandedDetails == nil { + self.currentExpandedDetails = expandedDetails } self.currentLayout = currentLayout self.currentLayoutTiles = currentLayoutTiles self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes + self.currentDetailsItems = currentDetailsItems self.distanceThresholdGroupCount = distanceThresholdGroupCount 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)) } - func updateVisibleItems() { + func updateVisibleItems(animated: Bool = false) { guard let theme = self.theme else { return } @@ -370,6 +376,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { let visibleBounds = self.scrollNode.view.bounds var topNode: ASDisplayNode? + let topTileNode = topNode if let scrollSubnodes = self.scrollNode.subnodes { for node in scrollSubnodes.reversed() { if let node = node as? InstantPageTileNode { @@ -379,34 +386,27 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - var tileIndex = -1 - for tile in self.currentLayoutTiles { - tileIndex += 1 - var tileVisibleFrame = tile.frame - tileVisibleFrame.origin.y -= 400.0 - tileVisibleFrame.size.height += 400.0 * 2.0 - 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 + let transition: ContainedViewLayoutTransition + if animated { + transition = .animated(duration: 0.3, curve: .spring) + } else { + transition = .immediate } - var collapseOffset: CGFloat = 0.0 - var itemIndex = -1 + var embedIndex = -1 + var detailsIndex = -1 + for item in self.currentLayoutItemsWithNodes { itemIndex += 1 + if item is InstantPageWebEmbedItem { + embedIndex += 1 + } + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + var itemThreshold: CGFloat = 0.0 if let group = item.distanceThresholdGroup() { var count: Int = 0 @@ -421,8 +421,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { thresholdedItemFrame.origin.y -= itemThreshold thresholdedItemFrame.size.height += itemThreshold * 2.0 - if let opened = self.currentOpenedDetails?[itemIndex], !opened { - collapseOffset = itemFrame.height - 44.0 + if let expanded = self.currentExpandedDetails?[detailsIndex], !expanded { + collapseOffset += itemFrame.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 { + 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 self?.openMedia(media) }, openPeer: { [weak self] peerId in self?.openPeer(peerId) }, openUrl: { [weak self] url in self?.openUrl(url) - }, updateWebEmbedHeight: { [weak self] key, height in - self?.updateWebEmbedHeight(key, height) - }, updateDetailsOpened: { [weak self] key, opened in - self?.updateDetailsOpened(key, opened) + }, updateWebEmbedHeight: { [weak self] height in + self?.updateWebEmbedHeight(embedIndex, height) + }, updateDetailsExpanded: { [weak self] expanded in + self?.updateDetailsExpanded(detailsIndex, expanded) }) { itemNode.frame = itemFrame if let topNode = topNode { @@ -461,12 +464,62 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } else { if (itemNode as! ASDisplayNode).frame != itemFrame { + let previousFrame = (itemNode as! ASDisplayNode).frame (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] = [] for (index, tileNode) in self.visibleTiles { if !visibleTileIndices.contains(index) { @@ -644,15 +697,40 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { 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)? { if let currentLayout = self.currentLayout { for item in currentLayout.items { - if let item = item as? InstantPageTextItem, item.selectable, item.frame.contains(location) { - return (item, CGPoint()) - } else if let item = item as? InstantPageTableItem, item.frame.contains(location) { - 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)) + let frame = self.effectiveFrameForItem(item) + if frame.contains(location) { + if let item = item as? InstantPageTextItem, item.selectable { + return (item, CGPoint()) + } else if let item = item as? InstantPageTableItem { + 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) } else if let (item, parentOffset) = self.textItemAtLocation(location) { - let textNodeFrame = item.frame + let textNodeFrame = effectiveFrameForItem(item) var itemRects = item.lineRects() 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) @@ -812,7 +890,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } case let .withBotStartPayload(botStart): 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: let _ = (strongSelf.account.postbox.loadedPeerWithId(peerId) @@ -910,13 +988,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - private func updateWebEmbedHeight(_ key: Int, _ height: Int) { - let currentHeight = self.currentWebEmbedHeights[key] + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { + let currentHeight = self.currentWebEmbedHeights[index] if height != currentHeight { if let currentHeight = currentHeight, currentHeight > height { return } - self.currentWebEmbedHeights[key] = height + self.currentWebEmbedHeights[index] = height let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) 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) { - if var currentOpenedDetails = self.currentOpenedDetails { - currentOpenedDetails[index] = opened - self.currentOpenedDetails = currentOpenedDetails + private func updateDetailsExpanded(_ index: Int, _ expanded: Bool) { + if var currentExpandedDetails = self.currentExpandedDetails { + currentExpandedDetails[index] = expanded + self.currentExpandedDetails = currentExpandedDetails } - //self.updateLayout() - self.updateVisibleItems() + self.updateVisibleItems(animated: true) } private func presentSettings() { diff --git a/TelegramUI/InstantPageDetailsItem.swift b/TelegramUI/InstantPageDetailsItem.swift index cfdf99ba87..6ad06823b7 100644 --- a/TelegramUI/InstantPageDetailsItem.swift +++ b/TelegramUI/InstantPageDetailsItem.swift @@ -10,20 +10,23 @@ final class InstantPageDetailsItem: InstantPageItem { let title: NSAttributedString let items: [InstantPageItem] + let safeInset: CGFloat let rtl: Bool + var initiallyExpanded: Bool + let index: Int - var open: Bool - - init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], rtl: Bool, open: Bool) { + init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) { self.frame = frame self.title = title self.items = items + self.safeInset = safeInset 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)? { - return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, updateDetailsOpened: updateDetailsOpened) + 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, updateDetailsExpanded: updateDetailsExpanded) } 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 { - 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) +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, safeInset: safeInset, rtl: rtl, initiallyExpanded: initiallyExpanded, index: index) } diff --git a/TelegramUI/InstantPageDetailsNode.swift b/TelegramUI/InstantPageDetailsNode.swift index 1b789b869e..a845343999 100644 --- a/TelegramUI/InstantPageDetailsNode.swift +++ b/TelegramUI/InstantPageDetailsNode.swift @@ -140,9 +140,9 @@ final class InstantPageDetailsContentNode : ASDisplayNode { //self?.openPeer(peerId) }, openUrl: { [weak self] url in //self?.openUrl(url) - }, updateWebEmbedHeight: { [weak self] key, height in + }, updateWebEmbedHeight: { [weak self] height in //self?.updateWebEmbedHeight(key, height) - }, updateDetailsOpened: { _, _ in + }, updateDetailsExpanded: { _ in }) { itemNode.frame = item.frame if let topNode = topNode { @@ -206,16 +206,16 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { private let separatorNode: ASDisplayNode private let contentNode: InstantPageDetailsContentNode - let updateOpened: (Int, Bool) -> Void - var opened: Bool + private let updateExpanded: (Bool) -> Void + 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.strings = strings self.theme = theme self.item = item - self.updateOpened = updateDetailsOpened + self.updateExpanded = updateDetailsExpanded let frame = item.frame @@ -241,12 +241,12 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { } 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.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() @@ -259,9 +259,6 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { self.addSubnode(self.separatorNode) 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.highligthedChanged = { [weak self] highlighted in @@ -287,26 +284,38 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { } @objc func buttonPressed() { - self.setOpened(!self.opened, animated: true) + self.setExpanded(!self.expanded, animated: true) } - func setOpened(_ opened: Bool, animated: Bool) { - self.opened = opened - self.arrowNode.setOpen(opened, animated: animated) - self.updateOpened(0, opened) + func setExpanded(_ expanded: Bool, animated: Bool) { + self.expanded = expanded + self.arrowNode.setOpen(expanded, animated: animated) + 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() { super.layout() let size = self.bounds.size + let inset = detailsInset + self.item.safeInset 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.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) + 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() } @@ -437,9 +446,9 @@ final class InstantPageDetailsArrowNode : ASDisplayNode { context.setLineCap(.round) context.setLineWidth(2.0) - context.move(to: CGPoint(x: 1.0, y: 6.0 - 5.0 * parameters.progress)) - context.addLine(to: CGPoint(x: 6.0, y: 1.0 + 5.0 * parameters.progress)) - context.addLine(to: CGPoint(x: 11.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: 6.0 - 5.0 * parameters.progress)) + context.addLine(to: CGPoint(x: 11.0, y: 1.0 + 5.0 * parameters.progress)) context.strokePath() } } diff --git a/TelegramUI/InstantPageFeedbackItem.swift b/TelegramUI/InstantPageFeedbackItem.swift index 2073740cb0..b3d03c59e6 100644 --- a/TelegramUI/InstantPageFeedbackItem.swift +++ b/TelegramUI/InstantPageFeedbackItem.swift @@ -15,7 +15,7 @@ final class InstantPageFeedbackItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageImageItem.swift b/TelegramUI/InstantPageImageItem.swift index da05deb8f9..dcff483c27 100644 --- a/TelegramUI/InstantPageImageItem.swift +++ b/TelegramUI/InstantPageImageItem.swift @@ -41,7 +41,7 @@ final class InstantPageImageItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index 20b6959fbe..b5d60863db 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -7,7 +7,8 @@ import SwiftSignalKit final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let account: Account - private let theme: InstantPageTheme + private let webPage: TelegramMediaWebpage + private var theme: InstantPageTheme let media: InstantPageMedia let attributes: [InstantPageImageAttribute] let url: InstantPageUrlItem? @@ -24,9 +25,12 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { 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 }) { self.account = account self.theme = theme + self.webPage = webPage self.media = media self.attributes = attributes self.url = url @@ -88,6 +92,19 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } 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() { @@ -95,8 +112,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let size = self.bounds.size - if self.currentSize != size { + if self.currentSize != size || self.themeUpdated { self.currentSize = size + self.themeUpdated = false self.imageNode.frame = CGRect(origin: CGPoint(), size: size) diff --git a/TelegramUI/InstantPageItem.swift b/TelegramUI/InstantPageItem.swift index c500e67350..99df243f44 100644 --- a/TelegramUI/InstantPageItem.swift +++ b/TelegramUI/InstantPageItem.swift @@ -10,7 +10,7 @@ protocol InstantPageItem { func matchesAnchor(_ anchor: String) -> Bool 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 linkSelectionRects(at point: CGPoint) -> [CGRect] diff --git a/TelegramUI/InstantPageLayout.swift b/TelegramUI/InstantPageLayout.swift index e67323bfe7..3e9f418109 100644 --- a/TelegramUI/InstantPageLayout.swift +++ b/TelegramUI/InstantPageLayout.swift @@ -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 var items: [InstantPageItem] = [] @@ -80,7 +80,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins switch 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): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) @@ -265,7 +265,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins var previousBlock: InstantPageBlock? var originY: CGFloat = 0.0 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 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.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)) nextItemOrigin.x += itemSize + spacing } @@ -522,7 +522,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins var previousBlock: InstantPageBlock? 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 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 { embedBoundingWidth = boundingWidth } + + let embedIndex = embedIndexCounter + embedIndexCounter += 1 + let size: CGSize if let dimensions = dimensions { 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)) } } else { - var hash: Int? - if let url = url { - hash = url.hashValue - } else if let html = html { - hash = html.hashValue - } - - if let hash = hash, let height = webEmbedHeights[hash] { + if let height = webEmbedHeights[embedIndex] { size = CGSize(width: embedBoundingWidth, height: CGFloat(height)) } else { size = CGSize(width: embedBoundingWidth, height: 44.0) @@ -619,7 +616,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins } 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) contentSize.height += 40.0 } @@ -670,13 +667,16 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins items.append(tableItem) 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 subitems: [InstantPageItem] = [] + let detailsIndex = detailsIndexCounter + detailsIndexCounter += 1 + var previousBlock: InstantPageBlock? 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 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() 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]) 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? if case let .Loaded(content) = webPage.content { maybeLoadedContent = content @@ -778,10 +778,11 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth: var mediaIndexCounter: Int = 0 var embedIndexCounter: Int = 0 + var detailsIndexCounter: Int = 0 var previousBlock: InstantPageBlock? 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 blockItems = blockLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) items.append(contentsOf: blockItems) diff --git a/TelegramUI/InstantPagePeerReferenceItem.swift b/TelegramUI/InstantPagePeerReferenceItem.swift index 5129bb8b1b..3cb90486a0 100644 --- a/TelegramUI/InstantPagePeerReferenceItem.swift +++ b/TelegramUI/InstantPagePeerReferenceItem.swift @@ -9,16 +9,18 @@ final class InstantPagePeerReferenceItem: InstantPageItem { let medias: [InstantPageMedia] = [] let initialPeer: Peer + let safeInset: CGFloat let rtl: Bool - init(frame: CGRect, initialPeer: Peer, rtl: Bool) { + init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, rtl: Bool) { self.frame = frame self.initialPeer = initialPeer + self.safeInset = safeInset 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)? { - return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, rtl: self.rtl, openPeer: openPeer) + 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, safeInset: self.safeInset, rtl: self.rtl, openPeer: openPeer) } func matchesAnchor(_ anchor: String) -> Bool { @@ -27,7 +29,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem { func matchesNode(_ node: InstantPageNode) -> Bool { 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 { return false } diff --git a/TelegramUI/InstantPagePeerReferenceNode.swift b/TelegramUI/InstantPagePeerReferenceNode.swift index 18eb3edff8..9f8919309d 100644 --- a/TelegramUI/InstantPagePeerReferenceNode.swift +++ b/TelegramUI/InstantPagePeerReferenceNode.swift @@ -44,6 +44,7 @@ private enum JoinState: Equatable { final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private let account: Account let initialPeer: Peer + let safeInset: CGFloat private let rtl: Bool private var strings: PresentationStrings private var theme: InstantPageTheme @@ -63,11 +64,12 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { 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.strings = strings self.theme = theme self.initialPeer = initialPeer + self.safeInset = safeInset self.rtl = rtl self.openPeer = openPeer @@ -219,7 +221,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { super.layout() 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.buttonNode.frame = CGRect(origin: CGPoint(), size: size) @@ -230,15 +232,15 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { let indicatorSize = self.activityIndicator.measure(size) 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.joinNode.frame = CGRect(origin: CGPoint(x: 17.0, 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.activityIndicator.frame = CGRect(origin: CGPoint(x: 17.0, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize) + 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: inset, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize) + 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: inset, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize) } else { - self.nameNode.frame = CGRect(origin: CGPoint(x: 17.0, 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.checkNode.frame = CGRect(origin: CGPoint(x: size.width - 17.0 - 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.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 - inset - joinSize.width, y: floor((size.height - joinSize.height) / 2.0)), size: joinSize) + 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 - inset - indicatorSize.width, y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize) } } diff --git a/TelegramUI/InstantPagePlayableVideoItem.swift b/TelegramUI/InstantPagePlayableVideoItem.swift index 949988edf9..a1c9f2448c 100644 --- a/TelegramUI/InstantPagePlayableVideoItem.swift +++ b/TelegramUI/InstantPagePlayableVideoItem.swift @@ -23,7 +23,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageShapeItem.swift b/TelegramUI/InstantPageShapeItem.swift index 614f9725cc..b4a0605b30 100644 --- a/TelegramUI/InstantPageShapeItem.swift +++ b/TelegramUI/InstantPageShapeItem.swift @@ -56,7 +56,7 @@ final class InstantPageShapeItem: InstantPageItem { 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 } diff --git a/TelegramUI/InstantPageSlideshowItem.swift b/TelegramUI/InstantPageSlideshowItem.swift index da660804f6..498a7445af 100644 --- a/TelegramUI/InstantPageSlideshowItem.swift +++ b/TelegramUI/InstantPageSlideshowItem.swift @@ -15,7 +15,7 @@ final class InstantPageSlideshowItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageTableItem.swift b/TelegramUI/InstantPageTableItem.swift index dfd73fc87d..d9ea022a0d 100644 --- a/TelegramUI/InstantPageTableItem.swift +++ b/TelegramUI/InstantPageTableItem.swift @@ -166,7 +166,7 @@ final class InstantPageTableItem: InstantPageItem { 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) } @@ -227,7 +227,7 @@ final class InstantPageTableContentNode: ASDisplayNode { for cell in self.item.cells { for item in cell.additionalItems { 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) self.addSubnode(node) } diff --git a/TelegramUI/InstantPageTextItem.swift b/TelegramUI/InstantPageTextItem.swift index 71950bd850..9b3e9e2b46 100644 --- a/TelegramUI/InstantPageTextItem.swift +++ b/TelegramUI/InstantPageTextItem.swift @@ -309,7 +309,7 @@ final class InstantPageTextItem: InstantPageItem { 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 } diff --git a/TelegramUI/InstantPageWebEmbedItem.swift b/TelegramUI/InstantPageWebEmbedItem.swift index 5ae8a3fdc9..04d63eb7e8 100644 --- a/TelegramUI/InstantPageWebEmbedItem.swift +++ b/TelegramUI/InstantPageWebEmbedItem.swift @@ -19,7 +19,7 @@ final class InstantPageWebEmbedItem: InstantPageItem { 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) } diff --git a/TelegramUI/InstantPageWebEmbedNode.swift b/TelegramUI/InstantPageWebEmbedNode.swift index ef1876fdbb..2d554944db 100644 --- a/TelegramUI/InstantPageWebEmbedNode.swift +++ b/TelegramUI/InstantPageWebEmbedNode.swift @@ -20,11 +20,11 @@ private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessa final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { let url: String? let html: String? - let updateWebEmbedHeight: (Int, Int) -> Void + let updateWebEmbedHeight: (CGFloat) -> Void 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.html = html self.updateWebEmbedHeight = updateWebEmbedHeight @@ -91,15 +91,7 @@ final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { } if eventName == "resize_frame", let height = dict["height"] as? Int { - var hash: Int? - 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) - } + self.updateWebEmbedHeight(CGFloat(height)) } } diff --git a/TelegramUI/OngoingCallContext.swift b/TelegramUI/OngoingCallContext.swift index fb7230b8b9..1578e99ffb 100644 --- a/TelegramUI/OngoingCallContext.swift +++ b/TelegramUI/OngoingCallContext.swift @@ -125,7 +125,7 @@ final class OngoingCallContext { private let audioSessionDisposable = MetaDisposable() private var networkTypeDisposable: Disposable? - init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, allowP2P: Bool, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal) { + init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal) { let _ = setupLogs self.internalId = internalId @@ -142,7 +142,7 @@ final class OngoingCallContext { 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) context.stateChanged = { [weak self] state in self?.contextState.set(.single(state)) @@ -186,13 +186,13 @@ final class OngoingCallContext { } } - func start(key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, audioSessionActive: Signal) { + func start(key: Data, isOutgoing: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, allowP2P: Bool, audioSessionActive: Signal) { self.audioSessionDisposable.set((audioSessionActive |> filter { $0 } |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { 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) } } })) diff --git a/TelegramUI/OngoingCallThreadLocalContext.h b/TelegramUI/OngoingCallThreadLocalContext.h index d6af838966..2ea0e9b970 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.h +++ b/TelegramUI/OngoingCallThreadLocalContext.h @@ -54,8 +54,8 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) { @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); -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue allowP2P:(BOOL)allowP2P proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType; -- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType; +- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P; - (void)stop; - (NSString * _Nullable)debugInfo; diff --git a/TelegramUI/OngoingCallThreadLocalContext.mm b/TelegramUI/OngoingCallThreadLocalContext.mm index 96ce9a56e6..96888d6de2 100644 --- a/TelegramUI/OngoingCallThreadLocalContext.mm +++ b/TelegramUI/OngoingCallThreadLocalContext.mm @@ -127,7 +127,6 @@ static void withContext(int32_t contextId, void (^f)(OngoingCallThreadLocalConte NSTimeInterval _callConnectTimeout; NSTimeInterval _callPacketTimeout; int32_t _dataSavingMode; - bool _allowP2P; tgvoip::VoIPController *_controller; @@ -181,7 +180,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { TGVoipLoggingFunction = loggingFunction; } -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue allowP2P:(BOOL)allowP2P proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType { self = [super init]; if (self != nil) { _queue = queue; @@ -193,7 +192,6 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { _callConnectTimeout = 30.0; _callPacketTimeout = 10.0; _dataSavingMode = 0; - _allowP2P = allowP2P; _networkType = networkType; _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 * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer { +- (void)startWithKey:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescription * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P { std::vector endpoints; NSArray *connections = [@[primaryConnection] arrayByAddingObjectsFromArray:alternativeConnections]; for (OngoingCallConnectionDescription *connection in connections) { @@ -261,7 +259,7 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) { _controller->SetConfig(config); _controller->SetEncryptionKey((char *)key.bytes, isOutgoing); - _controller->SetRemoteEndpoints(endpoints, _allowP2P, maxLayer); + _controller->SetRemoteEndpoints(endpoints, allowP2P, maxLayer); _controller->Start(); _controller->Connect(); diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 3d2d7d541b..a76a05d165 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -188,6 +188,7 @@ public class PeerMediaCollectionController: TelegramController { }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] content in if let strongSelf = self { + strongSelf.view.endEditing(true) switch content { case let .url(url): let canOpenIn = availableOpenInOptions(applicationContext: strongSelf.account.telegramApplicationContext, item: .url(url: url)).count > 1 diff --git a/TelegramUI/PresentationCall.swift b/TelegramUI/PresentationCall.swift index ab444a92a1..648eff985b 100644 --- a/TelegramUI/PresentationCall.swift +++ b/TelegramUI/PresentationCall.swift @@ -230,7 +230,7 @@ public final class PresentationCall { self.isOutgoing = isOutgoing 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 self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId) @@ -413,7 +413,7 @@ public final class PresentationCall { presentationState = .terminated(reason) case let .requesting(ringing): presentationState = .requesting(ringing) - case let .active(_, keyVisualHash, _, _): + case let .active(_, keyVisualHash, _, _, _): if let callContextState = callContextState { switch callContextState { case .initializing: @@ -441,10 +441,10 @@ public final class PresentationCall { if let _ = audioSessionControl { self.audioSessionShouldBeActive.set(true) } - case let .active(key, _, connections, maxLayer): + case let .active(key, _, connections, maxLayer, allowsP2P): self.audioSessionShouldBeActive.set(true) 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 { self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date()) } diff --git a/TelegramUI/PrivacyAndSecurityController.swift b/TelegramUI/PrivacyAndSecurityController.swift index 5e7404d69d..b90a4c80f6 100644 --- a/TelegramUI/PrivacyAndSecurityController.swift +++ b/TelegramUI/PrivacyAndSecurityController.swift @@ -376,7 +376,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign |> deliverOnMainQueue |> mapToSignal { value -> Signal in 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() } @@ -399,7 +399,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign |> deliverOnMainQueue |> mapToSignal { value -> Signal in 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() } @@ -424,8 +424,8 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign currentInfoDisposable.set((combineLatest(privacySignal, callsSignal) |> deliverOnMainQueue).start(next: { [weak currentInfoDisposable] info, callSettings in 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 - if let currentInfoDisposable = currentInfoDisposable, let updatedCallSettings = updatedCallSettings { + 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 (updatedCallsPrivacy, updatedCallSettings) = updatedCallSettings { let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { _ in return updatedCallSettings }).start() @@ -436,7 +436,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign |> deliverOnMainQueue |> mapToSignal { value -> Signal in 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() } @@ -485,7 +485,7 @@ public func privacyAndSecurityController(account: Account, initialSettings: Sign |> deliverOnMainQueue |> mapToSignal { value -> Signal in 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() } diff --git a/TelegramUI/SelectivePrivacySettingsController.swift b/TelegramUI/SelectivePrivacySettingsController.swift index 1fe76569fb..a8ede6f450 100644 --- a/TelegramUI/SelectivePrivacySettingsController.swift +++ b/TelegramUI/SelectivePrivacySettingsController.swift @@ -27,24 +27,29 @@ private enum SelectivePrivacySettingType { } } +enum SelectivePrivacySettingsPeerTarget { + case main + case callP2P +} + private final class SelectivePrivacySettingsControllerArguments { let account: Account let updateType: (SelectivePrivacySettingType) -> Void - let openEnableFor: () -> Void - let openDisableFor: () -> Void + let openEnableFor: (SelectivePrivacySettingsPeerTarget) -> Void + let openDisableFor: (SelectivePrivacySettingsPeerTarget) -> Void - let updateCallsP2PMode: ((VoiceCallP2PMode) -> Void)? - let updateCallsIntegrationEnabled: ((Bool) -> Void)? + let updateCallP2PMode: ((SelectivePrivacySettingType) -> 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.updateType = updateType self.openEnableFor = openEnableFor self.openDisableFor = openDisableFor - self.updateCallsP2PMode = updateCallsP2PMode - self.updateCallsIntegrationEnabled = updateCallsIntegrationEnabled + self.updateCallP2PMode = updateCallP2PMode + self.updateCallIntegrationEnabled = updateCallIntegrationEnabled } } @@ -52,6 +57,7 @@ private enum SelectivePrivacySettingsSection: Int32 { case setting case peers case callsP2P + case callsP2PPeers case callsIntegrationEnabled } @@ -77,6 +83,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case callsP2PContacts(PresentationTheme, String, Bool) case callsP2PNever(PresentationTheme, String, Bool) case callsP2PInfo(PresentationTheme, String) + case callsP2PDisableFor(PresentationTheme, String, String) + case callsP2PEnableFor(PresentationTheme, String, String) + case callsP2PPeersInfo(PresentationTheme, String) case callsIntegrationEnabled(PresentationTheme, String, Bool) case callsIntegrationInfo(PresentationTheme, String) @@ -88,6 +97,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return SelectivePrivacySettingsSection.peers.rawValue case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo: return SelectivePrivacySettingsSection.callsP2P.rawValue + case .callsP2PDisableFor, .callsP2PEnableFor, .callsP2PPeersInfo: + return SelectivePrivacySettingsSection.callsP2PPeers.rawValue case .callsIntegrationEnabled, .callsIntegrationInfo: return SelectivePrivacySettingsSection.callsIntegrationEnabled.rawValue } @@ -121,10 +132,16 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return 11 case .callsP2PInfo: return 12 - case .callsIntegrationEnabled: + case .callsP2PDisableFor: return 13 - case .callsIntegrationInfo: + case .callsP2PEnableFor: return 14 + case .callsP2PPeersInfo: + return 15 + case .callsIntegrationEnabled: + return 16 + case .callsIntegrationInfo: + return 17 } } @@ -208,6 +225,24 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { 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): if case let .callsIntegrationEnabled(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -247,11 +282,11 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .disableFor(theme, title, value): 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): return ItemListDisclosureItem(theme: theme, title: title, label: value, sectionId: self.section, style: .blocks, action: { - arguments.openEnableFor() + arguments.openEnableFor(.main) }) case let .peersInfo(theme, text): 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) case let .callsP2PAlways(theme, text, value): 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): 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): 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): 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): 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): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) @@ -289,17 +334,21 @@ private struct SelectivePrivacySettingsControllerState: Equatable { let saving: Bool let callDataSaving: VoiceCallDataSaving? - let callP2PMode: VoiceCallP2PMode? + let callP2PMode: SelectivePrivacySettingType? + let callP2PEnableFor: Set? + let callP2PDisableFor: Set? let callIntegrationAvailable: Bool? let callIntegrationEnabled: Bool? - init(setting: SelectivePrivacySettingType, enableFor: Set, disableFor: Set, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: VoiceCallP2PMode?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) { + init(setting: SelectivePrivacySettingType, enableFor: Set, disableFor: Set, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: Set?, callP2PDisableFor: Set?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?) { self.setting = setting self.enableFor = enableFor self.disableFor = disableFor self.saving = saving self.callDataSaving = callDataSaving self.callP2PMode = callP2PMode + self.callP2PEnableFor = callP2PEnableFor + self.callP2PDisableFor = callP2PDisableFor self.callIntegrationAvailable = callIntegrationAvailable self.callIntegrationEnabled = callIntegrationEnabled } @@ -323,6 +372,12 @@ private struct SelectivePrivacySettingsControllerState: Equatable { if lhs.callP2PMode != rhs.callP2PMode { return false } + if lhs.callP2PEnableFor != rhs.callP2PEnableFor { + return false + } + if lhs.callP2PDisableFor != rhs.callP2PDisableFor { + return false + } if lhs.callIntegrationAvailable != rhs.callIntegrationAvailable { return false } @@ -334,27 +389,35 @@ private struct SelectivePrivacySettingsControllerState: Equatable { } 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) -> 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) -> 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 { - 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 { - 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) + func withUpdatedCallP2PMode(_ mode: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { + 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) -> 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) -> 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 { - 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 { 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(.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)) + 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 { entries.append(.callsIntegrationEnabled(presentationData.theme, presentationData.strings.Privacy_Calls_Integration, integrationEnabled)) entries.append(.callsIntegrationInfo(presentationData.theme, presentationData.strings.Privacy_Calls_IntegrationHelp)) @@ -424,7 +499,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present 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 var initialEnableFor = Set() @@ -438,7 +513,23 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy case let .enableEveryone(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? + var initialCallP2PDisableFor: Set? + if let callCurrent = callSettings?.0 { + switch callCurrent { + case let .disableEveryone(enableFor): + initialCallP2PEnableFor = enableFor + initialCallP2PDisableFor = Set() + case let .enableContacts(enableFor, disableFor): + initialCallP2PEnableFor = enableFor + initialCallP2PDisableFor = disableFor + case let .enableEveryone(disableFor): + initialCallP2PEnableFor = Set() + 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 stateValue = Atomic(value: initialState) @@ -458,7 +549,7 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy updateState { $0.withUpdatedSetting(type) } - }, openEnableFor: { + }, openEnableFor: { target in let title: String switch kind { case .presence: @@ -475,10 +566,15 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy } pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds 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 switch kind { case .presence: @@ -495,19 +591,19 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy } pushControllerImpl?(selectivePrivacyPeersController(account: account, title: title, initialPeerIds: Array(peerIds), updated: { updatedPeerIds 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 - return state.withUpdatedCallsP2PMode(mode) + return state.withUpdatedCallP2PMode(mode) } - let _ = updateVoiceCallSettingsSettingsInteractively(postbox: account.postbox, { settings in - var settings = settings - settings.p2pMode = mode - return settings - }).start() - }, updateCallsIntegrationEnabled: { enabled in + }, updateCallIntegrationEnabled: { enabled in updateState { state in 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: { var wasSaving = false var settings: SelectivePrivacySettings? + var callP2PSettings: SelectivePrivacySettings? updateState { state in wasSaving = state.saving switch state.setting { @@ -542,6 +639,18 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy case .nobody: 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) } @@ -556,12 +665,18 @@ func selectivePrivacySettingsController(account: Account, kind: SelectivePrivacy 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 = 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 return state.withUpdatedSaving(false) } - if case .voiceCalls = kind, let dataSaving = state.callDataSaving, let p2pMode = state.callP2PMode, let systemIntegrationEnabled = state.callIntegrationEnabled { - updated(settings, VoiceCallSettings(dataSaving: dataSaving, p2pMode: p2pMode, enableSystemIntegration: systemIntegrationEnabled)) + if case .voiceCalls = kind, let dataSaving = state.callDataSaving, let callP2PSettings = callP2PSettings, let systemIntegrationEnabled = state.callIntegrationEnabled { + updated(settings, (callP2PSettings, VoiceCallSettings(dataSaving: dataSaving, p2pMode: nil, enableSystemIntegration: systemIntegrationEnabled))) } else { updated(settings, nil) }