Added non-mutual peer flood warning

Added server calls configuration
Added server calls P2P privacy setting
Further Instant View improvements
This commit is contained in:
Ilya Laktyushin 2018-11-06 10:41:40 +04:00
parent 386786d19a
commit 3707aface6
46 changed files with 3070 additions and 2482 deletions

View File

@ -217,38 +217,44 @@ public func blockedPeersController(account: Account) -> ViewController {
controller.peerSelected = { [weak controller] peerId in
if let strongController = controller {
strongController.inProgress = true
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> filter { $0 != nil }
let _ = (account.viewTracker.peerView(peerId)
|> take(1)
|> mapToSignal { peers -> Signal<([Peer]?, Peer?), NoError> in
return account.postbox.transaction { transaction -> ([Peer]?, Peer?) in
return (peers, transaction.getPeer(peerId))
}
|> map { view -> Peer? in
return peerViewMainPeer(view)
}
|> deliverOnMainQueue
|> mapToSignal { peers, peer -> Signal<Void, NoError> in
if let peers = peers, let peer = peer {
var updatedPeers = peers
for i in 0 ..< updatedPeers.count {
if updatedPeers[i].id == peerId {
updatedPeers.remove(at: i)
break
|> deliverOnMainQueue).start(next: { peer in
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|> filter { $0 != nil }
|> take(1)
|> map { peers -> ([Peer]?, Peer?) in
return (peers, peer)
}
|> deliverOnMainQueue
|> mapToSignal { peers, peer -> Signal<Void, NoError> in
if let peers = peers, let peer = peer {
var updatedPeers = peers
for i in 0 ..< updatedPeers.count {
if updatedPeers[i].id == peer.id {
updatedPeers.remove(at: i)
break
}
}
updatedPeers.insert(peer, at: 0)
peersPromise.set(.single(updatedPeers))
}
updatedPeers.insert(peer, at: 0)
peersPromise.set(.single(updatedPeers))
return .complete()
}
return .complete()
}
removePeerDisposable.set((requestUpdatePeerIsBlocked(account: account, peerId: peerId, isBlocked: true) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in
}, completed: {
if let strongController = controller {
strongController.inProgress = false
strongController.dismiss()
if let peer = peer {
removePeerDisposable.set((requestUpdatePeerIsBlocked(account: account, peerId: peer.id, isBlocked: true) |> then(applyPeers) |> deliverOnMainQueue).start(completed: {
if let strongController = controller {
strongController.inProgress = false
strongController.dismiss()
}
}))
}
}))
})
}
}
presentControllerImpl?(controller, nil)
@ -272,7 +278,6 @@ public func blockedPeersController(account: Account) -> ViewController {
}
peersPromise.set(.single(updatedPeers))
}
return .complete()
}

View File

@ -426,12 +426,12 @@ final class CallControllerNode: ASDisplayNode {
let point = recognizer.location(in: recognizer.view)
if self.statusNode.frame.contains(point) {
let timestamp = CACurrentMediaTime()
if self.debugTapCounter.0 < timestamp - 0.4 {
if self.debugTapCounter.0 < timestamp - 0.75 {
self.debugTapCounter.0 = timestamp
self.debugTapCounter.1 = 0
}
if self.debugTapCounter.0 >= timestamp - 0.4 {
if self.debugTapCounter.0 >= timestamp - 0.75 {
self.debugTapCounter.0 = timestamp
self.debugTapCounter.1 += 1
}
@ -568,6 +568,8 @@ final private class CallDebugNode: ASDisplayNode {
private let dimNode: ASDisplayNode
private let textNode: ASTextNode
private let timestamp = CACurrentMediaTime()
public var dismiss: (() -> Void)?
init(signal: Signal<(String, String), NoError>) {
@ -607,7 +609,9 @@ final private class CallDebugNode: ASDisplayNode {
}
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
self.dismiss?()
if CACurrentMediaTime() - self.timestamp > 1.0 {
self.dismiss?()
}
}
override func layout() {

View File

@ -109,6 +109,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
private let messageContextDisposable = MetaDisposable()
private let controllerNavigationDisposable = MetaDisposable()
private let sentMessageEventsDisposable = MetaDisposable()
private let failedMessageEventsDisposable = MetaDisposable()
private let messageActionCallbackDisposable = MetaDisposable()
private let editMessageDisposable = MetaDisposable()
private let enqueueMediaMessageDisposable = MetaDisposable()
@ -738,50 +739,41 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
if let strongSelf = self {
strongSelf.commitPurposefulAction()
func getUserPeer(postbox: Postbox, peerId: PeerId) -> Signal<Peer?, NoError> {
return postbox.transaction { transaction -> Peer? in
guard let peer = transaction.getPeer(peerId) else {
return nil
}
if let peer = peer as? TelegramSecretChat {
return transaction.getPeer(peer.regularPeerId)
let _ = (account.viewTracker.peerView(peerId)
|> take(1)
|> map { view -> Peer? in
return peerViewMainPeer(view)
}
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
let callResult = account.telegramApplicationContext.callManager?.requestCall(peerId: peer.id, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id {
account.telegramApplicationContext.navigateToCurrentCall?()
} else {
return peer
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let _ = (account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId))
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = account.telegramApplicationContext.callManager?.requestCall(peerId: peer.id, endCurrentIfAny: true)
})]), in: .window(.root))
}
})
}
}
}
let _ = (getUserPeer(postbox: strongSelf.account.postbox, peerId: peerId)
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData, cachedUserData.callsPrivate {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
return
}
let callResult = account.telegramApplicationContext.callManager?.requestCall(peerId: peer.id, endCurrentIfAny: false)
if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult {
if currentPeerId == peer.id {
account.telegramApplicationContext.navigateToCurrentCall?()
} else {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let _ = (account.postbox.transaction { transaction -> (Peer?, Peer?) in
return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId))
} |> deliverOnMainQueue).start(next: { peer, current in
if let peer = peer, let current = current {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
let _ = account.telegramApplicationContext.callManager?.requestCall(peerId: peer.id, endCurrentIfAny: true)
})]), in: .window(.root))
}
})
}
}
})
})
}
}, longTap: { [weak self] action in
if let strongSelf = self {
@ -1438,6 +1430,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
self.messageContextDisposable.dispose()
self.controllerNavigationDisposable.dispose()
self.sentMessageEventsDisposable.dispose()
self.failedMessageEventsDisposable.dispose()
self.messageActionCallbackDisposable.dispose()
self.editMessageDisposable.dispose()
self.enqueueMediaMessageDisposable.dispose()
@ -2049,6 +2042,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}, forwardSelectedMessages: { [weak self] in
if let strongSelf = self {
strongSelf.commitPurposefulAction()
if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
strongSelf.forwardMessages(messageIds: forwardMessageIds)
@ -2056,11 +2050,13 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}, forwardMessages: { [weak self] messages in
if let strongSelf = self, !messages.isEmpty {
strongSelf.commitPurposefulAction()
let forwardMessageIds = messages.map { $0.id }.sorted()
strongSelf.forwardMessages(messageIds: forwardMessageIds)
}
}, shareSelectedMessages: { [weak self] in
if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty {
strongSelf.commitPurposefulAction()
let _ = (strongSelf.account.postbox.transaction { transaction -> [Message] in
var messages: [Message] = []
for id in selectedIds {
@ -2787,6 +2783,18 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}
}))
self.failedMessageEventsDisposable.set((self.account.pendingMessageManager.failedMessageEvents(peerId: peerId)
|> deliverOnMainQueue).start(next: { [weak self] reason in
if let strongSelf = self {
switch reason {
case .flood:
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: nil, text: strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: {
self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, messageId: nil))
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}
}))
case let .group(groupId):
let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.group(groupId), .total(ApplicationSpecificPreferencesKeys.inAppNotificationSettings)])
self.chatUnreadCountDisposable = (self.account.postbox.combinedView(keys: [unreadCountsKey]) |> deliverOnMainQueue).start(next: { [weak self] views in
@ -4493,7 +4501,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
}
private func openPeerMention(_ name: String) {
private func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default) {
let disposable: MetaDisposable
if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable {
disposable = resolvePeerByNameDisposable
@ -4531,7 +4539,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
}
disposable.set((resolveSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in
if let strongSelf = self {
strongSelf.openResolved(.peer(peerId, .default))
strongSelf.openResolved(.peer(peerId, navigation))
}
}))
}
@ -4739,7 +4747,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
guard let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view else {
return nil
}
if let peer = self.presentationInterfaceState.renderedPeer?.peer, peer.smallProfileImage != nil {
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil {
let galleryController = AvatarGalleryController(account: self.account, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
}, synchronousLoad: true)
galleryController.setHintWillBePresentedInPreviewingContext(true)

View File

@ -58,9 +58,26 @@ private let playImage = generateImage(CGSize(width: 15.0, height: 18.0), rotated
private let titleFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)
enum ChatItemGalleryFooterContent {
enum ChatItemGalleryFooterContent: Equatable {
case info
case playback(paused: Bool, seekable: Bool)
static func ==(lhs: ChatItemGalleryFooterContent, rhs: ChatItemGalleryFooterContent) -> Bool {
switch lhs {
case .info:
if case .info = rhs {
return true
} else {
return false
}
case let .playback(lhsPaused, lhsSeekable):
if case let .playback(rhsPaused, rhsSeekable) = rhs, lhsPaused == rhsPaused, lhsSeekable == rhsSeekable {
return true
} else {
return false
}
}
}
}
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
@ -95,7 +112,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
var content: ChatItemGalleryFooterContent = .info {
didSet {
//if self.content != oldValue {
if self.content != oldValue {
switch self.content {
case .info:
self.authorNameNode.isHidden = false
@ -111,7 +128,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.playbackControlButton.isHidden = false
self.playbackControlButton.setImage(paused ? playImage : pauseImage, for: [])
}
//}
}
}
}
@ -332,11 +349,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
let sideInset: CGFloat = 8.0 + leftInset
let topInset: CGFloat = 8.0
let bottomInset: CGFloat = 8.0
let bottomInset: CGFloat = 2.0
panelHeight += 34.0 + topInset + bottomInset
textFrame.origin.y += 34.0 + topInset + bottomInset
scrubberView.frame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: width - sideInset * 2.0, height: 34.0))
scrubberView.frame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textFrame.maxY), size: CGSize(width: width - sideInset * 2.0, height: 34.0))
}
self.textNode.frame = textFrame
@ -364,7 +380,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
}
override func animateIn(fromHeight: CGFloat, transition: ContainedViewLayoutTransition) {
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.size.height - fromHeight))
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
transition.animatePositionAdditive(layer: scrubberView.layer, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
self.textNode.alpha = 1.0
self.dateNode.alpha = 1.0
self.authorNameNode.alpha = 1.0
@ -374,6 +394,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
}
override func animateOut(toHeight: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
transition.updateFrame(view: scrubberView, frame: scrubberView.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
}
transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
self.textNode.alpha = 0.0
self.dateNode.alpha = 0.0

View File

@ -604,7 +604,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)!
}
titleColor = presentationData.theme.theme.chat.bubble.incomingAccentTextColor
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty)
titleHighlightedColor = bubbleColor.fill
} else {
@ -614,9 +613,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme.theme)!
buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)!
}
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty)
titleColor = presentationData.theme.theme.chat.bubble.outgoingAccentTextColor
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty)
titleHighlightedColor = bubbleColor.fill
}
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, actionTitle, titleColor, titleHighlightedColor)

View File

@ -163,7 +163,7 @@ class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecog
strongSelf.currentThumbnailContainerNode = node
if let node = node {
strongSelf.insertSubnode(node, aboveSubnode: strongSelf.footerNode)
if let (navigationHeight, layout) = strongSelf.containerLayout {
if let (navigationHeight, layout) = strongSelf.containerLayout, !strongSelf.areControlsHidden {
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
node.animateIn(fromLeft: fromLeft)

View File

@ -5,6 +5,7 @@ import AsyncDisplayKit
final class InstantPageAnchorItem: InstantPageItem {
let wantsNode: Bool = false
let separatesTiles: Bool = false
let medias: [InstantPageMedia] = []
let anchor: String
@ -22,7 +23,7 @@ final class InstantPageAnchorItem: InstantPageItem {
func drawInTile(context: CGContext) {
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return nil
}

View File

@ -6,27 +6,30 @@ import AsyncDisplayKit
final class InstantPageArticleItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = false
let medias: [InstantPageMedia] = []
let webPage: TelegramMediaWebpage
let title: String
let description: String
let contentItems: [InstantPageItem]
let contentSize: CGSize
let cover: TelegramMediaImage?
let url: String
let webpageId: MediaId
let rtl: Bool
init(frame: CGRect, webPage: TelegramMediaWebpage, title: String, description: String, cover: TelegramMediaImage?, url: String, webpageId: MediaId) {
init(frame: CGRect, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool) {
self.frame = frame
self.webPage = webPage
self.title = title
self.description = description
self.contentItems = contentItems
self.contentSize = contentSize
self.cover = cover
self.url = url
self.webpageId = webpageId
self.rtl = rtl
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageArticleNode(account: account, webPage: self.webPage, strings: strings, theme: theme, title: self.title, description: self.description, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageArticleNode(account: account, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, rtl: self.rtl, openUrl: openUrl)
}
func matchesAnchor(_ anchor: String) -> Bool {
@ -35,7 +38,7 @@ final class InstantPageArticleItem: InstantPageItem {
func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPageArticleNode {
return self.webpageId == node.webpageId
return self === node.item
} else {
return false
}
@ -60,3 +63,30 @@ final class InstantPageArticleItem: InstantPageItem {
return []
}
}
func layoutArticleItem(theme: InstantPageTheme, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: MediaId, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem {
let inset: CGFloat = 17.0
var sideInset = inset
let imageSize = CGSize(width: 65.0, height: 65.0)
if cover != nil {
sideInset += imageSize.width + 10.0
}
var contentItems: [InstantPageItem] = []
let (titleItems, titleSize) = layoutTextItemWithString(title, boundingWidth: boundingWidth - inset - sideInset, offset: CGPoint(x: inset, y: 20.0), maxNumberOfLines: 2)
contentItems.append(contentsOf: titleItems)
let (descriptionItems, descriptionSize) = layoutTextItemWithString(description, boundingWidth: boundingWidth - inset - sideInset, offset: CGPoint(x: inset, y: 20.0 + titleSize.height + 14.0), maxNumberOfLines: 2)
contentItems.append(contentsOf: descriptionItems)
var hasRTL = false
for case let item as InstantPageTextItem in contentItems {
if item.containsRTL {
hasRTL = true
break
}
}
let contentSize = CGSize(width: boundingWidth, height: max(93.0, titleSize.height + descriptionSize.height + 20.0 + 14.0 + 20.0))
return InstantPageArticleItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height)), webPage: webPage, contentItems: contentItems, contentSize: contentSize, cover: cover, url: url, webpageId: webpageId, rtl: rtl || hasRTL)
}

View File

@ -5,25 +5,16 @@ import Postbox
import TelegramCore
import SwiftSignalKit
private func isRtl(string: String) -> Bool {
if string.count == 0 {
return false
}
let code = CFStringTokenizerCopyBestStringLanguage(string as CFString, CFRangeMake(0, string.count)) as String
return Locale.characterDirection(forLanguage: code) == .rightToLeft
}
final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
private let titleNode: ASTextNode
private let descriptionNode: ASTextNode
private var imageNode: TransformImageNode?
let item: InstantPageArticleItem
private let highlightedBackgroundNode: ASDisplayNode
private let buttonNode: HighlightableButtonNode
let title: String
let pageDescription: String
private let contentTile: InstantPageTile
private let contentTileNode: InstantPageTileNode
private var imageNode: TransformImageNode?
let url: String
let webpageId: MediaId
let cover: TelegramMediaImage?
@ -33,13 +24,12 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
private var fetchedDisposable = MetaDisposable()
init(account: Account, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, title: String, description: String, cover: TelegramMediaImage?, url: String, webpageId: MediaId, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.title = title
self.pageDescription = description
init(account: Account, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.item = item
self.url = url
self.webpageId = webpageId
self.cover = cover
self.rtl = isRtl(string: title) || isRtl(string: description)
self.rtl = rtl
self.openUrl = openUrl
self.highlightedBackgroundNode = ASDisplayNode()
@ -48,20 +38,15 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
self.buttonNode = HighlightableButtonNode()
self.titleNode = ASTextNode()
self.titleNode.isLayerBacked = true
self.titleNode.maximumNumberOfLines = 1
self.descriptionNode = ASTextNode()
self.descriptionNode.isLayerBacked = true
self.descriptionNode.maximumNumberOfLines = 2
self.contentTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height))
self.contentTile.items.append(contentsOf: contentItems)
self.contentTileNode = InstantPageTileNode(tile: self.contentTile, backgroundColor: .clear)
super.init()
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.descriptionNode)
self.addSubnode(self.contentTileNode)
if let image = cover {
let imageNode = TransformImageNode()
@ -109,10 +94,9 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
self.contentTileNode.frame = self.bounds
var sideInset: CGFloat = 0.0
if let imageNode = self.imageNode, let image = self.cover, let largest = largestImageRepresentation(image.representations) {
sideInset = imageSize.width + inset
let size = largest.dimensions.aspectFilled(imageSize)
let boundingSize = imageSize
@ -121,26 +105,19 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
apply()
}
let titleSize = self.titleNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height))
let descriptionSize = self.descriptionNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height))
if self.rtl {
if let imageNode = self.imageNode {
imageNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
if let imageNode = self.imageNode {
if self.rtl {
imageNode.frame = CGRect(origin: CGPoint(x: inset, y: 14.0), size: imageSize)
} else {
imageNode.frame = CGRect(origin: CGPoint(x: size.width - inset - imageSize.width, y: 14.0), size: imageSize)
}
self.titleNode.frame = CGRect(origin: CGPoint(x: size.width - titleSize.width - inset, y: 16.0), size: titleSize)
self.descriptionNode.frame = CGRect(origin: CGPoint(x: size.width - descriptionSize.width - inset, y: self.titleNode.frame.maxY + 6.0), size: descriptionSize)
} else {
if let imageNode = self.imageNode {
imageNode.frame = CGRect(origin: CGPoint(x: size.width - inset - imageSize.width, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
}
self.titleNode.frame = CGRect(origin: CGPoint(x: inset, y: 16.0), size: titleSize)
self.descriptionNode.frame = CGRect(origin: CGPoint(x: inset, y: self.titleNode.frame.maxY + 6.0), size: descriptionSize)
}
}
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
@ -148,12 +125,9 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
}
func updateHiddenMedia(media: InstantPageMedia?) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: UIFont(name: "Georgia", size: 17.0), textColor: theme.panelPrimaryColor)
self.descriptionNode.attributedText = NSAttributedString(string: self.pageDescription, font: theme.serif ? UIFont(name: "Georgia", size: 15.0) : Font.regular(15.0), textColor: theme.panelSecondaryColor)
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
}
}

View File

@ -6,6 +6,7 @@ import AsyncDisplayKit
final class InstantPageAudioItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = false
let medias: [InstantPageMedia]
let media: InstantPageMedia
@ -18,7 +19,7 @@ final class InstantPageAudioItem: InstantPageItem {
self.medias = [media]
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageAudioNode(account: account, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia)
}

View File

@ -244,6 +244,9 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode {
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
@objc func buttonPressed() {
if let _ = self.playbackState {
self.account.telegramApplicationContext.mediaManager?.playlistControl(.playback(.togglePlayPause), type: self.playlistType)

View File

@ -176,7 +176,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.update(strings: strings, theme: theme)
}
self.updateVisibleItems()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
self.updateNavigationBar()
self.recursivelyEnsureDisplaySynchronously(true)
@ -184,6 +184,33 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
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))
}
}
}
break
}
}
}
return .waitForSingleTap
}
override func didLoad() {
super.didLoad()
@ -195,25 +222,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
recognizer.delaysTouchesBegan = false
recognizer.tapActionAtPoint = { [weak self] point in
if let strongSelf = self {
if let currentLayout = strongSelf.currentLayout {
for item in currentLayout.items {
let frame = strongSelf.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 item is InstantPageDetailsItem {
return .fail
}
break
}
}
}
return strongSelf.tapActionAtPoint(point)
}
return .waitForSingleTap
}
@ -303,7 +312,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.view.contentOffset = contentOffset
}
if shouldUpdateVisibleItems {
self.updateVisibleItems()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
}
}
@ -327,10 +336,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var expandedDetails: [Int : Bool] = [:]
var itemIndex = -1
var detailsIndex = -1
for item in currentLayout.items {
if item.wantsNode {
itemIndex += 1
currentLayoutItemsWithNodes.append(item)
if let group = item.distanceThresholdGroup() {
let count: Int
@ -341,14 +349,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
distanceThresholdGroupCount[Int(group)] = count + 1
}
if let detailsItem = item as? InstantPageDetailsItem {
expandedDetails[itemIndex] = detailsItem.initiallyExpanded
detailsIndex += 1
expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
currentDetailsItems.append(detailsItem)
}
}
if let item = item as? InstantPageDetailsItem {
currentDetailsItems.append(item)
}
}
if self.currentExpandedDetails == nil {
@ -365,7 +371,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: containerLayout.size.width, height: 2000.0))
}
func updateVisibleItems(animated: Bool = false) {
func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
guard let theme = self.theme else {
return
}
@ -373,8 +379,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var visibleTileIndices = Set<Int>()
var visibleItemIndices = Set<Int>()
let visibleBounds = self.scrollNode.view.bounds
var topNode: ASDisplayNode?
let topTileNode = topNode
if let scrollSubnodes = self.scrollNode.subnodes {
@ -421,9 +425,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
thresholdedItemFrame.origin.y -= itemThreshold
thresholdedItemFrame.size.height += itemThreshold * 2.0
if let expanded = self.currentExpandedDetails?[detailsIndex], !expanded {
collapseOffset += itemFrame.height - 44.0
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: 44.0))
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) {
@ -442,7 +447,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let itemIndex = itemIndex
let embedIndex = embedIndex
let detailsIndex = detailsIndex
if let itemNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
self?.openMedia(media)
}, openPeer: { [weak self] peerId in
self?.openPeer(peerId)
@ -452,23 +457,39 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self?.updateWebEmbedHeight(embedIndex, height)
}, updateDetailsExpanded: { [weak self] expanded in
self?.updateDetailsExpanded(detailsIndex, expanded)
}) {
itemNode.frame = itemFrame
}, currentExpandedDetails: self.currentExpandedDetails) {
newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition)
// if case let .animated(duration, _) = transition {
// newNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.scrollNode.insertSubnode(itemNode, aboveSubnode: topNode)
self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode)
} else {
self.scrollNode.insertSubnode(itemNode, at: 0)
self.scrollNode.insertSubnode(newNode, at: 0)
}
topNode = newNode
self.visibleItemsWithNodes[itemIndex] = newNode
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] in
if let strongSelf = self {
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: true)
}
}
}
topNode = itemNode
self.visibleItemsWithNodes[itemIndex] = itemNode
}
} else {
if (itemNode as! ASDisplayNode).frame != itemFrame {
let previousFrame = (itemNode as! ASDisplayNode).frame
(itemNode as! ASDisplayNode).frame = itemFrame
transition.animateFrame(node: (itemNode as! ASDisplayNode), from: previousFrame)
transition.updateFrame(node: (itemNode as! ASDisplayNode), 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)
}
}
}
@ -478,19 +499,19 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
for tile in self.currentLayoutTiles {
tileIndex += 1
var tileFrame = tile.frame
if tileIndex > 0 {
tileFrame = tileFrame.offsetBy(dx: 0.0, dy: -collapseOffset)
}
let tileFrame = effectiveFrameForTile(tile)
var tileVisibleFrame = tileFrame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) || animated {
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if visibleTiles[tileIndex] == nil {
if self.visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
tileNode.frame = tile.frame
tileNode.frame = tileFrame
// if case let .animated(duration, _) = transition {
// tileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
@ -500,9 +521,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.visibleTiles[tileIndex] = tileNode
} else {
if visibleTiles[tileIndex]!.frame != tileFrame {
let previousFrame = visibleTiles[tileIndex]!.frame
visibleTiles[tileIndex]!.frame = tileFrame
transition.animateFrame(node: visibleTiles[tileIndex]!, from: previousFrame)
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
}
}
}
@ -550,7 +569,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateVisibleItems()
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
self.updateNavigationBar()
self.previousContentOffset = self.scrollNode.view.contentOffset
}
@ -647,16 +666,16 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var rects: [CGRect]?
if let location = location, let currentLayout = self.currentLayout {
for item in currentLayout.items {
if item.frame.contains(location) {
let itemNodeFrame = item.frame
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY))
let itemFrame = effectiveFrameForItem(item)
if itemFrame.contains(location) {
var contentOffset = CGPoint()
if let item = item as? InstantPageTableItem {
contentOffset = tableContentOffset(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: itemNodeFrame.minX - contentOffset.x, dy: itemNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
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
@ -697,21 +716,44 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return contentOffset
}
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
for (_, itemNode) in self.visibleItemsWithNodes {
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
return CGSize(width: item.frame.width, height: detailsNode.effectiveContentSize.height + item.titleHeight)
}
}
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 !expanded && layoutOrigin.y >= item.frame.maxY {
let offset = 44.0 - item.frame.height
origin.y += offset
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
return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: expanded ? item.frame.height : 44.0))
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)
}
@ -720,17 +762,23 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if let currentLayout = self.currentLayout {
for item in currentLayout.items {
let frame = self.effectiveFrameForItem(item)
if frame.contains(location) {
let itemFrame = self.effectiveFrameForItem(item)
if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint())
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageTableItem {
let contentOffset = tableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -item.frame.minX + contentOffset.x, dy: -item.frame.minY)) {
return (textItem, item.frame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
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))
}
}
}
}
}
}
@ -784,10 +832,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
])])
self.present(actionSheet, nil)
} else if let (item, parentOffset) = self.textItemAtLocation(location) {
let textNodeFrame = effectiveFrameForItem(item)
let textFrame = item.frame
var itemRects = item.lineRects()
for i in 0 ..< itemRects.count {
itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textNodeFrame.minX, dy: parentOffset.y + textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
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())
}
@ -845,6 +893,19 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
private func findAnchorItem(_ anchor: String, items: [InstantPageItem]) -> InstantPageAnchorItem? {
for item in items {
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
return item
} else if let item = item as? InstantPageDetailsItem {
if let anchorItem = findAnchorItem(anchor, items: item.items) {
return anchorItem
}
}
}
return nil
}
private func openUrl(_ url: InstantPageUrlItem) {
guard let items = self.currentLayout?.items else {
return
@ -853,11 +914,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let webPage = self.webPage, url.webpageId == webPage.id, let anchorRange = url.url.range(of: "#") {
let anchor = url.url[anchorRange.upperBound...]
if !anchor.isEmpty {
for item in items {
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: item.frame.origin.y - self.scrollNode.view.contentInset.top), animated: true)
return
}
if let anchorItem = findAnchorItem(String(anchor), items: items) {
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: anchorItem.frame.origin.y - self.scrollNode.view.contentInset.top), animated: true)
return
}
}
}
@ -914,6 +973,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}))
}
private func mediasFromItems(_ items: [InstantPageItem]) -> [InstantPageMedia] {
var medias: [InstantPageMedia] = []
for item in items {
if let detailsItem = item as? InstantPageDetailsItem {
medias.append(contentsOf: mediasFromItems(detailsItem.items))
} else {
medias.append(contentsOf: item.medias)
}
}
return medias
}
private func openMedia(_ media: InstantPageMedia) {
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
return
@ -942,11 +1013,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return
}
var medias: [InstantPageMedia] = []
for item in items {
medias.append(contentsOf: item.medias)
}
var medias: [InstantPageMedia] = mediasFromItems(items)
medias = medias.filter {
$0.media is TelegramMediaImage
}
@ -1000,7 +1067,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
if let strongSelf = self {
strongSelf.updateLayout()
strongSelf.updateVisibleItems()
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds)
}
}))
}
@ -1011,7 +1078,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
currentExpandedDetails[index] = expanded
self.currentExpandedDetails = currentExpandedDetails
}
self.updateVisibleItems(animated: true)
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: true)
}
private func presentSettings() {

View File

@ -2,22 +2,26 @@ import Foundation
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
final class InstantPageDetailsItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = true
let medias: [InstantPageMedia] = []
let title: NSAttributedString
let titleItems: [InstantPageItem]
let titleHeight: CGFloat
let items: [InstantPageItem]
let safeInset: CGFloat
let rtl: Bool
var initiallyExpanded: Bool
let initiallyExpanded: Bool
let index: Int
init(frame: CGRect, title: NSAttributedString, items: [InstantPageItem], safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) {
init(frame: CGRect, titleItems: [InstantPageItem], titleHeight: CGFloat, items: [InstantPageItem], safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) {
self.frame = frame
self.title = title
self.titleItems = titleItems
self.titleHeight = titleHeight
self.items = items
self.safeInset = safeInset
self.rtl = rtl
@ -25,8 +29,12 @@ final class InstantPageDetailsItem: InstantPageItem {
self.index = index
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, updateDetailsExpanded: updateDetailsExpanded)
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
var expanded: Bool?
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
expanded = currentlyExpanded
}
return InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, openMedia: openMedia, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded)
}
func matchesAnchor(_ anchor: String) -> Bool {
@ -46,21 +54,48 @@ final class InstantPageDetailsItem: InstantPageItem {
}
func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat {
if count > 3 {
return 1000.0
} else {
return CGFloat.greatestFiniteMagnitude
}
return CGFloat.greatestFiniteMagnitude
}
func drawInTile(context: CGContext) {
}
func linkSelectionRects(at point: CGPoint) -> [CGRect] {
if point.y < self.titleHeight {
for item in self.titleItems {
if item.frame.contains(point) {
let rects = item.linkSelectionRects(at: point.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY))
return rects.map { $0.offsetBy(dx: item.frame.minX, dy: item.frame.minY) }
}
}
} else {
for item in self.items {
if item.frame.contains(point) {
let rects = item.linkSelectionRects(at: point.offsetBy(dx: 0.0, dy: -self.titleHeight))
return rects.map { $0.offsetBy(dx: item.frame.minX, dy: item.frame.minY + self.titleHeight) }
}
}
}
return []
}
}
func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) -> InstantPageDetailsItem {
return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + 44.0), title: title, items: items, safeInset: safeInset, rtl: rtl, initiallyExpanded: initiallyExpanded, index: index)
let detailsInset: CGFloat = 17.0 + safeInset
let titleInset: CGFloat = 22.0
let (titleItems, titleSize) = layoutTextItemWithString(title, boundingWidth: boundingWidth - detailsInset * 2.0 - titleInset, offset: CGPoint(x: detailsInset + titleInset, y: 0.0))
let titleHeight = max(44.0, titleSize.height + 26.0)
var offset: CGFloat?
for var item in titleItems {
var itemOffset = floorToScreenPixels((titleHeight - item.frame.height) / 2.0)
if item is InstantPageTextItem {
offset = itemOffset
} else if let offset = offset {
itemOffset = offset
}
item.frame = item.frame.offsetBy(dx: 0.0, dy: itemOffset)
}
return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + titleHeight), titleItems: titleItems, titleHeight: titleHeight, items: items, safeInset: safeInset, rtl: rtl, initiallyExpanded: initiallyExpanded, index: index)
}

View File

@ -5,7 +5,6 @@ import Postbox
import TelegramCore
import SwiftSignalKit
private let detailsHeaderHeight: CGFloat = 44.0
private let detailsInset: CGFloat = 17.0
private let titleInset: CGFloat = 22.0
@ -14,6 +13,10 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
private let strings: PresentationStrings
private let theme: InstantPageTheme
private let openMedia: (InstantPageMedia) -> Void
private let openPeer: (PeerId) -> Void
private let openUrl: (InstantPageUrlItem) -> Void
var currentLayoutTiles: [InstantPageTile] = []
var currentLayoutItemsWithNodes: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int: Int] = [:]
@ -21,14 +24,26 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
var visibleTiles: [Int: InstantPageTileNode] = [:]
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
var currentWebEmbedHeights: [Int : CGFloat] = [:]
var currentExpandedDetails: [Int : Bool]?
var currentDetailsItems: [InstantPageDetailsItem] = []
var requestLayoutUpdate: (() -> Void)?
var currentLayout: InstantPageLayout
let contentSize: CGSize
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize) {
private var previousVisibleBounds: CGRect?
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
self.account = account
self.strings = strings
self.theme = theme
self.openMedia = openMedia
self.openPeer = openPeer
self.openUrl = openUrl
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
self.contentSize = contentSize
@ -45,9 +60,13 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
var currentDetailsItems: [InstantPageDetailsItem] = []
var currentLayoutItemsWithViews: [InstantPageItem] = []
var distanceThresholdGroupCount: [Int : Int] = [:]
var expandedDetails: [Int : Bool] = [:]
var detailsIndex = -1
for item in self.currentLayout.items {
if item.wantsNode {
currentLayoutItemsWithViews.append(item)
@ -60,21 +79,41 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
}
distanceThresholdGroupCount[Int(group)] = count + 1
}
if let detailsItem = item as? InstantPageDetailsItem {
detailsIndex += 1
expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
currentDetailsItems.append(detailsItem)
}
}
}
if self.currentExpandedDetails == nil {
self.currentExpandedDetails = expandedDetails
}
self.currentLayoutTiles = currentLayoutTiles
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
self.currentDetailsItems = currentDetailsItems
self.distanceThresholdGroupCount = distanceThresholdGroupCount
}
func updateVisibleItems() {
var effectiveContentSize: CGSize {
var contentSize = self.contentSize
for item in self.currentDetailsItems {
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
contentSize.height += -item.frame.height + (expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight)
}
return contentSize
}
func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
var visibleTileIndices = Set<Int>()
var visibleItemIndices = Set<Int>()
let visibleBounds = self.bounds // self.scrollNode.view.bounds
self.previousVisibleBounds = visibleBounds
var topNode: ASDisplayNode?
let topTileNode = topNode
if let scrollSubnodes = self.subnodes {
for node in scrollSubnodes.reversed() {
if let node = node as? InstantPageTileNode {
@ -84,32 +123,27 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
}
}
var tileIndex = -1
for tile in self.currentLayoutTiles {
tileIndex += 1
var tileVisibleFrame = tile.frame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: .clear)
tileNode.frame = tile.frame
if let topNode = topNode {
self.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
}
}
var collapseOffset: CGFloat = 0.0
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .spring)
} else {
transition = .immediate
}
var itemIndex = -1
var embedIndex = -1
var detailsIndex = -1
for item in self.currentLayoutItemsWithNodes {
itemIndex += 1
if item is InstantPageWebEmbedItem {
embedIndex += 1
}
if item is InstantPageDetailsItem {
detailsIndex += 1
}
var itemThreshold: CGFloat = 0.0
if let group = item.distanceThresholdGroup() {
var count: Int = 0
@ -118,10 +152,19 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
}
itemThreshold = item.distanceThresholdWithGroupCount(count)
}
var itemFrame = item.frame
itemFrame.origin.y -= itemThreshold
itemFrame.size.height += itemThreshold * 2.0
if visibleBounds.intersects(itemFrame) {
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]
@ -134,28 +177,82 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
}
if itemNode == nil {
if let itemNode = item.node(account: self.account, strings: self.strings, theme: self.theme, openMedia: { [weak self] media in
//self?.openMedia(media)
let itemIndex = itemIndex
let embedIndex = embedIndex
let detailsIndex = detailsIndex
if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
self?.openMedia(media)
}, openPeer: { [weak self] peerId in
//self?.openPeer(peerId)
self?.openPeer(peerId)
}, openUrl: { [weak self] url in
//self?.openUrl(url)
self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] height in
//self?.updateWebEmbedHeight(key, height)
}, updateDetailsExpanded: { _ in
}) {
itemNode.frame = item.frame
//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 case let .animated(duration, _) = transition {
// newNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.insertSubnode(itemNode, aboveSubnode: topNode)
self.insertSubnode(newNode, aboveSubnode: topNode)
} else {
self.insertSubnode(itemNode, at: 0)
self.insertSubnode(newNode, at: 0)
}
topNode = newNode
self.visibleItemsWithNodes[itemIndex] = newNode
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] in
self?.requestLayoutUpdate?()
}
}
topNode = itemNode
self.visibleItemsWithNodes[itemIndex] = itemNode
}
} else {
if (itemNode as! ASDisplayNode).frame != item.frame {
(itemNode as! ASDisplayNode).frame = item.frame
if (itemNode as! ASDisplayNode).frame != itemFrame {
transition.updateFrame(node: (itemNode as! ASDisplayNode), 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 case let .animated(duration, _) = transition {
// tileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
// }
if let topNode = topNode {
self.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
} else {
if visibleTiles[tileIndex]!.frame != tileFrame {
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
}
}
}
@ -189,6 +286,152 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
self.visibleItemsWithNodes.removeValue(forKey: index)
}
}
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<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
// if let strongSelf = self {
// strongSelf.updateLayout()
// strongSelf.updateVisibleItems()
// }
// }))
// }
}
private func updateDetailsExpanded(_ index: Int, _ expanded: Bool) {
if var currentExpandedDetails = self.currentExpandedDetails {
currentExpandedDetails[index] = expanded
self.currentExpandedDetails = currentExpandedDetails
}
self.requestLayoutUpdate?()
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
for (_, itemNode) in self.visibleItemsWithNodes {
if let transitionNode = itemNode.transitionNode(media: media) {
return transitionNode
}
}
return nil
}
func updateHiddenMedia(media: InstantPageMedia?) {
for (_, itemNode) in self.visibleItemsWithNodes {
itemNode.updateHiddenMedia(media: media)
}
}
private func tableContentOffset(item: InstantPageTableItem) -> CGPoint {
var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes {
if let itemNode = itemNode as? InstantPageTableNode, itemNode.item === item {
contentOffset = itemNode.contentOffset
break
}
}
return contentOffset
}
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
for (_, itemNode) in self.visibleItemsWithNodes {
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
return CGSize(width: item.frame.width, height: detailsNode.effectiveContentSize.height + item.titleHeight)
}
}
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)
}
}
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
for item in self.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? InstantPageTableItem {
let contentOffset = tableContentOffset(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
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
for item in self.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))
}
}
}
break
}
}
return .waitForSingleTap
}
}
final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
@ -204,12 +447,14 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
private let buttonNode: HighlightableButtonNode
private let arrowNode: InstantPageDetailsArrowNode
private let separatorNode: ASDisplayNode
private let contentNode: InstantPageDetailsContentNode
let contentNode: InstantPageDetailsContentNode
private let updateExpanded: (Bool) -> Void
var expanded: Bool
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, updateDetailsExpanded: @escaping (Bool) -> Void) {
var requestLayoutUpdate: (() -> Void)?
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
self.account = account
self.strings = strings
self.theme = theme
@ -225,39 +470,31 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.buttonNode = HighlightableButtonNode()
self.titleTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: frame.width, height: detailsHeaderHeight))
self.titleTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: frame.width, height: item.titleHeight))
self.titleTile.items.append(contentsOf: item.titleItems)
self.titleTileNode = InstantPageTileNode(tile: self.titleTile, backgroundColor: .clear)
let titleItems = layoutTextItemWithString(item.title, boundingWidth: frame.size.width - detailsInset * 2.0 - titleInset, offset: CGPoint(x: detailsInset + titleInset, y: 0.0)).0
var offset: CGFloat?
for var item in titleItems {
var itemOffset = floorToScreenPixels((detailsHeaderHeight - item.frame.height) / 2.0)
if item is InstantPageTextItem {
offset = itemOffset
} else if let offset = offset {
itemOffset = offset
}
item.frame = item.frame.offsetBy(dx: 0.0, dy: itemOffset)
}
self.titleTile.items.append(contentsOf: titleItems)
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: item.initiallyExpanded)
if let expanded = currentlyExpanded {
self.expanded = expanded
} else {
self.expanded = item.initiallyExpanded
}
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
self.separatorNode = ASDisplayNode()
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height))
self.expanded = item.initiallyExpanded
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, openPeer: openPeer, openUrl: openUrl)
super.init()
self.clipsToBounds = true
self.addSubnode(self.contentNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.titleTileNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.contentNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
@ -280,6 +517,10 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
}
}
self.contentNode.requestLayoutUpdate = { [weak self] in
self?.requestLayoutUpdate?()
}
self.update(strings: strings, theme: theme)
}
@ -293,11 +534,10 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.updateExpanded(expanded)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let size = layout.size
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let inset = detailsInset + self.item.safeInset
let lineSize = CGSize(width: frame.width - inset, height: UIScreenPixel)
let lineSize = CGSize(width: self.frame.width - inset, height: UIScreenPixel)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: item.rtl ? 0.0 : inset, y: size.height - lineSize.height), size: lineSize))
}
@ -308,15 +548,10 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
let inset = detailsInset + self.item.safeInset
self.titleTileNode.frame = self.titleTile.frame
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: detailsHeaderHeight + UIScreenPixel))
self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: detailsHeaderHeight))
self.arrowNode.frame = CGRect(x: inset, y: floorToScreenPixels((detailsHeaderHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0)
self.contentNode.frame = CGRect(x: 0.0, y: detailsHeaderHeight, width: size.width, height: self.item.frame.height - detailsHeaderHeight)
let lineSize = CGSize(width: frame.width - inset, height: UIScreenPixel)
self.separatorNode.frame = CGRect(origin: CGPoint(x: item.rtl ? 0.0 : inset, y: size.height - lineSize.height), size: lineSize)
self.contentNode.updateVisibleItems()
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: self.item.titleHeight + UIScreenPixel))
self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: self.item.titleHeight))
self.arrowNode.frame = CGRect(x: inset, y: floorToScreenPixels((self.item.titleHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0)
self.contentNode.frame = CGRect(x: 0.0, y: self.item.titleHeight, width: size.width, height: self.item.frame.height - self.item.titleHeight)
}
func updateIsVisible(_ isVisible: Bool) {
@ -324,11 +559,11 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
return nil
return self.contentNode.transitionNode(media: media)
}
func updateHiddenMedia(media: InstantPageMedia?) {
self.contentNode.updateHiddenMedia(media: media)
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
@ -336,6 +571,41 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
self.separatorNode.backgroundColor = theme.controlColor
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
}
func updateVisibleItems(visibleBounds: CGRect, animated: Bool) {
if self.bounds.height > self.item.titleHeight {
self.contentNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -self.contentNode.frame.minX, dy: -self.contentNode.frame.minY), animated: animated)
}
}
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if self.titleTileNode.frame.contains(location) {
for case let item as InstantPageTextItem in self.item.titleItems {
if item.frame.contains(location) {
return (item, self.titleTileNode.frame.origin)
}
}
}
else if let (textItem, parentOffset) = self.contentNode.textItemAtLocation(location.offsetBy(dx: -self.contentNode.frame.minX, dy: -self.contentNode.frame.minY)) {
return (textItem, self.contentNode.frame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y))
}
return nil
}
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
if self.titleTileNode.frame.contains(point) {
if self.item.linkSelectionRects(at: point).isEmpty {
return .fail
}
} else if self.contentNode.frame.contains(point) {
return self.contentNode.tapActionAtPoint(_: point.offsetBy(dx: -self.contentNode.frame.minX, dy: -self.contentNode.frame.minY))
}
return .waitForSingleTap
}
var effectiveContentSize: CGSize {
return self.contentNode.effectiveContentSize
}
}
private final class InstantPageDetailsArrowNodeParameters: NSObject {

View File

@ -6,6 +6,7 @@ import AsyncDisplayKit
final class InstantPageFeedbackItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = false
let medias: [InstantPageMedia] = []
let webPage: TelegramMediaWebpage
@ -15,7 +16,7 @@ final class InstantPageFeedbackItem: InstantPageItem {
self.webPage = webPage
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageFeedbackNode(account: account, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl)
}

View File

@ -81,7 +81,9 @@ final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode {
}
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
@ -89,7 +91,6 @@ final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode {
}
func updateHiddenMedia(media: InstantPageMedia?) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {

View File

@ -29,6 +29,7 @@ final class InstantPageImageItem: InstantPageItem {
let fit: Bool
let wantsNode: Bool = true
let separatesTiles: Bool = false
init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool) {
self.frame = frame
@ -41,7 +42,7 @@ final class InstantPageImageItem: InstantPageItem {
self.fit = fit
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageImageNode(account: account, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, url: self.url, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, openUrl: openUrl)
}

View File

@ -91,6 +91,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
if self.theme.imageEmptyColor != theme.imageEmptyColor {
self.theme = theme

View File

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

View File

@ -674,25 +674,30 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
let detailsIndex = detailsIndexCounter
detailsIndexCounter += 1
var subDetailsIndex = 0
var previousBlock: InstantPageBlock?
for subBlock in blocks {
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &subDetailsIndex, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock)
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset, y: contentSize.height + spacing))
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing))
subitems.append(contentsOf: blockItems)
contentSize.height += subLayout.contentSize.height + spacing
previousBlock = subBlock
}
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
contentSize.height += closingSpacing
if !blocks.isEmpty {
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
contentSize.height += closingSpacing
}
let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
styleStack.push(.lineSpacingFactor(0.685))
let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, safeInset: safeInset, rtl: rtl, initiallyExpanded: expanded, index: detailsIndex)
return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem])
case let .relatedArticles(title, articles):
var contentSize = CGSize(width: boundingWidth, height: 0.0)
var items: [InstantPageItem] = []
@ -713,7 +718,16 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
cover = media[coverId] as? TelegramMediaImage
}
let item = InstantPageArticleItem(frame: CGRect(origin: CGPoint(x: 0, y: contentSize.height), size: CGSize(width: boundingWidth, height: 93.0)), webPage: webpage, title: article.title ?? "", description: article.description ?? "", cover: cover, url: article.url, webpageId: article.webpageId)
var styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .article, link: false)
let title = attributedStringForRichText(.plain(article.title ?? ""), styleStack: styleStack)
styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .caption, link: false)
let description = attributedStringForRichText(.plain(article.description ?? ""), styleStack: styleStack)
let item = layoutArticleItem(theme: theme, webPage: webpage, title: title, description: description, cover: cover, url: article.url, webpageId: article.webpageId, boundingWidth: boundingWidth, rtl: rtl)
item.frame = item.frame.offsetBy(dx: 0.0, dy: contentSize.height)
contentSize.height += item.frame.height
items.append(item)

View File

@ -4,7 +4,7 @@ import TelegramCore
func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat {
if let upper = upper, let lower = lower {
switch (upper, lower) {
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil):
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil), (.anchor, _), (_, .anchor):
return 0.0
case (.divider, _), (_, .divider):
return 25.0
@ -49,7 +49,7 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) ->
}
} else if let lower = lower {
switch lower {
case .cover, .channelBanner:
case .cover, .channelBanner, .details, .anchor:
return 0.0
default:
return 24.0

View File

@ -1,5 +1,6 @@
import Foundation
import AsyncDisplayKit
import Display
protocol InstantPageNode {
func updateIsVisible(_ isVisible: Bool)
@ -7,4 +8,6 @@ protocol InstantPageNode {
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)?
func updateHiddenMedia(media: InstantPageMedia?)
func update(strings: PresentationStrings, theme: InstantPageTheme)
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
}

View File

@ -6,6 +6,7 @@ import AsyncDisplayKit
final class InstantPagePeerReferenceItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = false
let medias: [InstantPageMedia] = []
let initialPeer: Peer
@ -19,7 +20,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
self.rtl = rtl
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, rtl: self.rtl, openPeer: openPeer)
}

View File

@ -171,6 +171,9 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
private func applyThemeAndStrings(themeUpdated: Bool) {
if let peer = self.peer {
self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: self.theme.panelPrimaryColor)

View File

@ -15,6 +15,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
let interactive: Bool
let wantsNode: Bool = true
let separatesTiles: Bool = false
init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool) {
self.frame = frame
@ -23,7 +24,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
self.interactive = interactive
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPagePlayableVideoNode(account: account, webPage: self.webPage, media: self.media, interactive: self.interactive, openMedia: openMedia)
}

View File

@ -56,6 +56,9 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode {
}
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
}

View File

@ -17,6 +17,7 @@ final class InstantPageShapeItem: InstantPageItem {
let medias: [InstantPageMedia] = []
let wantsNode: Bool = false
let separatesTiles: Bool = false
init(frame: CGRect, shapeFrame: CGRect, shape: InstantPageShape, color: UIColor) {
self.frame = frame
@ -56,7 +57,7 @@ final class InstantPageShapeItem: InstantPageItem {
return false
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return nil
}

View File

@ -7,6 +7,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
var frame: CGRect
let webPage: TelegramMediaWebpage
let wantsNode: Bool = true
let separatesTiles: Bool = false
let medias: [InstantPageMedia]
init(frame: CGRect, webPage: TelegramMediaWebpage, medias: [InstantPageMedia]) {
@ -15,7 +16,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
self.medias = medias
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageSlideshowNode(account: account, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia)
}

View File

@ -426,6 +426,9 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode {
self.pagerNode.internalIsVisible = isVisible
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
}
}

View File

@ -100,6 +100,7 @@ final class InstantPageTableItem: InstantPageItem {
let horizontalInset: CGFloat
let medias: [InstantPageMedia] = []
let wantsNode: Bool = true
let separatesTiles: Bool = false
let theme: InstantPageTheme
@ -166,7 +167,7 @@ final class InstantPageTableItem: InstantPageItem {
return false
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageTableNode(item: self, account: account, strings: strings, theme: theme)
}
@ -227,7 +228,7 @@ final class InstantPageTableContentNode: ASDisplayNode {
for cell in self.item.cells {
for item in cell.additionalItems {
if item.wantsNode {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }) {
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, updateDetailsExpanded: { _ in }, currentExpandedDetails: nil) {
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
self.addSubnode(node)
}
@ -290,7 +291,9 @@ final class InstantPageTableNode: ASScrollNode, InstantPageNode {
}
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
@ -298,7 +301,6 @@ final class InstantPageTableNode: ASScrollNode, InstantPageNode {
}
func updateHiddenMedia(media: InstantPageMedia?) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {

View File

@ -57,6 +57,7 @@ final class InstantPageTextItem: InstantPageItem {
var alignment: NSTextAlignment = .natural
let medias: [InstantPageMedia] = []
let wantsNode: Bool = false
let separatesTiles: Bool = false
var selectable: Bool = true
var containsRTL: Bool {
@ -309,7 +310,7 @@ final class InstantPageTextItem: InstantPageItem {
return false
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return nil
}
@ -427,7 +428,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
}
}
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false) -> ([InstantPageItem], CGSize) {
func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0) -> ([InstantPageItem], CGSize) {
if string.length == 0 {
return ([], CGSize())
}
@ -490,10 +491,24 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
}
}
if lineCharacterCount > 0 {
let line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
let lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
var line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
var stop = false
if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 {
let attributes = string.attributes(at: lastIndex + lineCharacterCount - 1, effectiveRange: nil)
if let truncateString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, attributes as CFDictionary) {
let truncateToken = CTLineCreateWithAttributedString(truncateString)
let tokenWidth = CGFloat(CTLineGetTypographicBounds(truncateToken, nil, nil, nil) + 3.0)
if let truncatedLine = CTLineCreateTruncatedLine(line, Double(lineWidth - tokenWidth), .end, truncateToken) {
lineWidth += tokenWidth
line = truncatedLine
}
}
stop = true
}
var strikethroughItems: [InstantPageTextStrikethroughItem] = []
var markedItems: [InstantPageTextMarkedItem] = []
@ -563,8 +578,12 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
lastIndex += lineCharacterCount
if stop {
break
}
} else {
break;
break
}
}

View File

@ -39,6 +39,7 @@ enum InstantPageTextCategoryType {
case paragraph
case caption
case table
case article
}
struct InstantPageTextCategories {
@ -48,6 +49,7 @@ struct InstantPageTextCategories {
let paragraph: InstantPageTextAttributes
let caption: InstantPageTextAttributes
let table: InstantPageTextAttributes
let article: InstantPageTextAttributes
func attributes(type: InstantPageTextCategoryType, link: Bool) -> InstantPageTextAttributes {
switch type {
@ -63,11 +65,13 @@ struct InstantPageTextCategories {
return self.caption.withUnderline(link)
case .table:
return self.table.withUnderline(link)
case .article:
return self.article.withUnderline(link)
}
}
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextCategories {
return InstantPageTextCategories(kicker: self.kicker.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif))
return InstantPageTextCategories(kicker: self.kicker.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), article: self.article.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif))
}
}
@ -128,7 +132,8 @@ private let lightTheme = InstantPageTheme(
subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: .black),
paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: .black),
caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)),
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: .black)
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: .black),
article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: .black)
),
serif: false,
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
@ -154,7 +159,8 @@ private let sepiaTheme = InstantPageTheme(
subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0x4f321d)),
paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)),
caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)),
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d))
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)),
article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d))
),
serif: false,
codeBlockBackgroundColor: UIColor(rgb: 0xefe7d6),
@ -180,7 +186,8 @@ private let grayTheme = InstantPageTheme(
subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xcecece)),
paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)),
caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)),
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece))
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)),
article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece))
),
serif: false,
codeBlockBackgroundColor: UIColor(rgb: 0x555556),
@ -206,7 +213,8 @@ private let darkTheme = InstantPageTheme(
subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: UIColor(rgb: 0xb0b0b0)),
paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)),
caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)),
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0))
table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)),
article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 17.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0))
),
serif: false,
codeBlockBackgroundColor: UIColor(rgb: 0x131313),

View File

@ -14,16 +14,15 @@ final class InstantPageTile {
for item in self.items {
item.drawInTile(context: context)
}
// context.setFillColor(UIColor.red.cgColor)
// context.fill(CGRect(x: 0.0, y: self.frame.maxY - 1.0, width: self.frame.width, height: 2.0))
context.translateBy(x: self.frame.minX, y: self.frame.minY)
}
}
func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFloat) -> [InstantPageTile] {
var tileByOrigin: [Int: InstantPageTile] = [:]
var tileByOrigin: [Int : InstantPageTile] = [:]
let tileHeight: CGFloat = 256.0
var tileHoles: [CGRect] = []
for item in layout.items {
if !item.wantsNode {
let topTileIndex = max(0, Int(floor(item.frame.minY - 10.0) / tileHeight))
@ -38,10 +37,45 @@ func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFl
}
tile.items.append(item)
}
} else if item.separatesTiles {
tileHoles.append(item.frame)
}
}
return tileByOrigin.values.sorted(by: { lhs, rhs in
var finalTiles: [InstantPageTile] = []
var usedTiles = Set<Int>()
for hole in tileHoles {
let topTileIndex = max(0, Int(floor(hole.minY - 10.0) / tileHeight))
let bottomTileIndex = max(topTileIndex, Int(floor(hole.maxY + 10.0) / tileHeight))
for i in topTileIndex ... bottomTileIndex {
if let tile = tileByOrigin[i] {
if tile.frame.minY > hole.minY && tile.frame.minY < hole.maxY {
let delta = hole.maxY - tile.frame.minY
let updatedTile = InstantPageTile(frame: CGRect(origin: tile.frame.origin.offsetBy(dx: 0.0, dy: delta), size: CGSize(width: tile.frame.width, height: tile.frame.height - delta)))
updatedTile.items.append(contentsOf: tile.items)
finalTiles.append(updatedTile)
usedTiles.insert(i)
} else if tile.frame.maxY > hole.minY && tile.frame.minY < hole.minY {
let delta = tile.frame.maxY - hole.minY
let updatedTile = InstantPageTile(frame: CGRect(origin: tile.frame.origin, size: CGSize(width: tile.frame.width, height: tile.frame.height - delta)))
updatedTile.items.append(contentsOf: tile.items)
finalTiles.append(updatedTile)
usedTiles.insert(i)
}
}
}
//let holeTile = InstantPageTile(frame: hole)
//finalTiles.append(holeTile)
}
for (index, tile) in tileByOrigin {
if !usedTiles.contains(index) {
finalTiles.append(tile)
}
}
return finalTiles.sorted(by: { lhs, rhs in
return lhs.frame.minY < rhs.frame.minY
})
}

View File

@ -6,6 +6,7 @@ import AsyncDisplayKit
final class InstantPageWebEmbedItem: InstantPageItem {
var frame: CGRect
let wantsNode: Bool = true
let separatesTiles: Bool = true
let medias: [InstantPageMedia] = []
let url: String?
@ -19,7 +20,7 @@ final class InstantPageWebEmbedItem: InstantPageItem {
self.enableScrolling = enableScrolling
}
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void) -> (InstantPageNode & ASDisplayNode)? {
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> (InstantPageNode & ASDisplayNode)? {
return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight)
}

View File

@ -2,6 +2,7 @@ import Foundation
import TelegramCore
import WebKit
import AsyncDisplayKit
import Display
private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler {
private let f: (WKScriptMessage) -> ()
@ -119,6 +120,9 @@ final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode {
func updateIsVisible(_ isVisible: Bool) {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
}
func update(strings: PresentationStrings, theme: InstantPageTheme) {
}
}

View File

@ -181,7 +181,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
let peerRevealOptions: [ItemListRevealOption]
if item.editable && item.enabled {
peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_TerminateSession, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]
peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_Terminate, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]
} else {
peerRevealOptions = []
}

View File

@ -125,8 +125,9 @@ final class OngoingCallContext {
private let audioSessionDisposable = MetaDisposable()
private var networkTypeDisposable: Disposable?
init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?) {
let _ = setupLogs
OngoingCallThreadLocalContext.applyServerConfig(serializedData)
self.internalId = internalId
self.callSessionManager = callSessionManager

View File

@ -50,6 +50,7 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) {
@interface OngoingCallThreadLocalContext : NSObject
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
+ (void)applyServerConfig:(NSString * _Nullable)data;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState);
@property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile);

View File

@ -1,6 +1,7 @@
#import "OngoingCallThreadLocalContext.h"
#import "../../libtgvoip/VoIPController.h"
#import "../../libtgvoip/VoIPServerConfig.h"
#import "../../libtgvoip/os/darwin/SetupLogging.h"
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
@ -180,6 +181,36 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
TGVoipLoggingFunction = loggingFunction;
}
+ (void)applyServerConfig:(NSString *)data {
if (data.length == 0) {
return;
}
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
if (dict != nil) {
std::vector<std::string> result;
char **values = (char **)malloc(sizeof(char *) * (int)dict.count * 2);
memset(values, 0, (int)dict.count * 2);
__block int index = 0;
[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, __unused BOOL *stop) {
NSString *valueText = [NSString stringWithFormat:@"%@", value];
const char *keyText = [key UTF8String];
const char *valueTextValue = [valueText UTF8String];
values[index] = (char *)malloc(strlen(keyText) + 1);
values[index][strlen(keyText)] = 0;
memcpy(values[index], keyText, strlen(keyText));
values[index + 1] = (char *)malloc(strlen(valueTextValue) + 1);
values[index + 1][strlen(valueTextValue)] = 0;
memcpy(values[index + 1], valueTextValue, strlen(valueTextValue));
index += 2;
}];
tgvoip::ServerConfig::GetSharedInstance()->Update((const char **)values, index);
for (int i = 0; i < (int)dict.count * 2; i++) {
free(values[i]);
}
free(values);
}
}
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType {
self = [super init];
if (self != nil) {

View File

@ -222,13 +222,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
displayData = value.item.displayData
let baseColor: UIColor
if strongSelf.theme.list.itemPrimaryTextColor.isEqual(strongSelf.theme.list.itemAccentColor) {
baseColor = strongSelf.theme.list.controlSecondaryColor
} else {
baseColor = strongSelf.theme.list.itemPrimaryTextColor
}
let baseColor = strongSelf.theme.list.itemSecondaryTextColor
if value.order != strongSelf.currentOrder {
strongSelf.updateOrder?(value.order)
strongSelf.currentOrder = value.order

View File

@ -218,7 +218,7 @@ public final class PresentationCall {
private var droppedCall = false
private var dropCallKitCallTimer: SwiftSignalKit.Timer?
init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, allowP2P: Bool, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
self.account = account
self.audioSession = audioSession
self.callSessionManager = callSessionManager
@ -230,7 +230,7 @@ public final class PresentationCall {
self.isOutgoing = isOutgoing
self.peer = peer
self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType)
self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType, serializedData: serializedData)
var didReceiveAudioOutputs = false
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)

View File

@ -4,19 +4,6 @@ import TelegramCore
import SwiftSignalKit
import Display
private func p2pAllowed(settings: (VoiceCallSettings, VoipConfiguration)?, isContact: Bool) -> Bool {
var mode: VoiceCallP2PMode? = settings?.0.p2pMode
if mode == nil {
mode = settings?.1.defaultP2PMode
}
switch (mode ?? .contacts, isContact) {
case (.always, _), (.contacts, true):
return true
default:
return false
}
}
private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? {
let enabled = settings?.enableSystemIntegration ?? true
return enabled ? integration : nil
@ -198,6 +185,25 @@ public final class PresentationCallManager {
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
if let strongSelf = self {
strongSelf.callSettings = (callSettings, configuration)
if let legacyP2PMode = callSettings.legacyP2PMode {
_ = updateVoiceCallSettingsSettingsInteractively(postbox: postbox, { settings -> VoiceCallSettings in
var settings = settings
settings.legacyP2PMode = nil
return settings
}).start()
let settings: SelectivePrivacySettings
switch legacyP2PMode {
case .always:
settings = .enableEveryone(disableFor: Set<PeerId>())
case .contacts:
settings = .enableContacts(enableFor: Set<PeerId>(), disableFor: Set<PeerId>())
case .never:
settings = .disableEveryone(enableFor: Set<PeerId>())
}
_ = updateSelectiveAccountPrivacySettings(account: account, type: .voiceCallsP2P, settings: settings).start()
}
}
})
}
@ -213,7 +219,7 @@ public final class PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) {
if let firstState = ringingStates.first {
if self.currentCall == nil {
let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, allowP2P: p2pAllowed(settings: self.callSettings, isContact: firstState.2), proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
let call = PresentationCall(account: self.account, audioSession: self.audioSession, callSessionManager: self.callSessionManager, callKitIntegration: enableCallKit ? self.callKitIntegration : nil, serializedData: self.callSettings?.1.serializedData, getDeviceAccessData: self.getDeviceAccessData, internalId: firstState.1.id, peerId: firstState.1.peerId, isOutgoing: false, peer: firstState.0, proxyServer: self.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: self.networkType)
self.currentCall = call
self.currentCallPromise.set(.single(call))
self.hasActiveCallsPromise.set(true)
@ -312,7 +318,7 @@ public final class PresentationCallManager {
currentCall.rejectBusy()
}
let call = PresentationCall(account: strongSelf.account, audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings?.0), getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, allowP2P: p2pAllowed(settings: strongSelf.callSettings, isContact: isContact), proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType)
let call = PresentationCall(account: strongSelf.account, audioSession: strongSelf.audioSession, callSessionManager: strongSelf.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings?.0), serializedData: strongSelf.callSettings?.1.serializedData, getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: strongSelf.networkType)
strongSelf.currentCall = call
strongSelf.currentCallPromise.set(.single(call))
strongSelf.hasActiveCallsPromise.set(true)

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
case currentSessionInfo(PresentationTheme, String)
case pendingSessionsHeader(PresentationTheme, String)
case pendingSession(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
case pendingSessionsInfo(PresentationTheme, String)
case otherSessionsHeader(PresentationTheme, String)
case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool)
@ -83,7 +84,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
switch self {
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo:
return RecentSessionsSection.currentSession.rawValue
case .pendingSessionsHeader, .pendingSession:
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
return RecentSessionsSection.pendingSessions.rawValue
case .otherSessionsHeader, .session, .website:
return RecentSessionsSection.otherSessions.rawValue
@ -106,8 +107,10 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return .index(5)
case let .pendingSession(_, _, _, _, session, _, _, _):
return .session(session.hash)
case .otherSessionsHeader:
case .pendingSessionsInfo:
return .index(6)
case .otherSessionsHeader:
return .index(7)
case let .session(_, _, _, _, session, _, _, _):
return .session(session.hash)
case let .website(_, _, _, _, website, _, _, _, _):
@ -153,6 +156,12 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
} else {
return false
}
case let .pendingSessionsInfo(lhsTheme, lhsText):
if case let .pendingSessionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .otherSessionsHeader(lhsTheme, lhsText):
if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@ -244,14 +253,16 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .pendingSessionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .otherSessionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .pendingSession(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeSession(id)
})
case let .pendingSessionsInfo(theme, text):
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
case let .otherSessionsHeader(theme, text):
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
case let .session(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
@ -339,14 +350,14 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
let filteredPendingSessions: [RecentAccountSession] = sessions.filter({ $0.flags.contains(.passwordPending) })
if !filteredPendingSessions.isEmpty {
entries.append(.pendingSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_PasswordPending))
entries.append(.pendingSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_IncompleteAttempts))
for i in 0 ..< filteredPendingSessions.count {
if !existingSessionIds.contains(filteredPendingSessions[i].hash) {
existingSessionIds.insert(filteredPendingSessions[i].hash)
entries.append(.pendingSession(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredPendingSessions[i], enabled: state.removingSessionId != filteredPendingSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredPendingSessions[i].hash))
}
}
entries.append(.pendingSessionsInfo(presentationData.theme, presentationData.strings.AuthSessions_IncompleteAttemptsInfo))
}
entries.append(.otherSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_OtherSessions))
@ -425,38 +436,53 @@ public func recentSessionsController(account: Account) -> ViewController {
}
}
}, removeSession: { sessionId in
updateState {
return $0.withUpdatedRemovingSessionId(sessionId)
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationTheme: presentationData.theme)
let dismissAction: () -> Void = { [weak controller] in
controller?.dismissAnimated()
}
let applySessions: Signal<Void, NoError> = sessionsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { sessions -> Signal<Void, NoError> in
if let sessions = sessions {
var updatedSessions = sessions
for i in 0 ..< updatedSessions.count {
if updatedSessions[i].hash == sessionId {
updatedSessions.remove(at: i)
break
}
controller.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
dismissAction()
updateState {
return $0.withUpdatedRemovingSessionId(sessionId)
}
sessionsPromise.set(.single(updatedSessions))
}
return .complete()
}
removeSessionDisposable.set((terminateAccountSession(account: account, hash: sessionId) |> then((applySessions |> mapError { _ in TerminateSessionError.generic })) |> deliverOnMainQueue).start(error: { _ in
updateState {
return $0.withUpdatedRemovingSessionId(nil)
}
}, completed: {
updateState {
return $0.withUpdatedRemovingSessionId(nil)
}
}))
let applySessions: Signal<Void, NoError> = sessionsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue
|> mapToSignal { sessions -> Signal<Void, NoError> in
if let sessions = sessions {
var updatedSessions = sessions
for i in 0 ..< updatedSessions.count {
if updatedSessions[i].hash == sessionId {
updatedSessions.remove(at: i)
break
}
}
sessionsPromise.set(.single(updatedSessions))
}
return .complete()
}
removeSessionDisposable.set((terminateAccountSession(account: account, hash: sessionId) |> then((applySessions |> mapError { _ in TerminateSessionError.generic })) |> deliverOnMainQueue).start(error: { _ in
updateState {
return $0.withUpdatedRemovingSessionId(nil)
}
}, completed: {
updateState {
return $0.withUpdatedRemovingSessionId(nil)
}
}))
})
]),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, terminateOtherSessions: {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationTheme: presentationData.theme)

View File

@ -569,7 +569,7 @@ struct SecureIdDocumentFormState: FormControllerInnerState {
result.append(.entry(SecureIdDocumentFormEntry.expiryDate(document.expiryDate, expiryDateError)))
}
if (self.selfieRequired || self.requestOptionalData) || self.frontSideRequired || self.backSideRequired {
if ((self.selfieRequired || self.requestOptionalData) && identity.document != nil) || self.frontSideRequired || self.backSideRequired {
let type = identity.document?.type
if let last = result.last, case .spacer = last {

View File

@ -11,7 +11,7 @@ public enum VoiceCallDataSaving: Int32 {
public struct VoiceCallSettings: PreferencesEntry, Equatable {
public var dataSaving: VoiceCallDataSaving
public var p2pMode: VoiceCallP2PMode?
public var legacyP2PMode: VoiceCallP2PMode?
public var enableSystemIntegration: Bool
public static var defaultSettings: VoiceCallSettings {
@ -20,23 +20,23 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable {
init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode?, enableSystemIntegration: Bool) {
self.dataSaving = dataSaving
self.p2pMode = p2pMode
self.legacyP2PMode = p2pMode
self.enableSystemIntegration = enableSystemIntegration
}
public init(decoder: PostboxDecoder) {
self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))!
if let value = decoder.decodeOptionalInt32ForKey("p2pMode") {
self.p2pMode = VoiceCallP2PMode(rawValue: value) ?? .contacts
self.legacyP2PMode = VoiceCallP2PMode(rawValue: value)
} else {
self.p2pMode = nil
self.legacyP2PMode = nil
}
self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds")
if let p2pMode = self.p2pMode {
if let p2pMode = self.legacyP2PMode {
encoder.encodeInt32(p2pMode.rawValue, forKey: "p2pMode")
} else {
encoder.encodeNil(forKey: "p2pMode")
@ -56,7 +56,7 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable {
if lhs.dataSaving != rhs.dataSaving {
return false
}
if lhs.p2pMode != rhs.p2pMode {
if lhs.legacyP2PMode != rhs.legacyP2PMode {
return false
}
if lhs.enableSystemIntegration != rhs.enableSystemIntegration {