diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4d19e9e044..32b0d260f6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12473,6 +12473,10 @@ Sorry for the inconvenience."; "WebApp.MinimizedTitle.Others_1" = "%@ Other"; "WebApp.MinimizedTitle.Others_any" = "%@ Others"; +"WebApp.Minimized.CloseAllTitle" = "Are you sure you want to close all open tabs?"; +"WebApp.Minimized.CloseAll_1" = "Close All %@ Tab"; +"WebApp.Minimized.CloseAll_any" = "Close All %@ Tabs"; + "Stars.SendStars.Title" = "Send Stars"; "Stars.SendStars.AmountTitle" = "ENTER AMOUNT"; "Stars.SendStars.AmountPlaceholder" = "Stars Amount"; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 6154933932..1ddaf947db 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -71,6 +71,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { var isPanningUpdated: (Bool) -> Void = { _ in } var isExpandedUpdated: (Bool) -> Void = { _ in } var isPanGestureEnabled: (() -> Bool)? + var isInnerPanGestureEnabled: (() -> Bool)? var onExpandAnimationCompleted: () -> Void = {} init(isFullSize: Bool) { @@ -146,6 +147,23 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { return false } } + if let isInnerPanGestureEnabled = self.isInnerPanGestureEnabled, !isInnerPanGestureEnabled() { + func findWebViewAncestor(view: UIView?) -> WKWebView? { + guard let view else { + return nil + } + if let view = view as? WKWebView { + return view + } else if view != self.view { + return findWebViewAncestor(view: view.superview) + } else { + return nil + } + } + if let otherView = self.hitTest(gestureRecognizer.location(in: self.view), with: nil), let _ = findWebViewAncestor(view: otherView) { + return false + } + } } return true } @@ -163,23 +181,6 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { } return true } - if gestureRecognizer is UIPanGestureRecognizer { - func findWebViewAncestor(view: UIView?) -> WKWebView? { - guard let view else { - return nil - } - if let view = view as? WKWebView { - return view - } else if view != self.view { - return findWebViewAncestor(view: view.superview) - } else { - return nil - } - } - if let otherView = otherGestureRecognizer.view, let _ = findWebViewAncestor(view: otherView) { - return true - } - } if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UILongPressGestureRecognizer { return true } @@ -197,7 +198,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - guard let (layout, controllers, coveredByModalTransition) = self.validLayout else { + guard let (layout, controllers, coveredByModalTransition) = self.validLayout, let lastController = controllers.last else { return } @@ -271,9 +272,13 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { } if !self.isExpanded || self.isFullSize, translation > 40.0, let shouldCancelPanGesture = self.shouldCancelPanGesture, shouldCancelPanGesture() { - self.cancelPanGesture() - self.requestDismiss?() - return + if lastController.isMinimizable { + + } else { + self.cancelPanGesture() + self.requestDismiss?() + return + } } var bounds = self.bounds @@ -323,7 +328,11 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { var ignoreDismiss = false if let shouldCancelPanGesture = self.shouldCancelPanGesture, shouldCancelPanGesture() { - ignoreDismiss = true + if lastController.isMinimizable { + + } else { + ignoreDismiss = true + } } var minimizing = false diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index d091fb31f6..06f5ef926b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -121,6 +121,7 @@ public protocol AttachmentContainable: ViewController, MinimizableController { var isContainerPanning: () -> Bool { get set } var isContainerExpanded: () -> Bool { get set } var isPanGestureEnabled: (() -> Bool)? { get } + var isInnerPanGestureEnabled: (() -> Bool)? { get } var mediaPickerContext: AttachmentMediaPickerContext? { get } var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get } @@ -172,6 +173,10 @@ public extension AttachmentContainable { return nil } + var isInnerPanGestureEnabled: (() -> Bool)? { + return nil + } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { return nil } @@ -196,6 +201,10 @@ public protocol AttachmentMediaPickerContext { var captionIsAboveMedia: Signal { get } func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void + var canMakePaidContent: Bool { get } + var price: Int64? { get } + func setPrice(_ price: Int64) -> Void + var loadingProgress: Signal { get } var mainButtonState: Signal { get } @@ -206,6 +215,58 @@ public protocol AttachmentMediaPickerContext { func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) } +public extension AttachmentMediaPickerContext { + var selectionCount: Signal { + return .single(0) + } + + var caption: Signal { + return .single(nil) + } + + var captionIsAboveMedia: Signal { + return .single(false) + } + + var hasCaption: Bool { + return false + } + + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { + } + + var canMakePaidContent: Bool { + return false + } + + var price: Int64? { + return nil + } + + func setPrice(_ price: Int64) -> Void { + } + + var loadingProgress: Signal { + return .single(nil) + } + + var mainButtonState: Signal { + return .single(nil) + } + + func setCaption(_ caption: NSAttributedString) { + } + + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { + } + + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { + } + + func mainButtonAction() { + } +} + private func generateShadowImage() -> UIImage? { return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -444,6 +505,17 @@ public class AttachmentController: ViewController, MinimizableController { } } + self.container.isInnerPanGestureEnabled = { [weak self] in + guard let self, let currentController = self.currentControllers.last else { + return true + } + if let isInnerPanGestureEnabled = currentController.isInnerPanGestureEnabled { + return isInnerPanGestureEnabled() + } else { + return true + } + } + self.container.shouldCancelPanGesture = { [weak self] in if let strongSelf = self, let currentController = strongSelf.currentControllers.last { if !currentController.shouldDismissImmediately() { @@ -1191,6 +1263,14 @@ public class AttachmentController: ViewController, MinimizableController { } } + public var isMinimizable: Bool { + return self.mainController.isMinimizable + } + + public func shouldDismissImmediately() -> Bool { + return self.mainController.shouldDismissImmediately() + } + private var validLayout: ContainerViewLayout? override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 6d9605d02d..e93bf7d5a1 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -985,8 +985,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } var captionIsAboveMedia: Signal = .single(false) + var canMakePaidContent = false + var currentPrice: Int64? if let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext { captionIsAboveMedia = mediaPickerContext.captionIsAboveMedia + canMakePaidContent = mediaPickerContext.canMakePaidContent + currentPrice = mediaPickerContext.price } let _ = (combineLatest( @@ -1016,7 +1020,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { messageEffect: nil, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, - forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], + canMakePaidContent: canMakePaidContent, + currentPrice: currentPrice )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, @@ -1038,6 +1044,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { schedule: { [weak textInputPanelNode] messageEffect in textInputPanelNode?.sendMessage(.schedule, messageEffect) }, + editPrice: { [weak strongSelf] price in + guard let strongSelf, let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext else { + return + } + mediaPickerContext.setPrice(price) + }, openPremiumPaywall: { [weak self] c in guard let self else { return diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 9bc316a4a8..d3e337a910 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -10,23 +10,26 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/AppBundle:AppBundle", - "//submodules/InstantPageUI:InstantPageUI", - "//submodules/ContextUI:ContextUI", - "//submodules/UndoUI:UndoUI", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AppBundle", + "//submodules/InstantPageUI", + "//submodules/ContextUI", + "//submodules/UndoUI", + "//submodules/TranslateUI", "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", "//submodules/Components/BundleIconComponent:BundleIconComponent", "//submodules/Components/BlurredBackgroundComponent:BlurredBackgroundComponent", "//submodules/TelegramUI/Components/MinimizedContainer", + "//submodules/Pasteboard", + "//submodules/SaveToCameraRoll", ], visibility = [ "//visibility:public", diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index e000456868..9138570b48 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -1,310 +1,361 @@ -//import Foundation -//import UIKit -//import AsyncDisplayKit -//import TelegramCore -//import Postbox -//import SwiftSignalKit -//import Display -//import ComponentFlow -//import TelegramPresentationData -//import TelegramUIPreferences -//import AccountContext -//import AppBundle -//import InstantPageUI -// -//final class InstantPageView: UIView, UIScrollViewDelegate { -// private let webPage: TelegramMediaWebpage -// private var initialAnchor: String? -// private var pendingAnchor: String? -// private var initialState: InstantPageStoredState? -// -// private let scrollNode: ASScrollNode -// private let scrollNodeHeader: ASDisplayNode -// private let scrollNodeFooter: ASDisplayNode -// private var linkHighlightingNode: LinkHighlightingNode? -// private var textSelectionNode: LinkHighlightingNode? -// -// var currentLayout: InstantPageLayout? -// var currentLayoutTiles: [InstantPageTile] = [] -// var currentLayoutItemsWithNodes: [InstantPageItem] = [] -// var distanceThresholdGroupCount: [Int: Int] = [:] -// -// var visibleTiles: [Int: InstantPageTileNode] = [:] -// var visibleItemsWithNodes: [Int: InstantPageNode] = [:] -// -// var currentWebEmbedHeights: [Int : CGFloat] = [:] -// var currentExpandedDetails: [Int : Bool]? -// var currentDetailsItems: [InstantPageDetailsItem] = [] -// -// var currentAccessibilityAreas: [AccessibilityAreaNode] = [] -// -// init(webPage: TelegramMediaWebpage) { -// self.webPage = webPage -// -// self.scrollNode = ASScrollNode() -// -// super.init(frame: frame) -// -// self.addSubview(self.scrollNode.view) -// -// self.scrollNode.view.delaysContentTouches = false -// self.scrollNode.view.delegate = self -// -// if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { -// self.scrollNode.view.contentInsetAdjustmentBehavior = .never -// } -// -// let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) -// recognizer.delaysTouchesBegan = false -// recognizer.tapActionAtPoint = { [weak self] point in -// if let strongSelf = self { -// return strongSelf.tapActionAtPoint(point) -// } -// return .waitForSingleTap -// } -// recognizer.highlight = { [weak self] point in -// if let strongSelf = self { -// strongSelf.updateTouchesAtPoint(point) -// } -// } -// self.scrollNode.view.addGestureRecognizer(recognizer) -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction { -// if let currentLayout = self.currentLayout { -// for item in currentLayout.items { -// let frame = self.effectiveFrameForItem(item) -// if frame.contains(point) { -// if item is InstantPagePeerReferenceItem { -// return .fail -// } else if item is InstantPageAudioItem { -// return .fail -// } else if item is InstantPageArticleItem { -// return .fail -// } else if item is InstantPageFeedbackItem { -// return .fail -// } else if let item = item as? InstantPageDetailsItem { -// for (_, itemNode) in self.visibleItemsWithNodes { -// if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item { -// return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY)) -// } -// } -// } -// if !(item is InstantPageImageItem || item is InstantPagePlayableVideoItem) { -// break -// } -// } -// } -// } -// return .waitForSingleTap -// } -// -// private func updateTouchesAtPoint(_ location: CGPoint?) { -// var rects: [CGRect]? -// if let location = location, let currentLayout = self.currentLayout { -// for item in currentLayout.items { -// let itemFrame = self.effectiveFrameForItem(item) -// if itemFrame.contains(location) { -// var contentOffset = CGPoint() -// if let item = item as? InstantPageScrollableItem { -// contentOffset = self.scrollableContentOffset(item: item) -// } -// var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) -// -// for i in 0 ..< itemRects.count { -// itemRects[i] = itemRects[i].offsetBy(dx: itemFrame.minX - contentOffset.x, dy: itemFrame.minY).insetBy(dx: -2.0, dy: -2.0) -// } -// if !itemRects.isEmpty { -// rects = itemRects -// break -// } -// } -// } -// } -// -// if let rects = rects { -// let linkHighlightingNode: LinkHighlightingNode -// if let current = self.linkHighlightingNode { -// linkHighlightingNode = current -// } else { -// let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x007aff).withAlphaComponent(0.4) -// linkHighlightingNode = LinkHighlightingNode(color: highlightColor) -// linkHighlightingNode.isUserInteractionEnabled = false -// self.linkHighlightingNode = linkHighlightingNode -// self.scrollNode.addSubnode(linkHighlightingNode) -// } -// linkHighlightingNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) -// linkHighlightingNode.updateRects(rects) -// } else if let linkHighlightingNode = self.linkHighlightingNode { -// self.linkHighlightingNode = nil -// linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in -// linkHighlightingNode?.removeFromSupernode() -// }) -// } -// } -// -// private func updatePageLayout() { -// guard let containerLayout = self.containerLayout, let webPage = self.webPage, let theme = self.theme else { -// return -// } -// -// let currentLayout = instantPageLayoutForWebPage(webPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) -// -// for (_, tileNode) in self.visibleTiles { -// tileNode.removeFromSupernode() -// } -// self.visibleTiles.removeAll() -// -// let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width) -// -// var currentDetailsItems: [InstantPageDetailsItem] = [] -// var currentLayoutItemsWithNodes: [InstantPageItem] = [] -// var distanceThresholdGroupCount: [Int : Int] = [:] -// -// var expandedDetails: [Int : Bool] = [:] -// -// var detailsIndex = -1 -// for item in currentLayout.items { -// if item.wantsNode { -// currentLayoutItemsWithNodes.append(item) -// if let group = item.distanceThresholdGroup() { -// let count: Int -// if let currentCount = distanceThresholdGroupCount[Int(group)] { -// count = currentCount -// } else { -// count = 0 -// } -// distanceThresholdGroupCount[Int(group)] = count + 1 -// } -// if let detailsItem = item as? InstantPageDetailsItem { -// detailsIndex += 1 -// expandedDetails[detailsIndex] = detailsItem.initiallyExpanded -// currentDetailsItems.append(detailsItem) -// } -// } -// } -// -// if var currentExpandedDetails = self.currentExpandedDetails { -// for (index, expanded) in expandedDetails { -// if currentExpandedDetails[index] == nil { -// currentExpandedDetails[index] = expanded -// } -// } -// self.currentExpandedDetails = currentExpandedDetails -// } else { -// self.currentExpandedDetails = expandedDetails -// } -// -// let accessibilityAreas = instantPageAccessibilityAreasFromLayout(currentLayout, boundingWidth: containerLayout.size.width) -// -// self.currentLayout = currentLayout -// self.currentLayoutTiles = currentLayoutTiles -// self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes -// self.currentDetailsItems = currentDetailsItems -// self.distanceThresholdGroupCount = distanceThresholdGroupCount -// -// for areaNode in self.currentAccessibilityAreas { -// areaNode.removeFromSupernode() -// } -// for areaNode in accessibilityAreas { -// self.scrollNode.addSubnode(areaNode) -// } -// self.currentAccessibilityAreas = accessibilityAreas -// -// 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(visibleBounds: CGRect, animated: Bool = false) { -// guard let theme = self.theme else { -// return -// } -// -// var visibleTileIndices = Set() -// var visibleItemIndices = Set() -// -// var topNode: ASDisplayNode? -// let topTileNode = topNode -// if let scrollSubnodes = self.scrollNode.subnodes { -// for node in scrollSubnodes.reversed() { -// if let node = node as? InstantPageTileNode { -// topNode = node -// break -// } -// } -// } -// -// var collapseOffset: CGFloat = 0.0 -// let transition: ContainedViewLayoutTransition -// if animated { -// transition = .animated(duration: 0.3, curve: .spring) -// } else { -// transition = .immediate -// } -// -// var itemIndex = -1 -// var embedIndex = -1 -// var detailsIndex = -1 -// -// var previousDetailsNode: InstantPageDetailsNode? -// -// for item in self.currentLayoutItemsWithNodes { -// itemIndex += 1 -// if item is InstantPageWebEmbedItem { -// embedIndex += 1 -// } -// if let imageItem = item as? InstantPageImageItem, imageItem.media.media is TelegramMediaWebpage { -// embedIndex += 1 -// } -// if item is InstantPageDetailsItem { -// detailsIndex += 1 -// } -// -// var itemThreshold: CGFloat = 0.0 -// if let group = item.distanceThresholdGroup() { -// var count: Int = 0 -// if let currentCount = self.distanceThresholdGroupCount[group] { -// count = currentCount -// } -// itemThreshold = item.distanceThresholdWithGroupCount(count) -// } -// -// var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset) -// var thresholdedItemFrame = itemFrame -// thresholdedItemFrame.origin.y -= itemThreshold -// thresholdedItemFrame.size.height += itemThreshold * 2.0 -// -// if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] { -// let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight -// collapseOffset += itemFrame.height - height -// itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height)) -// } -// -// if visibleBounds.intersects(thresholdedItemFrame) { -// visibleItemIndices.insert(itemIndex) -// -// var itemNode = self.visibleItemsWithNodes[itemIndex] -// if let currentItemNode = itemNode { -// if !item.matchesNode(currentItemNode) { -// currentItemNode.removeFromSupernode() -// self.visibleItemsWithNodes.removeValue(forKey: itemIndex) -// itemNode = nil -// } -// } -// -// if itemNode == nil { -// let itemIndex = itemIndex -// let embedIndex = embedIndex -// let detailsIndex = detailsIndex -// if let newNode = item.node(context: self.context, strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, theme: theme, sourceLocation: self.sourceLocation, openMedia: { [weak self] media in -// self?.openMedia(media) -// }, longPressMedia: { [weak self] media in -// self?.longPressMedia(media) -// }, activatePinchPreview: { [weak self] sourceNode in +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramCore +import Postbox +import SwiftSignalKit +import Display +import ComponentFlow +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import InstantPageUI +import UndoUI +import TranslateUI +import ContextUI +import Pasteboard +import SaveToCameraRoll +import ShareController + +private final class InstantPageContainerNode: ASDisplayNode, UIScrollViewDelegate { + private let context: AccountContext + private let webPage: TelegramMediaWebpage + private let presentationData: PresentationData + private let theme: InstantPageTheme + private let sourceLocation: InstantPageSourceLocation + + private var initialAnchor: String? + private var pendingAnchor: String? + private var initialState: InstantPageStoredState? + + fileprivate let scrollNode: ASScrollNode + private let scrollNodeFooter: ASDisplayNode + private var linkHighlightingNode: LinkHighlightingNode? + private var textSelectionNode: LinkHighlightingNode? + + var currentLayout: InstantPageLayout? + var currentLayoutTiles: [InstantPageTile] = [] + var currentLayoutItemsWithNodes: [InstantPageItem] = [] + var distanceThresholdGroupCount: [Int: Int] = [:] + + var visibleTiles: [Int: InstantPageTileNode] = [:] + var visibleItemsWithNodes: [Int: InstantPageNode] = [:] + + var currentWebEmbedHeights: [Int : CGFloat] = [:] + var currentExpandedDetails: [Int : Bool]? + var currentDetailsItems: [InstantPageDetailsItem] = [] + + var currentAccessibilityAreas: [AccessibilityAreaNode] = [] + + var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } + var openMedia: (InstantPageMedia) -> Void = { _ in } + var longPressMedia: (InstantPageMedia) -> Void = { _ in } + var openPeer: (EnginePeer) -> Void = { _ in } + var openUrl: (InstantPageUrlItem) -> Void = { _ in } + var activatePinchPreview: ((PinchSourceContainerNode) -> Void)? + var pinchPreviewFinished: ((InstantPageNode) -> Void)? + + var present: (ViewController, Any?) -> Void = { _, _ in } + + private let updateLayoutDisposable = MetaDisposable() + + private var containerLayout: (size: CGSize, insets: UIEdgeInsets)? + + init(context: AccountContext, webPage: TelegramMediaWebpage, sourceLocation: InstantPageSourceLocation) { + self.context = context + self.webPage = webPage + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.theme = instantPageThemeForType(.light, settings: .defaultSettings) + self.sourceLocation = sourceLocation + + self.scrollNode = ASScrollNode() + self.scrollNode.backgroundColor = self.theme.pageBackgroundColor + + self.scrollNodeFooter = ASDisplayNode() + self.scrollNodeFooter.backgroundColor = self.theme.panelBackgroundColor + + super.init() + + self.addSubnode(self.scrollNode) + self.scrollNode.addSubnode(self.scrollNodeFooter) + + self.scrollNode.view.delaysContentTouches = false + self.scrollNode.view.delegate = self + + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollNode.view.contentInsetAdjustmentBehavior = .never + } + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + recognizer.delaysTouchesBegan = false + recognizer.tapActionAtPoint = { [weak self] point in + if let strongSelf = self { + return strongSelf.tapActionAtPoint(point) + } + return .waitForSingleTap + } + recognizer.highlight = { [weak self] point in + if let strongSelf = self { + strongSelf.updateTouchesAtPoint(point) + } + } + self.scrollNode.view.addGestureRecognizer(recognizer) + } + + deinit { + self.updateLayoutDisposable.dispose() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction { + if let currentLayout = self.currentLayout { + for item in currentLayout.items { + let frame = self.effectiveFrameForItem(item) + if frame.contains(point) { + if item is InstantPagePeerReferenceItem { + return .fail + } else if item is InstantPageAudioItem { + return .fail + } else if item is InstantPageArticleItem { + return .fail + } else if item is InstantPageFeedbackItem { + return .fail + } else if let item = item as? InstantPageDetailsItem { + for (_, itemNode) in self.visibleItemsWithNodes { + if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item { + return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY)) + } + } + } + if !(item is InstantPageImageItem || item is InstantPagePlayableVideoItem) { + break + } + } + } + } + return .waitForSingleTap + } + + private func updateTouchesAtPoint(_ location: CGPoint?) { + var rects: [CGRect]? + if let location = location, let currentLayout = self.currentLayout { + for item in currentLayout.items { + let itemFrame = self.effectiveFrameForItem(item) + if itemFrame.contains(location) { + var contentOffset = CGPoint() + if let item = item as? InstantPageScrollableItem { + contentOffset = self.scrollableContentOffset(item: item) + } + var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) + + for i in 0 ..< itemRects.count { + itemRects[i] = itemRects[i].offsetBy(dx: itemFrame.minX - contentOffset.x, dy: itemFrame.minY).insetBy(dx: -2.0, dy: -2.0) + } + if !itemRects.isEmpty { + rects = itemRects + break + } + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + let highlightColor = self.theme.linkHighlightColor + linkHighlightingNode = LinkHighlightingNode(color: highlightColor) + linkHighlightingNode.isUserInteractionEnabled = false + self.linkHighlightingNode = linkHighlightingNode + self.scrollNode.addSubnode(linkHighlightingNode) + } + linkHighlightingNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + + func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + self.containerLayout = (size, insets) + + var scrollInsets = insets + scrollInsets.top = 0.0 + if self.scrollNode.view.contentInset != insets { + self.scrollNode.view.contentInset = scrollInsets + self.scrollNode.view.scrollIndicatorInsets = scrollInsets + } + self.scrollNode.frame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top)) + + if self.currentLayout?.contentSize.width != size.width { + self.updatePageLayout() + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + } + + private func updatePageLayout() { + guard let (size, insets) = self.containerLayout else { + return + } + + let currentLayout = instantPageLayoutForWebPage(webPage, userLocation: self.sourceLocation.userLocation, boundingWidth: size.width, safeInset: insets.left, strings: self.presentationData.strings, theme: self.theme, dateTimeFormat: self.presentationData.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) + + for (_, tileNode) in self.visibleTiles { + tileNode.removeFromSupernode() + } + self.visibleTiles.removeAll() + + let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: size.width) + + var currentDetailsItems: [InstantPageDetailsItem] = [] + var currentLayoutItemsWithNodes: [InstantPageItem] = [] + var distanceThresholdGroupCount: [Int : Int] = [:] + + var expandedDetails: [Int : Bool] = [:] + + var detailsIndex = -1 + for item in currentLayout.items { + if item.wantsNode { + currentLayoutItemsWithNodes.append(item) + if let group = item.distanceThresholdGroup() { + let count: Int + if let currentCount = distanceThresholdGroupCount[Int(group)] { + count = currentCount + } else { + count = 0 + } + distanceThresholdGroupCount[Int(group)] = count + 1 + } + if let detailsItem = item as? InstantPageDetailsItem { + detailsIndex += 1 + expandedDetails[detailsIndex] = detailsItem.initiallyExpanded + currentDetailsItems.append(detailsItem) + } + } + } + + if var currentExpandedDetails = self.currentExpandedDetails { + for (index, expanded) in expandedDetails { + if currentExpandedDetails[index] == nil { + currentExpandedDetails[index] = expanded + } + } + self.currentExpandedDetails = currentExpandedDetails + } else { + self.currentExpandedDetails = expandedDetails + } + + let accessibilityAreas = instantPageAccessibilityAreasFromLayout(currentLayout, boundingWidth: size.width) + + self.currentLayout = currentLayout + self.currentLayoutTiles = currentLayoutTiles + self.currentLayoutItemsWithNodes = currentLayoutItemsWithNodes + self.currentDetailsItems = currentDetailsItems + self.distanceThresholdGroupCount = distanceThresholdGroupCount + + for areaNode in self.currentAccessibilityAreas { + areaNode.removeFromSupernode() + } + for areaNode in accessibilityAreas { + self.scrollNode.addSubnode(areaNode) + } + self.currentAccessibilityAreas = accessibilityAreas + + self.scrollNode.view.contentSize = currentLayout.contentSize + self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: size.width, height: 2000.0)) + } + + func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) { + var visibleTileIndices = Set() + var visibleItemIndices = Set() + + var topNode: ASDisplayNode? + let topTileNode = topNode + if let scrollSubnodes = self.scrollNode.subnodes { + for node in scrollSubnodes.reversed() { + if let node = node as? InstantPageTileNode { + topNode = node + break + } + } + } + + var collapseOffset: CGFloat = 0.0 + let transition: ContainedViewLayoutTransition + if animated { + transition = .animated(duration: 0.3, curve: .spring) + } else { + transition = .immediate + } + + var itemIndex = -1 + var embedIndex = -1 + var detailsIndex = -1 + + var previousDetailsNode: InstantPageDetailsNode? + + for item in self.currentLayoutItemsWithNodes { + itemIndex += 1 + if item is InstantPageWebEmbedItem { + embedIndex += 1 + } + if let imageItem = item as? InstantPageImageItem, imageItem.media.media._asMedia() is TelegramMediaWebpage { + embedIndex += 1 + } + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + + var itemThreshold: CGFloat = 0.0 + if let group = item.distanceThresholdGroup() { + var count: Int = 0 + if let currentCount = self.distanceThresholdGroupCount[group] { + count = currentCount + } + itemThreshold = item.distanceThresholdWithGroupCount(count) + } + + var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset) + var thresholdedItemFrame = itemFrame + thresholdedItemFrame.origin.y -= itemThreshold + thresholdedItemFrame.size.height += itemThreshold * 2.0 + + if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] { + let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight + collapseOffset += itemFrame.height - height + itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height)) + } + + if visibleBounds.intersects(thresholdedItemFrame) { + visibleItemIndices.insert(itemIndex) + + var itemNode = self.visibleItemsWithNodes[itemIndex] + if let currentItemNode = itemNode { + if !item.matchesNode(currentItemNode) { + currentItemNode.removeFromSupernode() + self.visibleItemsWithNodes.removeValue(forKey: itemIndex) + itemNode = nil + } + } + + if itemNode == nil { + let itemIndex = itemIndex + let embedIndex = embedIndex + let detailsIndex = detailsIndex + if let newNode = item.node(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, theme: self.theme, sourceLocation: self.sourceLocation, openMedia: { [weak self] media in + self?.openMedia(media) + }, longPressMedia: { [weak self] media in + self?.longPressMedia(media) + }, activatePinchPreview: { [weak self] sourceNode in + let _ = self // guard let strongSelf = self, let controller = strongSelf.controller else { // return // } @@ -317,7 +368,8 @@ // return strongSelf.view.convert(localRect, to: nil) // }) // controller.window?.presentInGlobalOverlay(pinchController) -// }, pinchPreviewFinished: { [weak self] itemNode in + }, pinchPreviewFinished: { [weak self] itemNode in + let _ = self // guard let strongSelf = self else { // return // } @@ -329,328 +381,360 @@ // } // } // } -// }, openPeer: { [weak self] peerId in -// self?.openPeer(peerId) -// }, openUrl: { [weak self] url in -// self?.openUrl(url) -// }, updateWebEmbedHeight: { [weak self] height in -// self?.updateWebEmbedHeight(embedIndex, height) -// }, updateDetailsExpanded: { [weak self] expanded in -// self?.updateDetailsExpanded(detailsIndex, expanded) -// }, currentExpandedDetails: self.currentExpandedDetails) { -// newNode.frame = itemFrame -// newNode.updateLayout(size: itemFrame.size, transition: transition) -// if let topNode = topNode { -// self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode) -// } else { -// self.scrollNode.insertSubnode(newNode, at: 0) -// } -// topNode = newNode -// self.visibleItemsWithNodes[itemIndex] = newNode -// itemNode = newNode -// -// if let itemNode = itemNode as? InstantPageDetailsNode { -// itemNode.requestLayoutUpdate = { [weak self] animated in -// if let strongSelf = self { -// strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: animated) -// } + }, openPeer: { [weak self] peerId in + self?.openPeer(peerId) + }, openUrl: { [weak self] url in + self?.openUrl(url) + }, updateWebEmbedHeight: { [weak self] height in + self?.updateWebEmbedHeight(embedIndex, height) + }, updateDetailsExpanded: { [weak self] expanded in + self?.updateDetailsExpanded(detailsIndex, expanded) + }, currentExpandedDetails: self.currentExpandedDetails) { + newNode.frame = itemFrame + newNode.updateLayout(size: itemFrame.size, transition: transition) + if let topNode = topNode { + self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode) + } else { + self.scrollNode.insertSubnode(newNode, at: 0) + } + topNode = newNode + self.visibleItemsWithNodes[itemIndex] = newNode + itemNode = newNode + + if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.requestLayoutUpdate = { [weak self] animated in + if let strongSelf = self { + strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: animated) + } + } + + if let previousDetailsNode = previousDetailsNode { + if itemNode.frame.minY - previousDetailsNode.frame.maxY < 1.0 { + itemNode.previousNode = previousDetailsNode + } + } + previousDetailsNode = itemNode + } + } + } else { + if let itemNode = itemNode, itemNode.frame != itemFrame { + transition.updateFrame(node: itemNode, frame: itemFrame) + itemNode.updateLayout(size: itemFrame.size, transition: transition) + } + } + + if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated) + } + } + } + + topNode = topTileNode + + var tileIndex = -1 + for tile in self.currentLayoutTiles { + tileIndex += 1 + + let tileFrame = effectiveFrameForTile(tile) + var tileVisibleFrame = tileFrame + tileVisibleFrame.origin.y -= 400.0 + tileVisibleFrame.size.height += 400.0 * 2.0 + if tileVisibleFrame.intersects(visibleBounds) { + visibleTileIndices.insert(tileIndex) + + if self.visibleTiles[tileIndex] == nil { + let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor) + tileNode.frame = tileFrame + 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 { + transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame) + } + } + } + } + + if let currentLayout = self.currentLayout { + 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) { + removeTileIndices.append(index) + tileNode.removeFromSupernode() + } + } + for index in removeTileIndices { + self.visibleTiles.removeValue(forKey: index) + } + + var removeItemIndices: [Int] = [] + for (index, itemNode) in self.visibleItemsWithNodes { + if !visibleItemIndices.contains(index) { + removeItemIndices.append(index) + itemNode.removeFromSupernode() + } else { + var itemFrame = itemNode.frame + let itemThreshold: CGFloat = 200.0 + itemFrame.origin.y -= itemThreshold + itemFrame.size.height += itemThreshold * 2.0 + itemNode.updateIsVisible(visibleBounds.intersects(itemFrame)) + } + } + for index in removeItemIndices { + self.visibleItemsWithNodes.removeValue(forKey: index) + } + } + + private struct ScrollingOffsetState: Equatable { + var value: CGFloat + var isDraggingOrDecelerating: Bool + } + + private var previousScrollingOffset: ScrollingOffsetState? + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + self.updateScrollingOffset(isReset: false, transition: .immediate) + } + + private func snapScrollingOffsetToInsets() { + let transition = ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)) + self.updateScrollingOffset(isReset: false, transition: transition) + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.snapScrollingOffsetToInsets() + } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.snapScrollingOffsetToInsets() + } + + private func updateScrollingOffset(isReset: Bool, transition: ComponentTransition) { + let scrollView = self.scrollNode.view + let isInteracting = scrollView.isDragging || scrollView.isDecelerating + if let previousScrollingOffsetValue = self.previousScrollingOffset { + let currentBounds = scrollView.bounds + let offsetToTopEdge = max(0.0, currentBounds.minY - 0.0) + let offsetToBottomEdge = max(0.0, scrollView.contentSize.height - currentBounds.maxY) + + let relativeOffset = scrollView.contentOffset.y - previousScrollingOffsetValue.value + self.onScrollingUpdate(ContentScrollingUpdate( + relativeOffset: relativeOffset, + absoluteOffsetToTopEdge: offsetToTopEdge, + absoluteOffsetToBottomEdge: offsetToBottomEdge, + isReset: isReset, + isInteracting: isInteracting, + transition: transition + )) + } + self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: isInteracting) + } + + private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint { + var contentOffset = CGPoint() + for (_, itemNode) in self.visibleItemsWithNodes { + if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item { + contentOffset = itemNode.contentOffset + break + } + } + return contentOffset + } + + private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? { + for (_, itemNode) in self.visibleItemsWithNodes { + if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item { + return detailsNode + } + } + return nil + } + + private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize { + if let node = nodeForDetailsItem(item) { + return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight) + } else { + return item.frame.size + } + } + + private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect { + let layoutOrigin = tile.frame.origin + var origin = layoutOrigin + for item in self.currentDetailsItems { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + if layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + return CGRect(origin: origin, size: tile.frame.size) + } + + 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 layoutOrigin.y >= item.frame.maxY { + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + origin.y += height - item.frame.height + } + } + + if let item = item as? InstantPageDetailsItem { + let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded + let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight + return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height)) + } 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 { + let itemFrame = self.effectiveFrameForItem(item) + if itemFrame.contains(location) { + if let item = item as? InstantPageTextItem, item.selectable { + return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY)) + } else if let item = item as? InstantPageScrollableItem { + let contentOffset = scrollableContentOffset(item: item) + if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) { + return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y)) + } + } else if let item = item as? InstantPageDetailsItem { + for (_, itemNode) in self.visibleItemsWithNodes { + if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item { + if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) { + return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y)) + } + } + } + } + } + } + } + return nil + } + + private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? { + if let (item, parentOffset) = self.textItemAtLocation(location) { + return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX - parentOffset.x, dy: -item.frame.minY - parentOffset.y)) + } + return nil + } + + private func longPressMedia(_ media: InstantPageMedia) { + let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + if let self, let image = media.media._asMedia() as? TelegramMediaImage { + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let _ = copyToPasteboard(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + } + }), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_LinkDialogSave, accessibilityLabel: self.presentationData.strings.Conversation_LinkDialogSave), action: { [weak self] in + if let self, let image = media.media._asMedia() as? TelegramMediaImage { + let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let _ = saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + } + }), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in + if let self, let image = media.media._asMedia() as? TelegramMediaImage { + self.present(ShareController(context: self.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(self.webPage), media: image), resource: $0.resource)) }))), nil) + } + })], catchTapsOutside: true) + self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let self { + for (_, itemNode) in self.visibleItemsWithNodes { + if let (node, _, _) = itemNode.transitionNode(media: media) { + return (self.scrollNode, node.convert(node.bounds, to: self.scrollNode), self, self.bounds) + } + } + } + return nil + })) + } + + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + break +// if let url = self.urlForTapLocation(location) { +// self.openUrl(url) // } -// -// if let previousDetailsNode = previousDetailsNode { -// if itemNode.frame.minY - previousDetailsNode.frame.maxY < 1.0 { -// itemNode.previousNode = previousDetailsNode + case .longTap: + break +// if let theme = self.theme, let url = self.urlForTapLocation(location) { +// let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url.url)).count > 1 +// let openText = canOpenIn ? self.strings.Conversation_FileOpenIn : self.strings.Conversation_LinkDialogOpen +// let actionSheet = ActionSheetController(instantPageTheme: theme) +// actionSheet.setItemGroups([ActionSheetItemGroup(items: [ +// ActionSheetTextItem(title: url.url), +// ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in +// actionSheet?.dismissAnimated() +// if let strongSelf = self { +// if canOpenIn { +// strongSelf.openUrlIn(url) +// } else { +// strongSelf.openUrl(url) +// } +// } +// }), +// ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// UIPasteboard.general.string = url.url +// }), +// ActionSheetButtonItem(title: self.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// if let link = URL(string: url.url) { +// let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) +// } +// }) +// ]), ActionSheetItemGroup(items: [ +// ActionSheetButtonItem(title: self.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in +// actionSheet?.dismissAnimated() +// }) +// ])]) +// self.present(actionSheet, nil) +// } else if let (item, parentOffset) = self.textItemAtLocation(location) { +// let textFrame = item.frame +// var itemRects = item.lineRects() +// for i in 0 ..< itemRects.count { +// itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textFrame.minX, dy: parentOffset.y + textFrame.minY).insetBy(dx: -2.0, dy: -2.0) // } +// self.updateTextSelectionRects(itemRects, text: item.plainText()) // } -// previousDetailsNode = itemNode -// } -// } -// } else { -// if let itemNode = itemNode, itemNode.frame != itemFrame { -// transition.updateFrame(node: itemNode, frame: itemFrame) -// itemNode.updateLayout(size: itemFrame.size, transition: transition) -// } -// } -// -// if let itemNode = itemNode as? InstantPageDetailsNode { -// itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated) -// } -// } -// } -// -// topNode = topTileNode -// -// var tileIndex = -1 -// for tile in self.currentLayoutTiles { -// tileIndex += 1 -// -// let tileFrame = effectiveFrameForTile(tile) -// var tileVisibleFrame = tileFrame -// tileVisibleFrame.origin.y -= 400.0 -// tileVisibleFrame.size.height += 400.0 * 2.0 -// if tileVisibleFrame.intersects(visibleBounds) { -// visibleTileIndices.insert(tileIndex) -// -// if self.visibleTiles[tileIndex] == nil { -// let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor) -// tileNode.frame = tileFrame -// 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 { -// transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame) -// } -// } -// } -// } -// -// if let currentLayout = self.currentLayout { -// 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) { -// removeTileIndices.append(index) -// tileNode.removeFromSupernode() -// } -// } -// for index in removeTileIndices { -// self.visibleTiles.removeValue(forKey: index) -// } -// -// var removeItemIndices: [Int] = [] -// for (index, itemNode) in self.visibleItemsWithNodes { -// if !visibleItemIndices.contains(index) { -// removeItemIndices.append(index) -// itemNode.removeFromSupernode() -// } else { -// var itemFrame = itemNode.frame -// let itemThreshold: CGFloat = 200.0 -// itemFrame.origin.y -= itemThreshold -// itemFrame.size.height += itemThreshold * 2.0 -// itemNode.updateIsVisible(visibleBounds.intersects(itemFrame)) -// } -// } -// for index in removeItemIndices { -// self.visibleItemsWithNodes.removeValue(forKey: index) -// } -// } -// -// func scrollViewDidScroll(_ scrollView: UIScrollView) { -// self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) -// self.previousContentOffset = self.scrollNode.view.contentOffset -// } -// -// func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { -// self.isDeceleratingBecauseOfDragging = decelerate -// if !decelerate { -// self.updateNavigationBar(forceState: true) -// } -// } -// -// func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { -// self.isDeceleratingBecauseOfDragging = false -// } -// -// private func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint { -// var contentOffset = CGPoint() -// for (_, itemNode) in self.visibleItemsWithNodes { -// if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item { -// contentOffset = itemNode.contentOffset -// break -// } -// } -// return contentOffset -// } -// -// private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? { -// for (_, itemNode) in self.visibleItemsWithNodes { -// if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item { -// return detailsNode -// } -// } -// return nil -// } -// -// private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize { -// if let node = nodeForDetailsItem(item) { -// return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight) -// } else { -// return item.frame.size -// } -// } -// -// private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect { -// let layoutOrigin = tile.frame.origin -// var origin = layoutOrigin -// for item in self.currentDetailsItems { -// let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded -// if layoutOrigin.y >= item.frame.maxY { -// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight -// origin.y += height - item.frame.height -// } -// } -// return CGRect(origin: origin, size: tile.frame.size) -// } -// -// 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 layoutOrigin.y >= item.frame.maxY { -// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight -// origin.y += height - item.frame.height -// } -// } -// -// if let item = item as? InstantPageDetailsItem { -// let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded -// let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight -// return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height)) -// } 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 { -// let itemFrame = self.effectiveFrameForItem(item) -// if itemFrame.contains(location) { -// if let item = item as? InstantPageTextItem, item.selectable { -// return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY)) -// } else if let item = item as? InstantPageScrollableItem { -// let contentOffset = scrollableContentOffset(item: item) -// if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) { -// return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y)) -// } -// } else if let item = item as? InstantPageDetailsItem { -// for (_, itemNode) in self.visibleItemsWithNodes { -// if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item { -// if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) { -// return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y)) -// } -// } -// } -// } -// } -// } -// } -// return nil -// } -// -// private func urlForTapLocation(_ location: CGPoint) -> InstantPageUrlItem? { -// if let (item, parentOffset) = self.textItemAtLocation(location) { -// return item.urlAttribute(at: location.offsetBy(dx: -item.frame.minX - parentOffset.x, dy: -item.frame.minY - parentOffset.y)) -// } -// return nil -// } -// -// private func longPressMedia(_ media: InstantPageMedia) { -// let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in -// if let strongSelf = self, let image = media.media as? TelegramMediaImage { -// let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) -// let _ = copyToPasteboard(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() -// } -// }), ContextMenuAction(content: .text(title: self.strings.Conversation_LinkDialogSave, accessibilityLabel: self.strings.Conversation_LinkDialogSave), action: { [weak self] in -// if let strongSelf = self, let image = media.media as? TelegramMediaImage { -// let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) -// let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() -// } -// }), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in -// if let strongSelf = self, let webPage = strongSelf.webPage, let image = media.media as? TelegramMediaImage { -// strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil) -// } -// })], catchTapsOutside: true) -// self.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in -// if let strongSelf = self { -// for (_, itemNode) in strongSelf.visibleItemsWithNodes { -// if let (node, _, _) = itemNode.transitionNode(media: media) { -// return (strongSelf.scrollNode, node.convert(node.bounds, to: strongSelf.scrollNode), strongSelf, strongSelf.bounds) -// } -// } -// } -// return nil -// })) -// } -// -// @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { -// switch recognizer.state { -// case .ended: -// if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { -// switch gesture { -// case .tap: -// break -//// if let url = self.urlForTapLocation(location) { -//// self.openUrl(url) -//// } -// case .longTap: -// break -//// if let theme = self.theme, let url = self.urlForTapLocation(location) { -//// let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url.url)).count > 1 -//// let openText = canOpenIn ? self.strings.Conversation_FileOpenIn : self.strings.Conversation_LinkDialogOpen -//// let actionSheet = ActionSheetController(instantPageTheme: theme) -//// actionSheet.setItemGroups([ActionSheetItemGroup(items: [ -//// ActionSheetTextItem(title: url.url), -//// ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in -//// actionSheet?.dismissAnimated() -//// if let strongSelf = self { -//// if canOpenIn { -//// strongSelf.openUrlIn(url) -//// } else { -//// strongSelf.openUrl(url) -//// } -//// } -//// }), -//// ActionSheetButtonItem(title: self.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in -//// actionSheet?.dismissAnimated() -//// UIPasteboard.general.string = url.url -//// }), -//// ActionSheetButtonItem(title: self.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in -//// actionSheet?.dismissAnimated() -//// if let link = URL(string: url.url) { -//// let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) -//// } -//// }) -//// ]), ActionSheetItemGroup(items: [ -//// ActionSheetButtonItem(title: self.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in -//// actionSheet?.dismissAnimated() -//// }) -//// ])]) -//// self.present(actionSheet, nil) -//// } else if let (item, parentOffset) = self.textItemAtLocation(location) { -//// let textFrame = item.frame -//// var itemRects = item.lineRects() -//// for i in 0 ..< itemRects.count { -//// itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textFrame.minX, dy: parentOffset.y + textFrame.minY).insetBy(dx: -2.0, dy: -2.0) -//// } -//// self.updateTextSelectionRects(itemRects, text: item.plainText()) -//// } -// default: -// break -// } -// } -// default: -// break -// } -// } -// -// private func updateTextSelectionRects(_ rects: [CGRect], text: String?) { + default: + break + } + } + default: + break + } + } + + private func updateTextSelectionRects(_ rects: [CGRect], text: String?) { // if let text = text, !rects.isEmpty { // let textSelectionNode: LinkHighlightingNode // if let current = self.textSelectionNode { @@ -664,63 +748,63 @@ // textSelectionNode.frame = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) // textSelectionNode.updateRects(rects) // -// var coveringRect = rects[0] -// for i in 1 ..< rects.count { -// coveringRect = coveringRect.union(rects[i]) -// } +//// var coveringRect = rects[0] +//// for i in 1 ..< rects.count { +//// coveringRect = coveringRect.union(rects[i]) +//// } // -// let context = self.context -// let strings = self.strings -// let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) -// |> take(1) -// |> deliverOnMainQueue).start(next: { [weak self] sharedData in -// let translationSettings: TranslationSettings -// if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { -// translationSettings = current -// } else { -// translationSettings = TranslationSettings.defaultSettings -// } -// -// var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in -// UIPasteboard.general.string = text -// -// if let strongSelf = self { -// let presentationData = context.sharedContext.currentPresentationData.with { $0 } -// strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) -// } -// }), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in -// if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content { -// strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil) -// } -// })] -// -// let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) -// if canTranslate { -// actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in -// let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language) -// controller.pushController = { [weak self] c in -// (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true -// self?.controller?.push(c) -// } -// controller.presentController = { [weak self] c in -// self?.controller?.present(c, in: .window(.root)) -// } -// self?.present(controller, nil) -// })) -// } -// -// let controller = makeContextMenuController(actions: actions) -// controller.dismissed = { [weak self] in -// self?.updateTextSelectionRects([], text: nil) -// } -// self?.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in -// if let strongSelf = self { -// return (strongSelf.scrollNode, coveringRect.insetBy(dx: -3.0, dy: -3.0), strongSelf, strongSelf.bounds) -// } else { -// return nil -// } -// })) -// }) +//// let context = self.context +//// let strings = self.presentationData.strings +//// let _ = (context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) +//// |> take(1) +//// |> deliverOnMainQueue).start(next: { [weak self] sharedData in +//// let translationSettings: TranslationSettings +//// if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { +//// translationSettings = current +//// } else { +//// translationSettings = TranslationSettings.defaultSettings +//// } +//// +//// var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in +//// UIPasteboard.general.string = text +//// +//// if let strongSelf = self { +//// let presentationData = context.sharedContext.currentPresentationData.with { $0 } +//// strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) +//// } +//// }), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in +//// if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content { +//// strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil) +//// } +//// })] +//// +//// let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) +//// if canTranslate { +//// actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in +//// let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language) +//// controller.pushController = { [weak self] c in +//// (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true +//// self?.controller?.push(c) +//// } +//// controller.presentController = { [weak self] c in +//// self?.controller?.present(c, in: .window(.root)) +//// } +//// self?.present(controller, nil) +//// })) +//// } +//// +//// let controller = makeContextMenuController(actions: actions) +//// controller.dismissed = { [weak self] in +//// self?.updateTextSelectionRects([], text: nil) +//// } +//// self?.present(controller, ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in +//// if let strongSelf = self { +//// return (strongSelf.scrollNode, coveringRect.insetBy(dx: -3.0, dy: -3.0), strongSelf, strongSelf.bounds) +//// } else { +//// return nil +//// } +//// })) +//// }) // // textSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) // } else if let textSelectionNode = self.textSelectionNode { @@ -729,229 +813,228 @@ // textSelectionNode?.removeFromSupernode() // }) // } -// } -// -// private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, Bool, [InstantPageDetailsItem])? { -// for item in items { -// if let item = item as? InstantPageAnchorItem, item.anchor == anchor { -// return (item, -10.0, false, []) -// } else if let item = item as? InstantPageTextItem { -// if let (lineIndex, empty) = item.anchors[anchor] { -// return (item, item.lines[lineIndex].frame.minY - 10.0, !empty, []) -// } -// } -// else if let item = item as? InstantPageTableItem { -// if let (offset, empty) = item.anchors[anchor] { -// return (item, offset - 10.0, !empty, []) -// } -// } -// else if let item = item as? InstantPageDetailsItem { -// if let (foundItem, offset, reference, detailsItems) = self.findAnchorItem(anchor, items: item.items) { -// var detailsItems = detailsItems -// detailsItems.insert(item, at: 0) -// return (foundItem, offset, reference, detailsItems) -// } -// } -// } -// return nil -// } -// -// private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) { -//// guard let theme = self.theme, let webPage = self.webPage else { -//// return -//// } -//// -//// var targetAnchor: InstantPageTextAnchorItem? -//// for (name, (line, _)) in item.anchors { -//// if name == referenceAnchor { -//// let anchors = item.lines[line].anchorItems -//// for anchor in anchors { -//// if anchor.name == referenceAnchor { -//// targetAnchor = anchor -//// break -//// } -//// } -//// } -//// } -//// -//// guard let anchorText = targetAnchor?.anchorText else { -//// return -//// } -//// -//// let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in -//// self?.openUrl(url) -//// }, openUrlIn: { [weak self] url in -//// self?.openUrlIn(url) -//// }, present: { [weak self] c, a in -//// self?.present(c, a) -//// }) -//// self.present(controller, nil) -// } -// -// private func scrollToAnchor(_ anchor: String) { -// guard let items = self.currentLayout?.items else { + } + + private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> (InstantPageItem, CGFloat, Bool, [InstantPageDetailsItem])? { + for item in items { + if let item = item as? InstantPageAnchorItem, item.anchor == anchor { + return (item, -10.0, false, []) + } else if let item = item as? InstantPageTextItem { + if let (lineIndex, empty) = item.anchors[anchor] { + return (item, item.lines[lineIndex].frame.minY - 10.0, !empty, []) + } + } + else if let item = item as? InstantPageTableItem { + if let (offset, empty) = item.anchors[anchor] { + return (item, offset - 10.0, !empty, []) + } + } + else if let item = item as? InstantPageDetailsItem { + if let (foundItem, offset, reference, detailsItems) = self.findAnchorItem(anchor, items: item.items) { + var detailsItems = detailsItems + detailsItems.insert(item, at: 0) + return (foundItem, offset, reference, detailsItems) + } + } + } + return nil + } + + private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) { +// guard let theme = self.theme, let webPage = self.webPage else { // return // } // -// if !anchor.isEmpty { -// if let (item, lineOffset, reference, detailsItems) = findAnchorItem(String(anchor), items: items) { -// if let item = item as? InstantPageTextItem, reference { -// self.presentReferenceView(item: item, referenceAnchor: anchor) -// } else { -// var previousDetailsNode: InstantPageDetailsNode? -// var containerOffset: CGFloat = 0.0 -// for detailsItem in detailsItems { -// if let previousNode = previousDetailsNode { -// previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false) -// let frame = previousNode.effectiveFrameForItem(detailsItem) -// containerOffset += frame.minY -// -// previousDetailsNode = previousNode.contentNode.nodeForDetailsItem(detailsItem) -// previousDetailsNode?.setExpanded(true, animated: false) -// } else { -// self.updateDetailsExpanded(detailsItem.index, true, animated: false) -// let frame = self.effectiveFrameForItem(detailsItem) -// containerOffset += frame.minY -// -// previousDetailsNode = self.nodeForDetailsItem(detailsItem) -// previousDetailsNode?.setExpanded(true, animated: false) -// } +// var targetAnchor: InstantPageTextAnchorItem? +// for (name, (line, _)) in item.anchors { +// if name == referenceAnchor { +// let anchors = item.lines[line].anchorItems +// for anchor in anchors { +// if anchor.name == referenceAnchor { +// targetAnchor = anchor +// break // } -// -// let frame: CGRect -// if let previousDetailsNode = previousDetailsNode { -// frame = previousDetailsNode.effectiveFrameForItem(item) -// } else { -// frame = self.effectiveFrameForItem(item) -// } -// -// var targetY = min(containerOffset + frame.minY + lineOffset, self.scrollNode.view.contentSize.height - self.scrollNode.frame.height) -// if targetY < self.scrollNode.view.contentOffset.y { -// targetY -= self.scrollNode.view.contentInset.top -// } else { -// targetY -= self.containerLayout?.statusBarHeight ?? 20.0 -// } -// self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true) // } -// } else if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, !instantPage.isComplete { +// } +// } +// +// guard let anchorText = targetAnchor?.anchorText else { +// return +// } +// +// let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in +// self?.openUrl(url) +// }, openUrlIn: { [weak self] url in +// self?.openUrlIn(url) +// }, present: { [weak self] c, a in +// self?.present(c, a) +// }) +// self.present(controller, nil) + } + + private func scrollToAnchor(_ anchor: String) { + guard let items = self.currentLayout?.items else { + return + } + + if !anchor.isEmpty { + if let (item, lineOffset, reference, detailsItems) = findAnchorItem(String(anchor), items: items) { + if let item = item as? InstantPageTextItem, reference { + self.presentReferenceView(item: item, referenceAnchor: anchor) + } else { + var previousDetailsNode: InstantPageDetailsNode? + var containerOffset: CGFloat = 0.0 + for detailsItem in detailsItems { + if let previousNode = previousDetailsNode { + previousNode.contentNode.updateDetailsExpanded(detailsItem.index, true, animated: false) + let frame = previousNode.effectiveFrameForItem(detailsItem) + containerOffset += frame.minY + + previousDetailsNode = previousNode.contentNode.nodeForDetailsItem(detailsItem) + previousDetailsNode?.setExpanded(true, animated: false) + } else { + self.updateDetailsExpanded(detailsItem.index, true, animated: false) + let frame = self.effectiveFrameForItem(detailsItem) + containerOffset += frame.minY + + previousDetailsNode = self.nodeForDetailsItem(detailsItem) + previousDetailsNode?.setExpanded(true, animated: false) + } + } + + let frame: CGRect + if let previousDetailsNode = previousDetailsNode { + frame = previousDetailsNode.effectiveFrameForItem(item) + } else { + frame = self.effectiveFrameForItem(item) + } + + var targetY = min(containerOffset + frame.minY + lineOffset, self.scrollNode.view.contentSize.height - self.scrollNode.frame.height) + if targetY < self.scrollNode.view.contentOffset.y { + targetY -= self.scrollNode.view.contentInset.top + } else { + targetY -= self.containerLayout?.insets.top ?? 20.0 + } + self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true) + } + } else if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage, !instantPage.isComplete { // self.loadProgress.set(0.5) -// self.pendingAnchor = anchor -// } -// } else { -// self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true) -// } -// } -// -// private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { -// let currentHeight = self.currentWebEmbedHeights[index] -// if height != currentHeight { -// if let currentHeight = currentHeight, currentHeight > height { -// return -// } -// self.currentWebEmbedHeights[index] = height -// -// let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) -// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in -// if let strongSelf = self { -// strongSelf.updateLayout() -// strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds) -// } -// })) -// } -// } -// -// private func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true) { -// if var currentExpandedDetails = self.currentExpandedDetails { -// currentExpandedDetails[index] = expanded -// self.currentExpandedDetails = currentExpandedDetails -// } -// self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated) -// } -// -//} -// -//final class BrowserInstantPageContent: UIView, BrowserContent { -// var onScrollingUpdate: (ContentScrollingUpdate) -> Void -// -// func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ComponentFlow.ComponentTransition) { -// -// } -// -// private var _state: BrowserContentState -// private let statePromise: Promise -// -// private let webPage: TelegramMediaWebpage -// private var initialized = false -// -// var state: Signal { -// return self.statePromise.get() -// } -// -// init(context: AccountContext, webPage: TelegramMediaWebpage, url: String) { -// self.webPage = webPage -// -// let presentationData = context.sharedContext.currentPresentationData.with { $0 } -// -// let title: String -// if case let .Loaded(content) = webPage.content { -// title = content.title ?? "" -// } else { -// title = "" -// } -// -// self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, contentType: .instantPage) -// self.statePromise = Promise(self._state) -// -// super.init() -// -// -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// func navigateBack() { -// -// } -// -// func navigateForward() { -// -// } -// -// func setFontSize(_ fontSize: CGFloat) { -// -// } -// -// func setForceSerif(_ force: Bool) { -// -// } -// -// func setSearch(_ query: String?, completion: ((Int) -> Void)?) { -// -// } -// -// func scrollToPreviousSearchResult(completion: ((Int, Int) -> Void)?) { -// -// } -// -// func scrollToNextSearchResult(completion: ((Int, Int) -> Void)?) { -// -// } -// -// func scrollToTop() { -// -// } -// -// func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { -//// let layout = ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: insets.bottom, right: 0.0), safeInsets: UIEdgeInsets(top: 0.0, left: insets.left, bottom: 0.0, right: insets.right), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false) -//// self.instantPageNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition) -//// self.instantPageNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) -//// //transition.updateFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: 56.0), size: CGSize(width: size.width, height: size.height - 56.0))) -//// -//// if !self.initialized { -//// self.initialized = true -//// self.instantPageNode.updateWebPage(self.webPage, anchor: nil) -//// } -// } -//} + self.pendingAnchor = anchor + } + } else { + self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true) + } + } + + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { + let currentHeight = self.currentWebEmbedHeights[index] + if height != currentHeight { + if let currentHeight = currentHeight, currentHeight > height { + return + } + self.currentWebEmbedHeights[index] = height + + let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in + if let self { + self.updatePageLayout() + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + })) + } + } + + private func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true) { + if var currentExpandedDetails = self.currentExpandedDetails { + currentExpandedDetails[index] = expanded + self.currentExpandedDetails = currentExpandedDetails + } + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: animated) + } +} + +final class BrowserInstantPageContent: UIView, BrowserContent { + var onScrollingUpdate: (ContentScrollingUpdate) -> Void = { _ in } + + private var _state: BrowserContentState + private let statePromise: Promise + + private let webPage: TelegramMediaWebpage + private var initialized = false + + private let instantPageNode: InstantPageContainerNode + + var state: Signal { + return self.statePromise.get() + } + + init(context: AccountContext, webPage: TelegramMediaWebpage, url: String, sourceLocation: InstantPageSourceLocation) { + self.webPage = webPage + + let title: String + if case let .Loaded(content) = webPage.content { + title = content.title ?? "" + } else { + title = "" + } + + self._state = BrowserContentState(title: title, url: url, estimatedProgress: 0.0, contentType: .instantPage) + self.statePromise = Promise(self._state) + + self.instantPageNode = InstantPageContainerNode(context: context, webPage: webPage, sourceLocation: sourceLocation) + + super.init(frame: .zero) + + self.addSubnode(self.instantPageNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func reload() { + } + + func stop() { + } + + func navigateBack() { + + } + + func navigateForward() { + + } + + func setFontSize(_ fontSize: CGFloat) { + + } + + func setForceSerif(_ force: Bool) { + + } + + func setSearch(_ query: String?, completion: ((Int) -> Void)?) { + + } + + func scrollToPreviousSearchResult(completion: ((Int, Int) -> Void)?) { + + } + + func scrollToNextSearchResult(completion: ((Int, Int) -> Void)?) { + + } + + func scrollToTop() { + let scrollView = self.instantPageNode.scrollNode.view + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: true) + } + + func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ComponentTransition) { +// let layout = ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: .portrait), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: insets.bottom, right: 0.0), safeInsets: UIEdgeInsets(top: 0.0, left: insets.left, bottom: 0.0, right: insets.right), additionalInsets: .zero, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false) + self.instantPageNode.updateLayout(size: size, insets: insets, transition: transition.containedViewLayoutTransition) + self.instantPageNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) + //transition.updateFrame(view: self.webView, frame: CGRect(origin: CGPoint(x: 0.0, y: 56.0), size: CGSize(width: size.width, height: size.height - 56.0))) + } +} diff --git a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift index d2fed4a1b5..c930499783 100644 --- a/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserNavigationBarComponent.swift @@ -110,7 +110,7 @@ final class BrowserNavigationBarComponent: CombinedComponent { return { context in var availableWidth = context.availableSize.width - let sideInset: CGFloat = 11.0 + context.component.sideInset + let sideInset: CGFloat = 16.0 + context.component.sideInset let collapsedHeight: CGFloat = 24.0 let expandedHeight = context.component.height @@ -202,8 +202,8 @@ final class BrowserNavigationBarComponent: CombinedComponent { centerLeftInset += item.size.width + 8.0 } - var centerRightInset = sideInset - var rightItemX = context.availableSize.width - sideInset + var centerRightInset = sideInset - 5.0 + var rightItemX = context.availableSize.width - (sideInset - 5.0) for item in rightItemList.reversed() { context.add(item .position(CGPoint(x: rightItemX - item.size.width / 2.0 + (item.size.width / 2.0 * 0.35 * context.component.collapseFraction), y: context.component.topInset + contentHeight / 2.0)) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index b13b49c03e..c98dc9da0b 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import SwiftSignalKit import Display +import TelegramCore import TelegramPresentationData import ComponentFlow import ViewControllerComponent @@ -14,6 +15,7 @@ import TelegramUIPreferences import OpenInExternalAppUI import MultilineTextComponent import MinimizedContainer +import InstantPageUI private let settingsTag = GenericComponentViewTag() @@ -72,8 +74,8 @@ private final class BrowserScreenComponent: CombinedComponent { let performAction = context.component.performAction let navigationContent: AnyComponentWithIdentity? - let navigationLeftItems: [AnyComponentWithIdentity] - let navigationRightItems: [AnyComponentWithIdentity] + var navigationLeftItems: [AnyComponentWithIdentity] + var navigationRightItems: [AnyComponentWithIdentity] if context.component.presentationState.isSearching { navigationContent = AnyComponentWithIdentity( id: "search", @@ -113,23 +115,6 @@ private final class BrowserScreenComponent: CombinedComponent { let isLoading = (context.component.contentState?.estimatedProgress ?? 1.0) < 1.0 navigationRightItems = [ - AnyComponentWithIdentity( - id: isLoading ? "stop" : "reload", - component: AnyComponent( - ReferenceButtonComponent( - content: AnyComponent( - BundleIconComponent( - name: isLoading ? "Instant View/CloseIcon" : "Chat/Context Menu/Reload", - tintColor: environment.theme.rootController.navigationBar.primaryTextColor - ) - ), - tag: settingsTag, - action: { - performAction.invoke(isLoading ? .stop : .reload) - } - ) - ) - ), AnyComponentWithIdentity( id: "settings", component: AnyComponent( @@ -148,6 +133,28 @@ private final class BrowserScreenComponent: CombinedComponent { ) ) ] + if case .webPage = context.component.contentState?.contentType { + navigationRightItems.insert( + AnyComponentWithIdentity( + id: isLoading ? "stop" : "reload", + component: AnyComponent( + ReferenceButtonComponent( + content: AnyComponent( + BundleIconComponent( + name: isLoading ? "Instant View/CloseIcon" : "Chat/Context Menu/Reload", + tintColor: environment.theme.rootController.navigationBar.primaryTextColor + ) + ), + tag: settingsTag, + action: { + performAction.invoke(isLoading ? .stop : .reload) + } + ) + ) + ), + at: 0 + ) + } } let collapseFraction = context.component.presentationState.isSearching ? 0.0 : context.component.panelCollapseFraction @@ -303,6 +310,8 @@ public class BrowserScreen: ViewController, MinimizableController { switch controller.subject { case let .webPage(url): content = BrowserWebContent(context: controller.context, url: url) + case let .instantPage(webPage, sourceLocation): + content = BrowserInstantPageContent(context: controller.context, webPage: webPage, url: webPage.content.url ?? "", sourceLocation: sourceLocation) } self.content = content @@ -717,6 +726,7 @@ public class BrowserScreen: ViewController, MinimizableController { public enum Subject { case webPage(url: String) + case instantPage(webPage: TelegramMediaWebpage, sourceLocation: InstantPageSourceLocation) } private let context: AccountContext @@ -754,6 +764,7 @@ public class BrowserScreen: ViewController, MinimizableController { } public var isMinimized = false + public var isMinimizable = true } private final class BrowserReferenceContentSource: ContextReferenceContentSource { diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index 2e69f5f98b..d209814b8d 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -20,6 +20,8 @@ public enum SendMessageActionSheetControllerParams { public let attachment: Bool public let canSendWhenOnline: Bool public let forwardMessageIds: [EngineMessage.Id] + public let canMakePaidContent: Bool + public let currentPrice: Int64? public init( isScheduledMessages: Bool, @@ -28,7 +30,9 @@ public enum SendMessageActionSheetControllerParams { messageEffect: (ChatSendMessageActionSheetControllerSendParameters.Effect?, (ChatSendMessageActionSheetControllerSendParameters.Effect?) -> Void)?, attachment: Bool, canSendWhenOnline: Bool, - forwardMessageIds: [EngineMessage.Id] + forwardMessageIds: [EngineMessage.Id], + canMakePaidContent: Bool, + currentPrice: Int64? ) { self.isScheduledMessages = isScheduledMessages self.mediaPreview = mediaPreview @@ -37,6 +41,8 @@ public enum SendMessageActionSheetControllerParams { self.attachment = attachment self.canSendWhenOnline = canSendWhenOnline self.forwardMessageIds = forwardMessageIds + self.canMakePaidContent = canMakePaidContent + self.currentPrice = currentPrice } } @@ -71,6 +77,7 @@ public func makeChatSendMessageActionSheetController( completion: @escaping () -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + editPrice: @escaping (Int64) -> Void, openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]? = nil, availableMessageEffects: AvailableMessageEffects? = nil, @@ -91,6 +98,7 @@ public func makeChatSendMessageActionSheetController( completion: completion, sendMessage: sendMessage, schedule: schedule, + editPrice: editPrice, openPremiumPaywall: openPremiumPaywall, reactionItems: reactionItems, availableMessageEffects: availableMessageEffects, diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 145cbda01f..a470303e9d 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -67,6 +67,7 @@ final class ChatSendMessageContextScreenComponent: Component { let completion: () -> Void let sendMessage: (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void + let editPrice: (Int64) -> Void let openPremiumPaywall: (ViewController) -> Void let reactionItems: [ReactionItem]? let availableMessageEffects: AvailableMessageEffects? @@ -87,6 +88,7 @@ final class ChatSendMessageContextScreenComponent: Component { completion: @escaping () -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + editPrice: @escaping (Int64) -> Void, openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]?, availableMessageEffects: AvailableMessageEffects?, @@ -106,6 +108,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.completion = completion self.sendMessage = sendMessage self.schedule = schedule + self.editPrice = editPrice self.openPremiumPaywall = openPremiumPaywall self.reactionItems = reactionItems self.availableMessageEffects = availableMessageEffects @@ -437,6 +440,8 @@ final class ChatSendMessageContextScreenComponent: Component { var reminders = false var isSecret = false var canSchedule = false + var canMakePaidContent = false + var currentPrice: Int64? switch component.params { case let .sendMessage(sendMessage): if let peerId = component.peerId { @@ -447,6 +452,8 @@ final class ChatSendMessageContextScreenComponent: Component { if sendMessage.isScheduledMessages { canSchedule = false } + canMakePaidContent = sendMessage.canMakePaidContent + currentPrice = sendMessage.currentPrice case .editMessage: break } @@ -566,6 +573,38 @@ final class ChatSendMessageContextScreenComponent: Component { } ))) } + if canMakePaidContent { + let title: String + let titleLayout: ContextMenuActionItemTextLayout + if let currentPrice { + title = environment.strings.Attachment_Paid_EditPrice + titleLayout = .secondLineWithValue(environment.strings.Attachment_Paid_EditPrice_Stars(Int32(currentPrice))) + } else { + title = environment.strings.Attachment_Paid_Create + titleLayout = .twoLinesMax + } + items.append(.action(ContextMenuActionItem( + id: AnyHashable("paid"), + text: title, + textLayout: titleLayout, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, _ in + guard let self, let component = self.component, case let .sendMessage(params) = component.params else { + return + } + + let editPrice = component.editPrice + let controller = component.context.sharedContext.makeStarsAmountScreen(context: component.context, initialValue: params.currentPrice, completion: { amount in + editPrice(amount) + }) + self.environment?.controller()?.dismiss() + Queue.mainQueue().after(0.45) { + component.openPremiumPaywall(controller) + } + } + ))) + } case .editMessage: items.append(.action(ContextMenuActionItem( id: AnyHashable("silent"), @@ -1385,6 +1424,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha completion: @escaping () -> Void, sendMessage: @escaping (ChatSendMessageActionSheetController.SendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, + editPrice: @escaping (Int64) -> Void, openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]?, availableMessageEffects: AvailableMessageEffects?, @@ -1409,6 +1449,7 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha completion: completion, sendMessage: sendMessage, schedule: schedule, + editPrice: editPrice, openPremiumPaywall: openPremiumPaywall, reactionItems: reactionItems, availableMessageEffects: availableMessageEffects, diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index f948a5069d..91d027f899 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -532,44 +532,7 @@ public final class ComposedPoll { } private final class CreatePollContext: AttachmentMediaPickerContext { - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - var hasCaption: Bool { - return false - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - - public var mainButtonState: Signal { - return .single(nil) - } - - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func mainButtonAction() { - } + } diff --git a/submodules/Display/Source/Navigation/MinimizedContainer.swift b/submodules/Display/Source/Navigation/MinimizedContainer.swift index c079393e1b..d652dc35de 100644 --- a/submodules/Display/Source/Navigation/MinimizedContainer.swift +++ b/submodules/Display/Source/Navigation/MinimizedContainer.swift @@ -24,8 +24,10 @@ public protocol MinimizableController: ViewController { var minimizedTopEdgeOffset: CGFloat? { get } var minimizedBounds: CGRect? { get } var isMinimized: Bool { get set } + var isMinimizable: Bool { get } func makeContentSnapshotView() -> UIView? + func shouldDismissImmediately() -> Bool } public extension MinimizableController { @@ -41,7 +43,15 @@ public extension MinimizableController { return false } + var isMinimizable: Bool { + return false + } + func makeContentSnapshotView() -> UIView? { return self.displayNode.view.snapshotView(afterScreenUpdates: false) } + + func shouldDismissImmediately() -> Bool { + return true + } } diff --git a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift index b2c8810209..cf0c2aafbd 100644 --- a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift @@ -319,7 +319,7 @@ public final class InstantPageContentNode : ASDisplayNode { // } } - func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) { + public func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) { if var currentExpandedDetails = self.currentExpandedDetails { currentExpandedDetails[index] = expanded self.currentExpandedDetails = currentExpandedDetails @@ -353,7 +353,7 @@ public final class InstantPageContentNode : ASDisplayNode { return contentOffset } - func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? { + public func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? { for (_, itemNode) in self.visibleItemsWithNodes { if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item { return detailsNode diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift index 38fff01023..db6a1f7445 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift @@ -26,7 +26,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { private let buttonNode: HighlightableButtonNode private let arrowNode: InstantPageDetailsArrowNode let separatorNode: ASDisplayNode - let contentNode: InstantPageContentNode + public let contentNode: InstantPageContentNode private let updateExpanded: (Bool) -> Void var expanded: Bool @@ -114,7 +114,7 @@ public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { self.updateExpanded(expanded) } - func setExpanded(_ expanded: Bool, animated: Bool) { + public func setExpanded(_ expanded: Bool, animated: Bool) { self.expanded = expanded self.arrowNode.setOpen(expanded, animated: animated) } diff --git a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift index 7d3ac039cf..b4be0ba7ee 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift @@ -20,7 +20,7 @@ public final class InstantPageImageItem: InstantPageItem { let webPage: TelegramMediaWebpage - let media: InstantPageMedia + public let media: InstantPageMedia let attributes: [InstantPageImageAttribute] public var medias: [InstantPageMedia] { diff --git a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift index cf9902a482..201756251b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift @@ -50,11 +50,11 @@ public final class InstantPageScrollableContentNode: ASDisplayNode { } } -final class InstantPageScrollableNode: ASScrollNode, InstantPageNode { - let item: InstantPageScrollableItem +public final class InstantPageScrollableNode: ASScrollNode, InstantPageNode { + public let item: InstantPageScrollableItem let contentNode: InstantPageScrollableContentNode - var contentOffset: CGPoint { + public var contentOffset: CGPoint { return self.view.contentOffset } @@ -90,19 +90,19 @@ final class InstantPageScrollableNode: ASScrollNode, InstantPageNode { } } - func updateIsVisible(_ isVisible: Bool) { + public func updateIsVisible(_ isVisible: Bool) { } - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { } - func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - func updateHiddenMedia(media: InstantPageMedia?) { + public func updateHiddenMedia(media: InstantPageMedia?) { } - func update(strings: PresentationStrings, theme: InstantPageTheme) { + public func update(strings: PresentationStrings, theme: InstantPageTheme) { } } diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index c01951175b..d0865f7fdd 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -96,7 +96,7 @@ public final class InstantPageTextItem: InstantPageItem { let alignment: NSTextAlignment let opaqueBackground: Bool public let medias: [InstantPageMedia] = [] - let anchors: [String: (Int, Bool)] + public let anchors: [String: (Int, Bool)] public let wantsNode: Bool = false public let separatesTiles: Bool = false public var selectable: Bool = true diff --git a/submodules/InstantPageUI/Sources/InstantPageTheme.swift b/submodules/InstantPageUI/Sources/InstantPageTheme.swift index 0a9819a0f4..6f32d2bd28 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTheme.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTheme.swift @@ -84,32 +84,32 @@ public struct InstantPageTextCategories { } public final class InstantPageTheme { - let type: InstantPageThemeType - let pageBackgroundColor: UIColor + public let type: InstantPageThemeType + public let pageBackgroundColor: UIColor - let textCategories: InstantPageTextCategories - let serif: Bool + public let textCategories: InstantPageTextCategories + public let serif: Bool - let codeBlockBackgroundColor: UIColor + public let codeBlockBackgroundColor: UIColor - let linkColor: UIColor - let textHighlightColor: UIColor - let linkHighlightColor: UIColor - let markerColor: UIColor + public let linkColor: UIColor + public let textHighlightColor: UIColor + public let linkHighlightColor: UIColor + public let markerColor: UIColor - let panelBackgroundColor: UIColor - let panelHighlightedBackgroundColor: UIColor - let panelPrimaryColor: UIColor - let panelSecondaryColor: UIColor - let panelAccentColor: UIColor + public let panelBackgroundColor: UIColor + public let panelHighlightedBackgroundColor: UIColor + public let panelPrimaryColor: UIColor + public let panelSecondaryColor: UIColor + public let panelAccentColor: UIColor - let tableBorderColor: UIColor - let tableHeaderColor: UIColor - let controlColor: UIColor + public let tableBorderColor: UIColor + public let tableHeaderColor: UIColor + public let controlColor: UIColor - let imageTintColor: UIColor? + public let imageTintColor: UIColor? - let overlayPanelColor: UIColor + public let overlayPanelColor: UIColor public init(type: InstantPageThemeType, pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?, overlayPanelColor: UIColor) { self.type = type @@ -308,7 +308,7 @@ func instantPageThemeTypeForSettingsAndTime(themeSettings: PresentationThemeSett return (settings.themeType, false) } -func instantPageThemeForType(_ type: InstantPageThemeType, settings: InstantPagePresentationSettings) -> InstantPageTheme { +public func instantPageThemeForType(_ type: InstantPageThemeType, settings: InstantPagePresentationSettings) -> InstantPageTheme { switch type { case .light: return lightTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif) diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 91fbc7b364..5cefd8611b 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -397,44 +397,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab } private final class LocationPickerContext: AttachmentMediaPickerContext { - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - - public var mainButtonState: Signal { - return .single(nil) - } - - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func mainButtonAction() { - } } public func storyLocationPickerController( diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index d6bee1e13f..5e53e62236 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -188,7 +188,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { private let bannedSendPhotos: (Int32, Bool)? private let bannedSendVideos: (Int32, Bool)? private let canBoostToUnrestrict: Bool - private let paidMediaAllowed: Bool + fileprivate let paidMediaAllowed: Bool private let subject: Subject private let saveEditedPhotos: Bool @@ -2207,7 +2207,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { }) } - private var selectionCount: Int32 = 0 + fileprivate var selectionCount: Int32 = 0 fileprivate func updateSelectionState(count: Int32) { self.selectionCount = count @@ -2667,6 +2667,45 @@ final class MediaPickerContext: AttachmentMediaPickerContext { return isForcedCaption } + var canMakePaidContent: Bool { + guard let controller = self.controller else { + return false + } + var isPaidAvailable = false + if controller.paidMediaAllowed && controller.selectionCount <= 10 { + isPaidAvailable = true + } + return isPaidAvailable + } + + var price: Int64? { + guard let controller = self.controller else { + return nil + } + var price: Int64? + if let selectionContext = controller.interaction?.selectionState, let editingContext = controller.interaction?.editingState { + for case let item as TGMediaEditableItem in selectionContext.selectedItems() { + if price == nil, let itemPrice = editingContext.price(for: item) as? Int64 { + price = itemPrice + break + } + } + } + return price + } + + func setPrice(_ price: Int64) { + guard let controller = self.controller else { + return + } + if let selectionContext = controller.interaction?.selectionState, let editingContext = controller.interaction?.editingState { + selectionContext.selectionLimit = 10 + for case let item as TGMediaEditableItem in selectionContext.selectedItems() { + editingContext.setPrice(NSNumber(value: price), for: item) + } + } + } + var captionIsAboveMedia: Signal { return Signal { [weak self] subscriber in guard let interaction = self?.controller?.interaction else { diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 88a0c66abe..35985e5b8e 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1516,24 +1516,30 @@ private func monetizationEntries( starsTransactionsInfo: StarsTransactionsContext.State, adsRestricted: Bool, premiumConfiguration: PremiumConfiguration, - monetizationConfiguration: MonetizationConfiguration + monetizationConfiguration: MonetizationConfiguration, + canViewRevenue: Bool, + canViewStarsRevenue: Bool ) -> [StatsEntry] { var entries: [StatsEntry] = [] entries.append(.adsHeader(presentationData.theme, presentationData.strings.Monetization_Header)) - if !data.topHoursGraph.isEmpty { - entries.append(.adsImpressionsTitle(presentationData.theme, presentationData.strings.Monetization_ImpressionsTitle)) - entries.append(.adsImpressionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .hourlyStep)) + if canViewRevenue { + if !data.topHoursGraph.isEmpty { + entries.append(.adsImpressionsTitle(presentationData.theme, presentationData.strings.Monetization_ImpressionsTitle)) + entries.append(.adsImpressionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .hourlyStep)) + } + + if !data.revenueGraph.isEmpty { + entries.append(.adsTonRevenueTitle(presentationData.theme, presentationData.strings.Monetization_AdRevenueTitle)) + entries.append(.adsTonRevenueGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.revenueGraph, .currency, data.usdRate)) + } } - if !data.revenueGraph.isEmpty { - entries.append(.adsTonRevenueTitle(presentationData.theme, presentationData.strings.Monetization_AdRevenueTitle)) - entries.append(.adsTonRevenueGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.revenueGraph, .currency, data.usdRate)) - } - - if let starsData, !starsData.revenueGraph.isEmpty { - entries.append(.adsStarsRevenueTitle(presentationData.theme, presentationData.strings.Monetization_StarsRevenueTitle)) - entries.append(.adsStarsRevenueGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, starsData.revenueGraph, .stars, starsData.usdRate)) + if canViewStarsRevenue { + if let starsData, !starsData.revenueGraph.isEmpty { + entries.append(.adsStarsRevenueTitle(presentationData.theme, presentationData.strings.Monetization_StarsRevenueTitle)) + entries.append(.adsStarsRevenueGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, starsData.revenueGraph, .stars, starsData.usdRate)) + } } entries.append(.adsProceedsTitle(presentationData.theme, presentationData.strings.Monetization_OverviewTitle)) @@ -1543,40 +1549,45 @@ private func monetizationEntries( if let peer, case let .channel(channel) = peer, channel.flags.contains(.isCreator) { isCreator = true } - entries.append(.adsTonBalanceTitle(presentationData.theme, presentationData.strings.Monetization_TonBalanceTitle)) - entries.append(.adsTonBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, monetizationConfiguration.withdrawalAvailable)) - - if isCreator { - let withdrawalInfoText: String - if data.balances.availableBalance == 0 { - withdrawalInfoText = presentationData.strings.Monetization_Balance_ZeroInfo - } else if monetizationConfiguration.withdrawalAvailable { - withdrawalInfoText = presentationData.strings.Monetization_Balance_AvailableInfo - } else { - withdrawalInfoText = presentationData.strings.Monetization_Balance_ComingLaterInfo + + if canViewRevenue { + entries.append(.adsTonBalanceTitle(presentationData.theme, presentationData.strings.Monetization_TonBalanceTitle)) + entries.append(.adsTonBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, monetizationConfiguration.withdrawalAvailable)) + + if isCreator { + let withdrawalInfoText: String + if data.balances.availableBalance == 0 { + withdrawalInfoText = presentationData.strings.Monetization_Balance_ZeroInfo + } else if monetizationConfiguration.withdrawalAvailable { + withdrawalInfoText = presentationData.strings.Monetization_Balance_AvailableInfo + } else { + withdrawalInfoText = presentationData.strings.Monetization_Balance_ComingLaterInfo + } + entries.append(.adsTonBalanceInfo(presentationData.theme, withdrawalInfoText)) } - entries.append(.adsTonBalanceInfo(presentationData.theme, withdrawalInfoText)) } - if let starsData, starsData.balances.overallRevenue > 0 { - entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle)) - entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > 0, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp)) - entries.append(.adsStarsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_Balance_StarsInfo)) + if canViewStarsRevenue { + if let starsData, starsData.balances.overallRevenue > 0 { + entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle)) + entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > 0, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp)) + entries.append(.adsStarsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_Balance_StarsInfo)) + } } var addedTransactionsTabs = false - if !transactionsInfo.transactions.isEmpty && !starsTransactionsInfo.transactions.isEmpty { + if !transactionsInfo.transactions.isEmpty && !starsTransactionsInfo.transactions.isEmpty && canViewRevenue && canViewStarsRevenue { addedTransactionsTabs = true entries.append(.adsTransactionsTabs(presentationData.theme, presentationData.strings.Monetization_TonTransactions, presentationData.strings.Monetization_StarsTransactions, state.starsSelected)) } var displayTonTransactions = false - if !transactionsInfo.transactions.isEmpty && (starsTransactionsInfo.transactions.isEmpty || !state.starsSelected) { + if canViewRevenue && !transactionsInfo.transactions.isEmpty && (starsTransactionsInfo.transactions.isEmpty || !state.starsSelected) { displayTonTransactions = true } var displayStarsTransactions = false - if !starsTransactionsInfo.transactions.isEmpty && (transactionsInfo.transactions.isEmpty || state.starsSelected) { + if canViewStarsRevenue && !starsTransactionsInfo.transactions.isEmpty && (transactionsInfo.transactions.isEmpty || state.starsSelected) { displayStarsTransactions = true } @@ -1675,7 +1686,9 @@ private func channelStatsControllerEntries( starsTransactions: StarsTransactionsContext.State, adsRestricted: Bool, premiumConfiguration: PremiumConfiguration, - monetizationConfiguration: MonetizationConfiguration + monetizationConfiguration: MonetizationConfiguration, + canViewRevenue: Bool, + canViewStarsRevenue: Bool ) -> [StatsEntry] { switch state.section { case .stats: @@ -1715,7 +1728,9 @@ private func channelStatsControllerEntries( starsTransactionsInfo: starsTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration, - monetizationConfiguration: monetizationConfiguration + monetizationConfiguration: monetizationConfiguration, + canViewRevenue: canViewRevenue, + canViewStarsRevenue: canViewStarsRevenue ) } } @@ -2039,7 +2054,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD ) |> deliverOnMainQueue |> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in - let (adsRestricted, canViewRevenue, _) = peerData + let (adsRestricted, canViewRevenue, canViewStarsRevenue) = peerData var isGroup = false if let peer, case let .channel(channel) = peer, case .group = channel.info { @@ -2124,14 +2139,14 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var tabs: [String] = [] tabs.append(presentationData.strings.Stats_Statistics) tabs.append(presentationData.strings.Stats_Boosts) - if canViewRevenue { + if canViewRevenue || canViewStarsRevenue { tabs.append(presentationData.strings.Stats_Monetization) } title = .textWithTabs(peer?.compactDisplayTitle ?? "", tabs, index) } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(presentationData: presentationData, state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly, revenueState: revenueState?.stats, revenueTransactions: revenueTransactions, starsState: starsState?.stats, starsTransactions: starsTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration, monetizationConfiguration: monetizationConfiguration), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(presentationData: presentationData, state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly, revenueState: revenueState?.stats, revenueTransactions: revenueTransactions, starsState: starsState?.stats, starsTransactions: starsTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration, monetizationConfiguration: monetizationConfiguration, canViewRevenue: canViewRevenue, canViewStarsRevenue: canViewStarsRevenue), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 730934d938..b52bbbe978 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -179,7 +179,17 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { case .text: iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage case let .url(value): - if isTelegramMeLink(value), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: value), case .peer(_, .appStart) = internalUrl { + var isApp = false + if isTelegramMeLink(value), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: value) { + if case .peer(_, .appStart) = internalUrl { + isApp = true + } else if case .peer(_, .attachBotStart) = internalUrl { + isApp = true + } else if case .startAttach = internalUrl { + isApp = true + } + } + if isApp { iconImage = incoming ? graphics.chatBubbleActionButtonIncomingWebAppIconImage : graphics.chatBubbleActionButtonOutgoingWebAppIconImage } else if value.lowercased().contains("?startgroup=") { iconImage = incoming ? graphics.chatBubbleActionButtonIncomingAddToChatIconImage : graphics.chatBubbleActionButtonOutgoingAddToChatIconImage diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift index 83416dd5e7..6dccf1e8e5 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift @@ -697,25 +697,91 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll self.scrollView.addSubnode(itemNode) self.itemNodes[item.id] = itemNode } - itemNode.closeTapped = { [weak self] in + itemNode.closeTapped = { [weak self, weak itemNode] in guard let self else { return } if self.isExpanded { - var needsLayout = true - self.currentTransition = .dismiss(itemId: item.id) - - self.items.removeAll(where: { $0.id == item.id }) - if self.items.count == 1 { - self.isExpanded = false - self.willMaximize?() - needsLayout = false + let proceed = { [weak self] in + guard let self else { + return + } + var needsLayout = true + self.currentTransition = .dismiss(itemId: item.id) + + self.items.removeAll(where: { $0.id == item.id }) + if self.items.count == 1 { + self.isExpanded = false + self.willMaximize?() + needsLayout = false + } + if needsLayout { + self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring)) + } } - if needsLayout { - self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring)) + if let item = itemNode?.item, !item.controller.shouldDismissImmediately() { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: self.presentationData.strings.WebApp_CloseConfirmation), + ActionSheetButtonItem(title: self.presentationData.strings.WebApp_CloseAnyway, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + + proceed() + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.navigationController?.presentOverlay(controller: actionSheet, inGlobal: false, blockInteraction: false) + } else { + proceed() } } else { - self.navigationController?.dismissMinimizedControllers(animated: true) + if self.items.count > 1 { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: self.presentationData.strings.WebApp_Minimized_CloseAllTitle), + ActionSheetButtonItem(title: self.presentationData.strings.WebApp_Minimized_CloseAll(Int32(self.items.count)), color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + self?.navigationController?.dismissMinimizedControllers(animated: true) + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.navigationController?.presentOverlay(controller: actionSheet, inGlobal: false, blockInteraction: false) + } else if let item = self.items.first { + if !item.controller.shouldDismissImmediately() { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: self.presentationData.strings.WebApp_CloseConfirmation), + ActionSheetButtonItem(title: self.presentationData.strings.WebApp_CloseAnyway, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + self?.navigationController?.dismissMinimizedControllers(animated: true) + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.navigationController?.presentOverlay(controller: actionSheet, inGlobal: false, blockInteraction: false) + } else { + self.navigationController?.dismissMinimizedControllers(animated: true) + } + } } } itemNode.tapped = { [weak self, weak itemNode] in diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 43b4f5bff3..3f1c558dd8 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -709,7 +709,9 @@ final class PeerSelectionControllerNode: ASDisplayNode { messageEffect: nil, attachment: false, canSendWhenOnline: false, - forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], + canMakePaidContent: false, + currentPrice: nil )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, @@ -731,6 +733,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { schedule: { [weak textInputPanelNode] messageEffect in textInputPanelNode?.sendMessage(.schedule, messageEffect) }, + editPrice: { _ in }, openPremiumPaywall: { [weak controller] c in guard let controller else { return diff --git a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift index 5c96cc4c14..520c5d79da 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift @@ -26,30 +26,7 @@ public class PremiumGiftAttachmentScreen: PremiumGiftScreen, AttachmentContainab private final class PremiumGiftContext: AttachmentMediaPickerContext { private weak var controller: PremiumGiftScreen? - - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - + public var mainButtonState: Signal { return self.controller?.mainButtonStatePromise.get() ?? .single(nil) } @@ -57,15 +34,6 @@ private final class PremiumGiftContext: AttachmentMediaPickerContext { init(controller: PremiumGiftScreen) { self.controller = controller } - - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } func mainButtonAction() { self.controller?.mainButtonPressed() diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 34f351ace1..bc1c23f0ae 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -355,29 +355,6 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina private final class ThemeColorsGridContext: AttachmentMediaPickerContext { private weak var controller: ThemeColorsGridController? - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - public var mainButtonState: Signal { return .single(self.controller?.mainButtonState) } @@ -386,15 +363,6 @@ private final class ThemeColorsGridContext: AttachmentMediaPickerContext { self.controller = controller } - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - func mainButtonAction() { self.controller?.mainButtonPressed() } diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index 4d0734eea5..3a4d0bdd01 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -164,44 +164,6 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, } private final class AttachmentFileContext: AttachmentMediaPickerContext { - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - - public var mainButtonState: Signal { - return .single(nil) - } - - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func mainButtonAction() { - } } class AttachmentFileControllerImpl: ItemListController, AttachmentFileController, AttachmentContainable { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 579cc0fe4f..4e690f40d6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -78,7 +78,6 @@ public extension ChatControllerImpl { if source == .menu { self.updateChatPresentationInterfaceState(interactive: false) { state in return state.updatedForceInputCommandsHidden(true) -// return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true) } if let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer { @@ -199,7 +198,7 @@ public extension ChatControllerImpl { } })) } else { - self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(self.presentationData.theme), fromMenu: buttonText == "Menu", replyToMessageId: nil, threadId: self.chatLocation.threadId) + self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(self.presentationData.theme), fromMenu: false, replyToMessageId: nil, threadId: self.chatLocation.threadId) |> afterDisposed { updateProgress() }) @@ -208,7 +207,7 @@ public extension ChatControllerImpl { return } let context = strongSelf.context - let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize)) + let params = WebAppParameters(source: .button, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize)) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) }, completion: { [weak self] in diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 6f040f3f40..6c7f3779a9 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -163,6 +163,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no selfController.interfaceInteraction?.editMessage() }, schedule: { _ in + }, + editPrice: { _ in }, openPremiumPaywall: { [weak selfController] c in guard let selfController else { return @@ -231,7 +233,9 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no }), attachment: false, canSendWhenOnline: sendWhenOnlineAvailable, - forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] + forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], + canMakePaidContent: false, + currentPrice: nil )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, @@ -271,6 +275,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no return } selfController.controllerInteraction?.scheduleCurrentMessage(params) + }, editPrice: { _ in }, openPremiumPaywall: { [weak selfController] c in guard let selfController else { return diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index b60d4f8e5b..33c8ec4ec9 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -453,29 +453,6 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { return .single(0) } } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - - public var mainButtonState: Signal { - return .single(nil) - } init(controller: ContactSelectionControllerImpl) { self.controller = controller diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 30f7ee7b64..e63b7b104c 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -24,6 +24,7 @@ import WebsiteType import GalleryData import StoryContainerScreen import WallpaperGalleryScreen +import BrowserUI func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { var story: TelegramMediaStory? @@ -373,7 +374,13 @@ func openChatInstantPageImpl(context: AccountContext, message: Message, sourcePe if let (webpage, anchor) = instantPageAndAnchor(message: message) { let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) - let pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor) + let pageController: ViewController + if !"".isEmpty, context.sharedContext.immediateExperimentalUISettings.browserExperiment { + let _ = anchor + pageController = BrowserScreen(context: context, subject: .instantPage(webPage: webpage, sourceLocation: sourceLocation)) + } else { + pageController = InstantPageController(context: context, webPage: webpage, sourceLocation: sourceLocation, anchor: anchor) + } navigationController.pushViewController(pageController) } } diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 079887e0f8..803e75a929 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -590,25 +590,6 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { } } - public var hasCaption: Bool { - return false - } - - public var captionIsAboveMedia: Signal { - return .single(false) - } - - public func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - - public var loadingProgress: Signal { - return .single(nil) - } - - public var mainButtonState: Signal { - return .single(nil) - } - init(interaction: WebSearchControllerInteraction) { self.interaction = interaction } @@ -624,8 +605,4 @@ public class WebSearchPickerContext: AttachmentMediaPickerContext { public func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { self.interaction?.schedule(parameters) } - - public func mainButtonAction() { - - } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index e30b1bb96b..1c3422b89f 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -184,6 +184,7 @@ public class WebAppCancelButtonNode: ASDisplayNode { public struct WebAppParameters { public enum Source { case generic + case button case menu case attachMenu case inline @@ -1211,6 +1212,10 @@ public final class WebAppController: ViewController, AttachmentContainable { self.openBotSettings() } + case "web_app_setup_swipe_behavior": + if let json = json, let isPanGestureEnabled = json["allow_vertical_swipe"] as? Bool { + self.controller?._isPanGestureEnabled = isPanGestureEnabled + } default: break } @@ -1949,7 +1954,7 @@ public final class WebAppController: ViewController, AttachmentContainable { switch self.source { case .generic, .settings: completion() - case .inline, .attachMenu, .menu, .simple: + case .button, .inline, .attachMenu, .menu, .simple: let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId) ) @@ -2179,10 +2184,28 @@ public final class WebAppController: ViewController, AttachmentContainable { } } - public func shouldDismissImmediately() -> Bool { + public var isMinimizable: Bool { return true } + public func shouldDismissImmediately() -> Bool { + if self.controllerNode.needDismissConfirmation { + return false + } else { + return true + } + } + + fileprivate var _isPanGestureEnabled = true + public var isInnerPanGestureEnabled: (() -> Bool)? { + return { [weak self] in + guard let self else { + return true + } + return self._isPanGestureEnabled + } + } + fileprivate var canMinimize: Bool { return self.controllerNode.canMinimize } @@ -2191,25 +2214,6 @@ public final class WebAppController: ViewController, AttachmentContainable { final class WebAppPickerContext: AttachmentMediaPickerContext { private weak var controller: WebAppController? - var selectionCount: Signal { - return .single(0) - } - - var caption: Signal { - return .single(nil) - } - - var hasCaption: Bool { - return false - } - - var captionIsAboveMedia: Signal { - return .single(false) - } - - func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { - } - public var loadingProgress: Signal { return self.controller?.controllerNode.loadingProgressPromise.get() ?? .single(nil) } @@ -2222,15 +2226,6 @@ final class WebAppPickerContext: AttachmentMediaPickerContext { self.controller = controller } - func setCaption(_ caption: NSAttributedString) { - } - - func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - - func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - } - func mainButtonAction() { self.controller?.controllerNode.mainButtonPressed() }