mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-27 18:42:25 +00:00
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:
parent
386786d19a
commit
3707aface6
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,9 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = []
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user