mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 03:21:29 +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
|
controller.peerSelected = { [weak controller] peerId in
|
||||||
if let strongController = controller {
|
if let strongController = controller {
|
||||||
strongController.inProgress = true
|
strongController.inProgress = true
|
||||||
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|
|
||||||
|> filter { $0 != nil }
|
let _ = (account.viewTracker.peerView(peerId)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { peers -> Signal<([Peer]?, Peer?), NoError> in
|
|> map { view -> Peer? in
|
||||||
return account.postbox.transaction { transaction -> ([Peer]?, Peer?) in
|
return peerViewMainPeer(view)
|
||||||
return (peers, transaction.getPeer(peerId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|> mapToSignal { peers, peer -> Signal<Void, NoError> in
|
let applyPeers: Signal<Void, NoError> = peersPromise.get()
|
||||||
if let peers = peers, let peer = peer {
|
|> filter { $0 != nil }
|
||||||
var updatedPeers = peers
|
|> take(1)
|
||||||
for i in 0 ..< updatedPeers.count {
|
|> map { peers -> ([Peer]?, Peer?) in
|
||||||
if updatedPeers[i].id == peerId {
|
return (peers, peer)
|
||||||
updatedPeers.remove(at: i)
|
}
|
||||||
break
|
|> 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()
|
||||||
}
|
}
|
||||||
|
if let peer = peer {
|
||||||
return .complete()
|
removePeerDisposable.set((requestUpdatePeerIsBlocked(account: account, peerId: peer.id, isBlocked: true) |> then(applyPeers) |> deliverOnMainQueue).start(completed: {
|
||||||
}
|
if let strongController = controller {
|
||||||
removePeerDisposable.set((requestUpdatePeerIsBlocked(account: account, peerId: peerId, isBlocked: true) |> then(applyPeers) |> deliverOnMainQueue).start(error: { _ in
|
strongController.inProgress = false
|
||||||
|
strongController.dismiss()
|
||||||
}, completed: {
|
}
|
||||||
if let strongController = controller {
|
}))
|
||||||
strongController.inProgress = false
|
|
||||||
strongController.dismiss()
|
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
presentControllerImpl?(controller, nil)
|
presentControllerImpl?(controller, nil)
|
||||||
@ -272,7 +278,6 @@ public func blockedPeersController(account: Account) -> ViewController {
|
|||||||
}
|
}
|
||||||
peersPromise.set(.single(updatedPeers))
|
peersPromise.set(.single(updatedPeers))
|
||||||
}
|
}
|
||||||
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -426,12 +426,12 @@ final class CallControllerNode: ASDisplayNode {
|
|||||||
let point = recognizer.location(in: recognizer.view)
|
let point = recognizer.location(in: recognizer.view)
|
||||||
if self.statusNode.frame.contains(point) {
|
if self.statusNode.frame.contains(point) {
|
||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
if self.debugTapCounter.0 < timestamp - 0.4 {
|
if self.debugTapCounter.0 < timestamp - 0.75 {
|
||||||
self.debugTapCounter.0 = timestamp
|
self.debugTapCounter.0 = timestamp
|
||||||
self.debugTapCounter.1 = 0
|
self.debugTapCounter.1 = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.debugTapCounter.0 >= timestamp - 0.4 {
|
if self.debugTapCounter.0 >= timestamp - 0.75 {
|
||||||
self.debugTapCounter.0 = timestamp
|
self.debugTapCounter.0 = timestamp
|
||||||
self.debugTapCounter.1 += 1
|
self.debugTapCounter.1 += 1
|
||||||
}
|
}
|
||||||
@ -568,6 +568,8 @@ final private class CallDebugNode: ASDisplayNode {
|
|||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
private let textNode: ASTextNode
|
private let textNode: ASTextNode
|
||||||
|
|
||||||
|
private let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
public var dismiss: (() -> Void)?
|
public var dismiss: (() -> Void)?
|
||||||
|
|
||||||
init(signal: Signal<(String, String), NoError>) {
|
init(signal: Signal<(String, String), NoError>) {
|
||||||
@ -607,7 +609,9 @@ final private class CallDebugNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
self.dismiss?()
|
if CACurrentMediaTime() - self.timestamp > 1.0 {
|
||||||
|
self.dismiss?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layout() {
|
override func layout() {
|
||||||
|
|||||||
@ -109,6 +109,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
|||||||
private let messageContextDisposable = MetaDisposable()
|
private let messageContextDisposable = MetaDisposable()
|
||||||
private let controllerNavigationDisposable = MetaDisposable()
|
private let controllerNavigationDisposable = MetaDisposable()
|
||||||
private let sentMessageEventsDisposable = MetaDisposable()
|
private let sentMessageEventsDisposable = MetaDisposable()
|
||||||
|
private let failedMessageEventsDisposable = MetaDisposable()
|
||||||
private let messageActionCallbackDisposable = MetaDisposable()
|
private let messageActionCallbackDisposable = MetaDisposable()
|
||||||
private let editMessageDisposable = MetaDisposable()
|
private let editMessageDisposable = MetaDisposable()
|
||||||
private let enqueueMediaMessageDisposable = MetaDisposable()
|
private let enqueueMediaMessageDisposable = MetaDisposable()
|
||||||
@ -738,50 +739,41 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.commitPurposefulAction()
|
strongSelf.commitPurposefulAction()
|
||||||
|
|
||||||
func getUserPeer(postbox: Postbox, peerId: PeerId) -> Signal<Peer?, NoError> {
|
let _ = (account.viewTracker.peerView(peerId)
|
||||||
return postbox.transaction { transaction -> Peer? in
|
|> take(1)
|
||||||
guard let peer = transaction.getPeer(peerId) else {
|
|> map { view -> Peer? in
|
||||||
return nil
|
return peerViewMainPeer(view)
|
||||||
}
|
}
|
||||||
if let peer = peer as? TelegramSecretChat {
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
return transaction.getPeer(peer.regularPeerId)
|
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 {
|
} 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
|
}, longTap: { [weak self] action in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -1438,6 +1430,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
|||||||
self.messageContextDisposable.dispose()
|
self.messageContextDisposable.dispose()
|
||||||
self.controllerNavigationDisposable.dispose()
|
self.controllerNavigationDisposable.dispose()
|
||||||
self.sentMessageEventsDisposable.dispose()
|
self.sentMessageEventsDisposable.dispose()
|
||||||
|
self.failedMessageEventsDisposable.dispose()
|
||||||
self.messageActionCallbackDisposable.dispose()
|
self.messageActionCallbackDisposable.dispose()
|
||||||
self.editMessageDisposable.dispose()
|
self.editMessageDisposable.dispose()
|
||||||
self.enqueueMediaMessageDisposable.dispose()
|
self.enqueueMediaMessageDisposable.dispose()
|
||||||
@ -2049,6 +2042,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
|||||||
}
|
}
|
||||||
}, forwardSelectedMessages: { [weak self] in
|
}, forwardSelectedMessages: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.commitPurposefulAction()
|
||||||
if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
|
if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
|
||||||
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
|
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
|
||||||
strongSelf.forwardMessages(messageIds: forwardMessageIds)
|
strongSelf.forwardMessages(messageIds: forwardMessageIds)
|
||||||
@ -2056,11 +2050,13 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
|
|||||||
}
|
}
|
||||||
}, forwardMessages: { [weak self] messages in
|
}, forwardMessages: { [weak self] messages in
|
||||||
if let strongSelf = self, !messages.isEmpty {
|
if let strongSelf = self, !messages.isEmpty {
|
||||||
|
strongSelf.commitPurposefulAction()
|
||||||
let forwardMessageIds = messages.map { $0.id }.sorted()
|
let forwardMessageIds = messages.map { $0.id }.sorted()
|
||||||
strongSelf.forwardMessages(messageIds: forwardMessageIds)
|
strongSelf.forwardMessages(messageIds: forwardMessageIds)
|
||||||
}
|
}
|
||||||
}, shareSelectedMessages: { [weak self] in
|
}, shareSelectedMessages: { [weak self] in
|
||||||
if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty {
|
if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty {
|
||||||
|
strongSelf.commitPurposefulAction()
|
||||||
let _ = (strongSelf.account.postbox.transaction { transaction -> [Message] in
|
let _ = (strongSelf.account.postbox.transaction { transaction -> [Message] in
|
||||||
var messages: [Message] = []
|
var messages: [Message] = []
|
||||||
for id in selectedIds {
|
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):
|
case let .group(groupId):
|
||||||
let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.group(groupId), .total(ApplicationSpecificPreferencesKeys.inAppNotificationSettings)])
|
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
|
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
|
let disposable: MetaDisposable
|
||||||
if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable {
|
if let resolvePeerByNameDisposable = self.resolvePeerByNameDisposable {
|
||||||
disposable = 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
|
disposable.set((resolveSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peerId in
|
||||||
if let strongSelf = self {
|
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 {
|
guard let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view else {
|
||||||
return nil
|
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
|
let galleryController = AvatarGalleryController(account: self.account, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in
|
||||||
}, synchronousLoad: true)
|
}, synchronousLoad: true)
|
||||||
galleryController.setHintWillBePresentedInPreviewingContext(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 titleFont = Font.medium(15.0)
|
||||||
private let dateFont = Font.regular(14.0)
|
private let dateFont = Font.regular(14.0)
|
||||||
|
|
||||||
enum ChatItemGalleryFooterContent {
|
enum ChatItemGalleryFooterContent: Equatable {
|
||||||
case info
|
case info
|
||||||
case playback(paused: Bool, seekable: Bool)
|
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 {
|
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
||||||
@ -95,7 +112,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
|||||||
|
|
||||||
var content: ChatItemGalleryFooterContent = .info {
|
var content: ChatItemGalleryFooterContent = .info {
|
||||||
didSet {
|
didSet {
|
||||||
//if self.content != oldValue {
|
if self.content != oldValue {
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case .info:
|
case .info:
|
||||||
self.authorNameNode.isHidden = false
|
self.authorNameNode.isHidden = false
|
||||||
@ -111,7 +128,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
|||||||
self.playbackControlButton.isHidden = false
|
self.playbackControlButton.isHidden = false
|
||||||
self.playbackControlButton.setImage(paused ? playImage : pauseImage, for: [])
|
self.playbackControlButton.setImage(paused ? playImage : pauseImage, for: [])
|
||||||
}
|
}
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,11 +349,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
|||||||
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
|
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
|
||||||
let sideInset: CGFloat = 8.0 + leftInset
|
let sideInset: CGFloat = 8.0 + leftInset
|
||||||
let topInset: CGFloat = 8.0
|
let topInset: CGFloat = 8.0
|
||||||
let bottomInset: CGFloat = 8.0
|
let bottomInset: CGFloat = 2.0
|
||||||
panelHeight += 34.0 + topInset + bottomInset
|
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
|
self.textNode.frame = textFrame
|
||||||
@ -364,7 +380,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func animateIn(fromHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
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.textNode.alpha = 1.0
|
||||||
self.dateNode.alpha = 1.0
|
self.dateNode.alpha = 1.0
|
||||||
self.authorNameNode.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) {
|
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))
|
transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
|
||||||
self.textNode.alpha = 0.0
|
self.textNode.alpha = 0.0
|
||||||
self.dateNode.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)!
|
buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantIncoming(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)!
|
||||||
}
|
}
|
||||||
titleColor = presentationData.theme.theme.chat.bubble.incomingAccentTextColor
|
titleColor = presentationData.theme.theme.chat.bubble.incomingAccentTextColor
|
||||||
|
|
||||||
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: true, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
||||||
titleHighlightedColor = bubbleColor.fill
|
titleHighlightedColor = bubbleColor.fill
|
||||||
} else {
|
} else {
|
||||||
@ -614,9 +613,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme.theme)!
|
buttonIconImage = PresentationResourcesChat.chatMessageAttachedContentButtonIconInstantOutgoing(presentationData.theme.theme)!
|
||||||
buttonHighlightedIconImage = PresentationResourcesChat.chatMessageAttachedContentHighlightedButtonIconInstantOutgoing(presentationData.theme.theme, wallpaper: !presentationData.theme.wallpaper.isEmpty)!
|
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
|
titleColor = presentationData.theme.theme.chat.bubble.outgoingAccentTextColor
|
||||||
|
let bubbleColor = bubbleColorComponents(theme: presentationData.theme.theme, incoming: false, wallpaper: !presentationData.theme.wallpaper.isEmpty)
|
||||||
titleHighlightedColor = bubbleColor.fill
|
titleHighlightedColor = bubbleColor.fill
|
||||||
}
|
}
|
||||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, buttonIconImage, buttonHighlightedIconImage, actionTitle, titleColor, titleHighlightedColor)
|
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
|
strongSelf.currentThumbnailContainerNode = node
|
||||||
if let node = node {
|
if let node = node {
|
||||||
strongSelf.insertSubnode(node, aboveSubnode: strongSelf.footerNode)
|
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)
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate)
|
||||||
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
node.animateIn(fromLeft: fromLeft)
|
node.animateIn(fromLeft: fromLeft)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import AsyncDisplayKit
|
|||||||
|
|
||||||
final class InstantPageAnchorItem: InstantPageItem {
|
final class InstantPageAnchorItem: InstantPageItem {
|
||||||
let wantsNode: Bool = false
|
let wantsNode: Bool = false
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
|
|
||||||
let anchor: String
|
let anchor: String
|
||||||
@ -22,7 +23,7 @@ final class InstantPageAnchorItem: InstantPageItem {
|
|||||||
func drawInTile(context: CGContext) {
|
func drawInTile(context: CGContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,27 +6,30 @@ import AsyncDisplayKit
|
|||||||
final class InstantPageArticleItem: InstantPageItem {
|
final class InstantPageArticleItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
let webPage: TelegramMediaWebpage
|
let webPage: TelegramMediaWebpage
|
||||||
|
|
||||||
let title: String
|
let contentItems: [InstantPageItem]
|
||||||
let description: String
|
let contentSize: CGSize
|
||||||
let cover: TelegramMediaImage?
|
let cover: TelegramMediaImage?
|
||||||
let url: String
|
let url: String
|
||||||
let webpageId: MediaId
|
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.frame = frame
|
||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
self.title = title
|
self.contentItems = contentItems
|
||||||
self.description = description
|
self.contentSize = contentSize
|
||||||
self.cover = cover
|
self.cover = cover
|
||||||
self.url = url
|
self.url = url
|
||||||
self.webpageId = webpageId
|
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)? {
|
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, webPage: self.webPage, strings: strings, theme: theme, title: self.title, description: self.description, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl)
|
return InstantPageArticleNode(account: account, 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 {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
@ -35,7 +38,7 @@ final class InstantPageArticleItem: InstantPageItem {
|
|||||||
|
|
||||||
func matchesNode(_ node: InstantPageNode) -> Bool {
|
func matchesNode(_ node: InstantPageNode) -> Bool {
|
||||||
if let node = node as? InstantPageArticleNode {
|
if let node = node as? InstantPageArticleNode {
|
||||||
return self.webpageId == node.webpageId
|
return self === node.item
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -60,3 +63,30 @@ final class InstantPageArticleItem: InstantPageItem {
|
|||||||
return []
|
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 TelegramCore
|
||||||
import SwiftSignalKit
|
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 {
|
final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
|
||||||
private let titleNode: ASTextNode
|
let item: InstantPageArticleItem
|
||||||
private let descriptionNode: ASTextNode
|
|
||||||
private var imageNode: TransformImageNode?
|
|
||||||
|
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
|
|
||||||
let title: String
|
private let contentTile: InstantPageTile
|
||||||
let pageDescription: String
|
private let contentTileNode: InstantPageTileNode
|
||||||
|
private var imageNode: TransformImageNode?
|
||||||
|
|
||||||
let url: String
|
let url: String
|
||||||
let webpageId: MediaId
|
let webpageId: MediaId
|
||||||
let cover: TelegramMediaImage?
|
let cover: TelegramMediaImage?
|
||||||
@ -33,13 +24,12 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
private var fetchedDisposable = MetaDisposable()
|
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) {
|
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.title = title
|
self.item = item
|
||||||
self.pageDescription = description
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.webpageId = webpageId
|
self.webpageId = webpageId
|
||||||
self.cover = cover
|
self.cover = cover
|
||||||
self.rtl = isRtl(string: title) || isRtl(string: description)
|
self.rtl = rtl
|
||||||
self.openUrl = openUrl
|
self.openUrl = openUrl
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
@ -48,20 +38,15 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
self.buttonNode = HighlightableButtonNode()
|
self.buttonNode = HighlightableButtonNode()
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.contentTile = InstantPageTile(frame: CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height))
|
||||||
self.titleNode.isLayerBacked = true
|
self.contentTile.items.append(contentsOf: contentItems)
|
||||||
self.titleNode.maximumNumberOfLines = 1
|
self.contentTileNode = InstantPageTileNode(tile: self.contentTile, backgroundColor: .clear)
|
||||||
|
|
||||||
self.descriptionNode = ASTextNode()
|
|
||||||
self.descriptionNode.isLayerBacked = true
|
|
||||||
self.descriptionNode.maximumNumberOfLines = 2
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.highlightedBackgroundNode)
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.buttonNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.contentTileNode)
|
||||||
self.addSubnode(self.descriptionNode)
|
|
||||||
|
|
||||||
if let image = cover {
|
if let image = cover {
|
||||||
let imageNode = TransformImageNode()
|
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.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.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) {
|
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 size = largest.dimensions.aspectFilled(imageSize)
|
||||||
let boundingSize = imageSize
|
let boundingSize = imageSize
|
||||||
|
|
||||||
@ -121,26 +105,19 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
|
|||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height))
|
if let imageNode = self.imageNode {
|
||||||
let descriptionSize = self.descriptionNode.measure(CGSize(width: size.width - inset * 2.0 - sideInset, height: size.height))
|
if self.rtl {
|
||||||
|
imageNode.frame = CGRect(origin: CGPoint(x: inset, y: 14.0), size: imageSize)
|
||||||
if self.rtl {
|
} else {
|
||||||
if let imageNode = self.imageNode {
|
imageNode.frame = CGRect(origin: CGPoint(x: size.width - inset - imageSize.width, y: 14.0), size: imageSize)
|
||||||
imageNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((size.height - imageSize.height) / 2.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 updateIsVisible(_ isVisible: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
||||||
@ -148,12 +125,9 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateHiddenMedia(media: InstantPageMedia?) {
|
func updateHiddenMedia(media: InstantPageMedia?) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
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
|
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AsyncDisplayKit
|
|||||||
final class InstantPageAudioItem: InstantPageItem {
|
final class InstantPageAudioItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia]
|
let medias: [InstantPageMedia]
|
||||||
|
|
||||||
let media: InstantPageMedia
|
let media: InstantPageMedia
|
||||||
@ -18,7 +19,7 @@ final class InstantPageAudioItem: InstantPageItem {
|
|||||||
self.medias = [media]
|
self.medias = [media]
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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 updateIsVisible(_ isVisible: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
}
|
||||||
|
|
||||||
@objc func buttonPressed() {
|
@objc func buttonPressed() {
|
||||||
if let _ = self.playbackState {
|
if let _ = self.playbackState {
|
||||||
self.account.telegramApplicationContext.mediaManager?.playlistControl(.playback(.togglePlayPause), type: self.playlistType)
|
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)
|
itemNode.update(strings: strings, theme: theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateVisibleItems()
|
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
|
||||||
self.updateNavigationBar()
|
self.updateNavigationBar()
|
||||||
|
|
||||||
self.recursivelyEnsureDisplaySynchronously(true)
|
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() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
@ -195,25 +222,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
recognizer.delaysTouchesBegan = false
|
recognizer.delaysTouchesBegan = false
|
||||||
recognizer.tapActionAtPoint = { [weak self] point in
|
recognizer.tapActionAtPoint = { [weak self] point in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let currentLayout = strongSelf.currentLayout {
|
return strongSelf.tapActionAtPoint(point)
|
||||||
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 .waitForSingleTap
|
return .waitForSingleTap
|
||||||
}
|
}
|
||||||
@ -303,7 +312,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.scrollNode.view.contentOffset = contentOffset
|
self.scrollNode.view.contentOffset = contentOffset
|
||||||
}
|
}
|
||||||
if shouldUpdateVisibleItems {
|
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 expandedDetails: [Int : Bool] = [:]
|
||||||
|
|
||||||
var itemIndex = -1
|
var detailsIndex = -1
|
||||||
for item in currentLayout.items {
|
for item in currentLayout.items {
|
||||||
if item.wantsNode {
|
if item.wantsNode {
|
||||||
itemIndex += 1
|
|
||||||
currentLayoutItemsWithNodes.append(item)
|
currentLayoutItemsWithNodes.append(item)
|
||||||
if let group = item.distanceThresholdGroup() {
|
if let group = item.distanceThresholdGroup() {
|
||||||
let count: Int
|
let count: Int
|
||||||
@ -341,14 +349,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
distanceThresholdGroupCount[Int(group)] = count + 1
|
distanceThresholdGroupCount[Int(group)] = count + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if let detailsItem = item as? InstantPageDetailsItem {
|
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 {
|
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))
|
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 {
|
guard let theme = self.theme else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -373,8 +379,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var visibleTileIndices = Set<Int>()
|
var visibleTileIndices = Set<Int>()
|
||||||
var visibleItemIndices = Set<Int>()
|
var visibleItemIndices = Set<Int>()
|
||||||
|
|
||||||
let visibleBounds = self.scrollNode.view.bounds
|
|
||||||
|
|
||||||
var topNode: ASDisplayNode?
|
var topNode: ASDisplayNode?
|
||||||
let topTileNode = topNode
|
let topTileNode = topNode
|
||||||
if let scrollSubnodes = self.scrollNode.subnodes {
|
if let scrollSubnodes = self.scrollNode.subnodes {
|
||||||
@ -421,9 +425,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
thresholdedItemFrame.origin.y -= itemThreshold
|
thresholdedItemFrame.origin.y -= itemThreshold
|
||||||
thresholdedItemFrame.size.height += itemThreshold * 2.0
|
thresholdedItemFrame.size.height += itemThreshold * 2.0
|
||||||
|
|
||||||
if let expanded = self.currentExpandedDetails?[detailsIndex], !expanded {
|
if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] {
|
||||||
collapseOffset += itemFrame.height - 44.0
|
let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight
|
||||||
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: 44.0))
|
collapseOffset += itemFrame.height - height
|
||||||
|
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height))
|
||||||
}
|
}
|
||||||
|
|
||||||
if visibleBounds.intersects(thresholdedItemFrame) {
|
if visibleBounds.intersects(thresholdedItemFrame) {
|
||||||
@ -442,7 +447,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let itemIndex = itemIndex
|
let itemIndex = itemIndex
|
||||||
let embedIndex = embedIndex
|
let embedIndex = embedIndex
|
||||||
let detailsIndex = detailsIndex
|
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)
|
self?.openMedia(media)
|
||||||
}, openPeer: { [weak self] peerId in
|
}, openPeer: { [weak self] peerId in
|
||||||
self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
@ -452,23 +457,39 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self?.updateWebEmbedHeight(embedIndex, height)
|
self?.updateWebEmbedHeight(embedIndex, height)
|
||||||
}, updateDetailsExpanded: { [weak self] expanded in
|
}, updateDetailsExpanded: { [weak self] expanded in
|
||||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||||
}) {
|
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||||
itemNode.frame = itemFrame
|
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 {
|
if let topNode = topNode {
|
||||||
self.scrollNode.insertSubnode(itemNode, aboveSubnode: topNode)
|
self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode)
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
if (itemNode as! ASDisplayNode).frame != itemFrame {
|
if (itemNode as! ASDisplayNode).frame != itemFrame {
|
||||||
let previousFrame = (itemNode as! ASDisplayNode).frame
|
transition.updateFrame(node: (itemNode as! ASDisplayNode), frame: itemFrame)
|
||||||
(itemNode as! ASDisplayNode).frame = itemFrame
|
itemNode?.updateLayout(size: itemFrame.size, transition: transition)
|
||||||
transition.animateFrame(node: (itemNode as! ASDisplayNode), from: previousFrame)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
for tile in self.currentLayoutTiles {
|
||||||
tileIndex += 1
|
tileIndex += 1
|
||||||
|
|
||||||
var tileFrame = tile.frame
|
let tileFrame = effectiveFrameForTile(tile)
|
||||||
if tileIndex > 0 {
|
|
||||||
tileFrame = tileFrame.offsetBy(dx: 0.0, dy: -collapseOffset)
|
|
||||||
}
|
|
||||||
var tileVisibleFrame = tileFrame
|
var tileVisibleFrame = tileFrame
|
||||||
tileVisibleFrame.origin.y -= 400.0
|
tileVisibleFrame.origin.y -= 400.0
|
||||||
tileVisibleFrame.size.height += 400.0 * 2.0
|
tileVisibleFrame.size.height += 400.0 * 2.0
|
||||||
if tileVisibleFrame.intersects(visibleBounds) || animated {
|
if tileVisibleFrame.intersects(visibleBounds) {
|
||||||
visibleTileIndices.insert(tileIndex)
|
visibleTileIndices.insert(tileIndex)
|
||||||
|
|
||||||
if visibleTiles[tileIndex] == nil {
|
if self.visibleTiles[tileIndex] == nil {
|
||||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
|
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 {
|
if let topNode = topNode {
|
||||||
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
|
self.scrollNode.insertSubnode(tileNode, aboveSubnode: topNode)
|
||||||
} else {
|
} else {
|
||||||
@ -500,9 +521,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.visibleTiles[tileIndex] = tileNode
|
self.visibleTiles[tileIndex] = tileNode
|
||||||
} else {
|
} else {
|
||||||
if visibleTiles[tileIndex]!.frame != tileFrame {
|
if visibleTiles[tileIndex]!.frame != tileFrame {
|
||||||
let previousFrame = visibleTiles[tileIndex]!.frame
|
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
|
||||||
visibleTiles[tileIndex]!.frame = tileFrame
|
|
||||||
transition.animateFrame(node: visibleTiles[tileIndex]!, from: previousFrame)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -550,7 +569,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
self.updateVisibleItems()
|
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds)
|
||||||
self.updateNavigationBar()
|
self.updateNavigationBar()
|
||||||
self.previousContentOffset = self.scrollNode.view.contentOffset
|
self.previousContentOffset = self.scrollNode.view.contentOffset
|
||||||
}
|
}
|
||||||
@ -647,16 +666,16 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var rects: [CGRect]?
|
var rects: [CGRect]?
|
||||||
if let location = location, let currentLayout = self.currentLayout {
|
if let location = location, let currentLayout = self.currentLayout {
|
||||||
for item in currentLayout.items {
|
for item in currentLayout.items {
|
||||||
if item.frame.contains(location) {
|
let itemFrame = effectiveFrameForItem(item)
|
||||||
let itemNodeFrame = item.frame
|
if itemFrame.contains(location) {
|
||||||
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -item.frame.minX, dy: -item.frame.minY))
|
|
||||||
var contentOffset = CGPoint()
|
var contentOffset = CGPoint()
|
||||||
if let item = item as? InstantPageTableItem {
|
if let item = item as? InstantPageTableItem {
|
||||||
contentOffset = tableContentOffset(item: item)
|
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 {
|
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 {
|
if !itemRects.isEmpty {
|
||||||
rects = itemRects
|
rects = itemRects
|
||||||
@ -697,21 +716,44 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return contentOffset
|
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 {
|
private func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
|
||||||
let layoutOrigin = item.frame.origin
|
let layoutOrigin = item.frame.origin
|
||||||
var origin = layoutOrigin
|
var origin = layoutOrigin
|
||||||
|
|
||||||
for item in self.currentDetailsItems {
|
for item in self.currentDetailsItems {
|
||||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
||||||
if !expanded && layoutOrigin.y >= item.frame.maxY {
|
if layoutOrigin.y >= item.frame.maxY {
|
||||||
let offset = 44.0 - item.frame.height
|
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
|
||||||
origin.y += offset
|
origin.y += height - item.frame.height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = item as? InstantPageDetailsItem {
|
if let item = item as? InstantPageDetailsItem {
|
||||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
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 {
|
} else {
|
||||||
return CGRect(origin: origin, size: item.frame.size)
|
return CGRect(origin: origin, size: item.frame.size)
|
||||||
}
|
}
|
||||||
@ -720,17 +762,23 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
|
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
|
||||||
if let currentLayout = self.currentLayout {
|
if let currentLayout = self.currentLayout {
|
||||||
for item in currentLayout.items {
|
for item in currentLayout.items {
|
||||||
let frame = self.effectiveFrameForItem(item)
|
let itemFrame = self.effectiveFrameForItem(item)
|
||||||
if frame.contains(location) {
|
if itemFrame.contains(location) {
|
||||||
if let item = item as? InstantPageTextItem, item.selectable {
|
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 {
|
} else if let item = item as? InstantPageTableItem {
|
||||||
let contentOffset = tableContentOffset(item: item)
|
let contentOffset = tableContentOffset(item: item)
|
||||||
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -item.frame.minX + contentOffset.x, dy: -item.frame.minY)) {
|
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
|
||||||
return (textItem, item.frame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
|
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
|
||||||
}
|
}
|
||||||
} else if let item = item as? InstantPageDetailsItem {
|
} 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)
|
self.present(actionSheet, nil)
|
||||||
} else if let (item, parentOffset) = self.textItemAtLocation(location) {
|
} else if let (item, parentOffset) = self.textItemAtLocation(location) {
|
||||||
let textNodeFrame = effectiveFrameForItem(item)
|
let textFrame = item.frame
|
||||||
var itemRects = item.lineRects()
|
var itemRects = item.lineRects()
|
||||||
for i in 0 ..< itemRects.count {
|
for i in 0 ..< itemRects.count {
|
||||||
itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textNodeFrame.minX, dy: parentOffset.y + textNodeFrame.minY).insetBy(dx: -2.0, dy: -2.0)
|
itemRects[i] = itemRects[i].offsetBy(dx: parentOffset.x + textFrame.minX, dy: parentOffset.y + textFrame.minY).insetBy(dx: -2.0, dy: -2.0)
|
||||||
}
|
}
|
||||||
self.updateTextSelectionRects(itemRects, text: item.plainText())
|
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) {
|
private func openUrl(_ url: InstantPageUrlItem) {
|
||||||
guard let items = self.currentLayout?.items else {
|
guard let items = self.currentLayout?.items else {
|
||||||
return
|
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: "#") {
|
if let webPage = self.webPage, url.webpageId == webPage.id, let anchorRange = url.url.range(of: "#") {
|
||||||
let anchor = url.url[anchorRange.upperBound...]
|
let anchor = url.url[anchorRange.upperBound...]
|
||||||
if !anchor.isEmpty {
|
if !anchor.isEmpty {
|
||||||
for item in items {
|
if let anchorItem = findAnchorItem(String(anchor), items: items) {
|
||||||
if let item = item as? InstantPageAnchorItem, item.anchor == anchor {
|
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: anchorItem.frame.origin.y - self.scrollNode.view.contentInset.top), animated: true)
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: item.frame.origin.y - self.scrollNode.view.contentInset.top), animated: true)
|
return
|
||||||
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) {
|
private func openMedia(_ media: InstantPageMedia) {
|
||||||
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
|
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
|
||||||
return
|
return
|
||||||
@ -942,11 +1013,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var medias: [InstantPageMedia] = []
|
var medias: [InstantPageMedia] = mediasFromItems(items)
|
||||||
for item in items {
|
|
||||||
medias.append(contentsOf: item.medias)
|
|
||||||
}
|
|
||||||
|
|
||||||
medias = medias.filter {
|
medias = medias.filter {
|
||||||
$0.media is TelegramMediaImage
|
$0.media is TelegramMediaImage
|
||||||
}
|
}
|
||||||
@ -1000,7 +1067,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
|
self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateLayout()
|
strongSelf.updateLayout()
|
||||||
strongSelf.updateVisibleItems()
|
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1011,7 +1078,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
currentExpandedDetails[index] = expanded
|
currentExpandedDetails[index] = expanded
|
||||||
self.currentExpandedDetails = currentExpandedDetails
|
self.currentExpandedDetails = currentExpandedDetails
|
||||||
}
|
}
|
||||||
self.updateVisibleItems(animated: true)
|
self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentSettings() {
|
private func presentSettings() {
|
||||||
|
|||||||
@ -2,22 +2,26 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class InstantPageDetailsItem: InstantPageItem {
|
final class InstantPageDetailsItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = true
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
|
|
||||||
let title: NSAttributedString
|
let titleItems: [InstantPageItem]
|
||||||
|
let titleHeight: CGFloat
|
||||||
let items: [InstantPageItem]
|
let items: [InstantPageItem]
|
||||||
let safeInset: CGFloat
|
let safeInset: CGFloat
|
||||||
let rtl: Bool
|
let rtl: Bool
|
||||||
var initiallyExpanded: Bool
|
let initiallyExpanded: Bool
|
||||||
let index: Int
|
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.frame = frame
|
||||||
self.title = title
|
self.titleItems = titleItems
|
||||||
|
self.titleHeight = titleHeight
|
||||||
self.items = items
|
self.items = items
|
||||||
self.safeInset = safeInset
|
self.safeInset = safeInset
|
||||||
self.rtl = rtl
|
self.rtl = rtl
|
||||||
@ -25,8 +29,12 @@ final class InstantPageDetailsItem: InstantPageItem {
|
|||||||
self.index = index
|
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)? {
|
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 InstantPageDetailsNode(account: account, strings: strings, theme: theme, item: self, updateDetailsExpanded: updateDetailsExpanded)
|
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 {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
@ -46,21 +54,48 @@ final class InstantPageDetailsItem: InstantPageItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat {
|
func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat {
|
||||||
if count > 3 {
|
return CGFloat.greatestFiniteMagnitude
|
||||||
return 1000.0
|
|
||||||
} else {
|
|
||||||
return CGFloat.greatestFiniteMagnitude
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawInTile(context: CGContext) {
|
func drawInTile(context: CGContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkSelectionRects(at point: CGPoint) -> [CGRect] {
|
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 []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) -> InstantPageDetailsItem {
|
func layoutDetailsItem(theme: InstantPageTheme, title: NSAttributedString, boundingWidth: CGFloat, items: [InstantPageItem], contentSize: CGSize, safeInset: CGFloat, rtl: Bool, initiallyExpanded: Bool, index: Int) -> InstantPageDetailsItem {
|
||||||
return InstantPageDetailsItem(frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: contentSize.height + 44.0), title: title, items: items, 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 TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
private let detailsHeaderHeight: CGFloat = 44.0
|
|
||||||
private let detailsInset: CGFloat = 17.0
|
private let detailsInset: CGFloat = 17.0
|
||||||
private let titleInset: CGFloat = 22.0
|
private let titleInset: CGFloat = 22.0
|
||||||
|
|
||||||
@ -14,6 +13,10 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let theme: InstantPageTheme
|
private let theme: InstantPageTheme
|
||||||
|
|
||||||
|
private let openMedia: (InstantPageMedia) -> Void
|
||||||
|
private let openPeer: (PeerId) -> Void
|
||||||
|
private let openUrl: (InstantPageUrlItem) -> Void
|
||||||
|
|
||||||
var currentLayoutTiles: [InstantPageTile] = []
|
var currentLayoutTiles: [InstantPageTile] = []
|
||||||
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
||||||
var distanceThresholdGroupCount: [Int: Int] = [:]
|
var distanceThresholdGroupCount: [Int: Int] = [:]
|
||||||
@ -21,14 +24,26 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
var visibleTiles: [Int: InstantPageTileNode] = [:]
|
var visibleTiles: [Int: InstantPageTileNode] = [:]
|
||||||
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
|
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
|
||||||
|
|
||||||
|
var currentWebEmbedHeights: [Int : CGFloat] = [:]
|
||||||
|
var currentExpandedDetails: [Int : Bool]?
|
||||||
|
var currentDetailsItems: [InstantPageDetailsItem] = []
|
||||||
|
|
||||||
|
var requestLayoutUpdate: (() -> Void)?
|
||||||
|
|
||||||
var currentLayout: InstantPageLayout
|
var currentLayout: InstantPageLayout
|
||||||
let contentSize: CGSize
|
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.account = account
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
|
self.openMedia = openMedia
|
||||||
|
self.openPeer = openPeer
|
||||||
|
self.openUrl = openUrl
|
||||||
|
|
||||||
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||||
self.contentSize = contentSize
|
self.contentSize = contentSize
|
||||||
|
|
||||||
@ -45,9 +60,13 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
|
|
||||||
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
|
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
|
||||||
|
|
||||||
|
var currentDetailsItems: [InstantPageDetailsItem] = []
|
||||||
var currentLayoutItemsWithViews: [InstantPageItem] = []
|
var currentLayoutItemsWithViews: [InstantPageItem] = []
|
||||||
var distanceThresholdGroupCount: [Int : Int] = [:]
|
var distanceThresholdGroupCount: [Int : Int] = [:]
|
||||||
|
|
||||||
|
var expandedDetails: [Int : Bool] = [:]
|
||||||
|
|
||||||
|
var detailsIndex = -1
|
||||||
for item in self.currentLayout.items {
|
for item in self.currentLayout.items {
|
||||||
if item.wantsNode {
|
if item.wantsNode {
|
||||||
currentLayoutItemsWithViews.append(item)
|
currentLayoutItemsWithViews.append(item)
|
||||||
@ -60,21 +79,41 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
distanceThresholdGroupCount[Int(group)] = count + 1
|
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.currentLayoutTiles = currentLayoutTiles
|
||||||
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
|
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
|
||||||
|
self.currentDetailsItems = currentDetailsItems
|
||||||
self.distanceThresholdGroupCount = distanceThresholdGroupCount
|
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 visibleTileIndices = Set<Int>()
|
||||||
var visibleItemIndices = Set<Int>()
|
var visibleItemIndices = Set<Int>()
|
||||||
|
|
||||||
let visibleBounds = self.bounds // self.scrollNode.view.bounds
|
self.previousVisibleBounds = visibleBounds
|
||||||
|
|
||||||
var topNode: ASDisplayNode?
|
var topNode: ASDisplayNode?
|
||||||
|
let topTileNode = topNode
|
||||||
if let scrollSubnodes = self.subnodes {
|
if let scrollSubnodes = self.subnodes {
|
||||||
for node in scrollSubnodes.reversed() {
|
for node in scrollSubnodes.reversed() {
|
||||||
if let node = node as? InstantPageTileNode {
|
if let node = node as? InstantPageTileNode {
|
||||||
@ -84,32 +123,27 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tileIndex = -1
|
var collapseOffset: CGFloat = 0.0
|
||||||
for tile in self.currentLayoutTiles {
|
let transition: ContainedViewLayoutTransition
|
||||||
tileIndex += 1
|
if animated {
|
||||||
var tileVisibleFrame = tile.frame
|
transition = .animated(duration: 0.3, curve: .spring)
|
||||||
tileVisibleFrame.origin.y -= 400.0
|
} else {
|
||||||
tileVisibleFrame.size.height += 400.0 * 2.0
|
transition = .immediate
|
||||||
if tileVisibleFrame.intersects(visibleBounds) {
|
|
||||||
visibleTileIndices.insert(tileIndex)
|
|
||||||
|
|
||||||
if visibleTiles[tileIndex] == nil {
|
|
||||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: .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 itemIndex = -1
|
var itemIndex = -1
|
||||||
|
var embedIndex = -1
|
||||||
|
var detailsIndex = -1
|
||||||
|
|
||||||
for item in self.currentLayoutItemsWithNodes {
|
for item in self.currentLayoutItemsWithNodes {
|
||||||
itemIndex += 1
|
itemIndex += 1
|
||||||
|
if item is InstantPageWebEmbedItem {
|
||||||
|
embedIndex += 1
|
||||||
|
}
|
||||||
|
if item is InstantPageDetailsItem {
|
||||||
|
detailsIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
var itemThreshold: CGFloat = 0.0
|
var itemThreshold: CGFloat = 0.0
|
||||||
if let group = item.distanceThresholdGroup() {
|
if let group = item.distanceThresholdGroup() {
|
||||||
var count: Int = 0
|
var count: Int = 0
|
||||||
@ -118,10 +152,19 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
itemThreshold = item.distanceThresholdWithGroupCount(count)
|
itemThreshold = item.distanceThresholdWithGroupCount(count)
|
||||||
}
|
}
|
||||||
var itemFrame = item.frame
|
|
||||||
itemFrame.origin.y -= itemThreshold
|
var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
|
||||||
itemFrame.size.height += itemThreshold * 2.0
|
var thresholdedItemFrame = itemFrame
|
||||||
if visibleBounds.intersects(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)
|
visibleItemIndices.insert(itemIndex)
|
||||||
|
|
||||||
var itemNode = self.visibleItemsWithNodes[itemIndex]
|
var itemNode = self.visibleItemsWithNodes[itemIndex]
|
||||||
@ -134,28 +177,82 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if itemNode == nil {
|
if itemNode == nil {
|
||||||
if let itemNode = item.node(account: self.account, strings: self.strings, theme: self.theme, openMedia: { [weak self] media in
|
let itemIndex = itemIndex
|
||||||
//self?.openMedia(media)
|
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
|
}, openPeer: { [weak self] peerId in
|
||||||
//self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
}, openUrl: { [weak self] url in
|
}, openUrl: { [weak self] url in
|
||||||
//self?.openUrl(url)
|
self?.openUrl(url)
|
||||||
}, updateWebEmbedHeight: { [weak self] height in
|
}, updateWebEmbedHeight: { [weak self] height in
|
||||||
//self?.updateWebEmbedHeight(key, height)
|
//self?.updateWebEmbedHeight(embedIndex, height)
|
||||||
}, updateDetailsExpanded: { _ in
|
}, updateDetailsExpanded: { [weak self] expanded in
|
||||||
}) {
|
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||||
itemNode.frame = item.frame
|
}, 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 {
|
if let topNode = topNode {
|
||||||
self.insertSubnode(itemNode, aboveSubnode: topNode)
|
self.insertSubnode(newNode, aboveSubnode: topNode)
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
if (itemNode as! ASDisplayNode).frame != item.frame {
|
if (itemNode as! ASDisplayNode).frame != itemFrame {
|
||||||
(itemNode as! ASDisplayNode).frame = item.frame
|
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)
|
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 {
|
final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
||||||
@ -204,12 +447,14 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
private let buttonNode: HighlightableButtonNode
|
private let buttonNode: HighlightableButtonNode
|
||||||
private let arrowNode: InstantPageDetailsArrowNode
|
private let arrowNode: InstantPageDetailsArrowNode
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let contentNode: InstantPageDetailsContentNode
|
let contentNode: InstantPageDetailsContentNode
|
||||||
|
|
||||||
private let updateExpanded: (Bool) -> Void
|
private let updateExpanded: (Bool) -> Void
|
||||||
var expanded: Bool
|
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.account = account
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
@ -225,39 +470,31 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
self.buttonNode = HighlightableButtonNode()
|
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)
|
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.separatorNode = ASDisplayNode()
|
||||||
|
|
||||||
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height))
|
self.contentNode = InstantPageDetailsContentNode(account: account, strings: strings, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, openPeer: openPeer, openUrl: openUrl)
|
||||||
|
|
||||||
self.expanded = item.initiallyExpanded
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.addSubnode(self.contentNode)
|
||||||
self.addSubnode(self.highlightedBackgroundNode)
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubnode(self.buttonNode)
|
||||||
self.addSubnode(self.titleTileNode)
|
self.addSubnode(self.titleTileNode)
|
||||||
self.addSubnode(self.arrowNode)
|
self.addSubnode(self.arrowNode)
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.contentNode)
|
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
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)
|
self.update(strings: strings, theme: theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,11 +534,10 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.updateExpanded(expanded)
|
self.updateExpanded(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
let size = layout.size
|
|
||||||
let inset = detailsInset + self.item.safeInset
|
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))
|
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
|
let inset = detailsInset + self.item.safeInset
|
||||||
|
|
||||||
self.titleTileNode.frame = self.titleTile.frame
|
self.titleTileNode.frame = self.titleTile.frame
|
||||||
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: detailsHeaderHeight + UIScreenPixel))
|
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: self.item.titleHeight + UIScreenPixel))
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: detailsHeaderHeight))
|
self.buttonNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: self.item.titleHeight))
|
||||||
self.arrowNode.frame = CGRect(x: inset, y: floorToScreenPixels((detailsHeaderHeight - 8.0) / 2.0) + 1.0, width: 13.0, height: 8.0)
|
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: detailsHeaderHeight, width: size.width, height: self.item.frame.height - detailsHeaderHeight)
|
self.contentNode.frame = CGRect(x: 0.0, y: self.item.titleHeight, width: size.width, height: self.item.frame.height - self.item.titleHeight)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateIsVisible(_ isVisible: Bool) {
|
func updateIsVisible(_ isVisible: Bool) {
|
||||||
@ -324,11 +559,11 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
||||||
return nil
|
return self.contentNode.transitionNode(media: media)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHiddenMedia(media: InstantPageMedia?) {
|
func updateHiddenMedia(media: InstantPageMedia?) {
|
||||||
|
self.contentNode.updateHiddenMedia(media: media)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
@ -336,6 +571,41 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.separatorNode.backgroundColor = theme.controlColor
|
self.separatorNode.backgroundColor = theme.controlColor
|
||||||
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
|
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 {
|
private final class InstantPageDetailsArrowNodeParameters: NSObject {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AsyncDisplayKit
|
|||||||
final class InstantPageFeedbackItem: InstantPageItem {
|
final class InstantPageFeedbackItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
|
|
||||||
let webPage: TelegramMediaWebpage
|
let webPage: TelegramMediaWebpage
|
||||||
@ -15,7 +16,7 @@ final class InstantPageFeedbackItem: InstantPageItem {
|
|||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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 updateIsVisible(_ isVisible: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
||||||
@ -89,7 +91,6 @@ final class InstantPageFeedbackNode: ASDisplayNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateHiddenMedia(media: InstantPageMedia?) {
|
func updateHiddenMedia(media: InstantPageMedia?) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ final class InstantPageImageItem: InstantPageItem {
|
|||||||
let fit: Bool
|
let fit: Bool
|
||||||
|
|
||||||
let wantsNode: Bool = true
|
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) {
|
init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute] = [], url: InstantPageUrlItem? = nil, interactive: Bool, roundCorners: Bool, fit: Bool) {
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
@ -41,7 +42,7 @@ final class InstantPageImageItem: InstantPageItem {
|
|||||||
self.fit = fit
|
self.fit = fit
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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 updateIsVisible(_ isVisible: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
if self.theme.imageEmptyColor != theme.imageEmptyColor {
|
if self.theme.imageEmptyColor != theme.imageEmptyColor {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|||||||
@ -7,10 +7,11 @@ protocol InstantPageItem {
|
|||||||
var frame: CGRect { get set }
|
var frame: CGRect { get set }
|
||||||
var wantsNode: Bool { get }
|
var wantsNode: Bool { get }
|
||||||
var medias: [InstantPageMedia] { get }
|
var medias: [InstantPageMedia] { get }
|
||||||
|
var separatesTiles: Bool { get }
|
||||||
|
|
||||||
func matchesAnchor(_ anchor: String) -> Bool
|
func matchesAnchor(_ anchor: String) -> Bool
|
||||||
func drawInTile(context: CGContext)
|
func drawInTile(context: CGContext)
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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 matchesNode(_ node: InstantPageNode) -> Bool
|
||||||
func linkSelectionRects(at point: CGPoint) -> [CGRect]
|
func linkSelectionRects(at point: CGPoint) -> [CGRect]
|
||||||
|
|
||||||
|
|||||||
@ -674,25 +674,30 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
|||||||
let detailsIndex = detailsIndexCounter
|
let detailsIndex = detailsIndexCounter
|
||||||
detailsIndexCounter += 1
|
detailsIndexCounter += 1
|
||||||
|
|
||||||
|
var subDetailsIndex = 0
|
||||||
|
|
||||||
var previousBlock: InstantPageBlock?
|
var previousBlock: InstantPageBlock?
|
||||||
for subBlock in blocks {
|
for subBlock in blocks {
|
||||||
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: subitems, fillToWidthAndHeight: false, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, 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 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)
|
subitems.append(contentsOf: blockItems)
|
||||||
contentSize.height += subLayout.contentSize.height + spacing
|
contentSize.height += subLayout.contentSize.height + spacing
|
||||||
previousBlock = subBlock
|
previousBlock = subBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
|
if !blocks.isEmpty {
|
||||||
contentSize.height += closingSpacing
|
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
|
||||||
|
contentSize.height += closingSpacing
|
||||||
|
}
|
||||||
|
|
||||||
let styleStack = InstantPageTextStyleStack()
|
let styleStack = InstantPageTextStyleStack()
|
||||||
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
|
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)
|
let detailsItem = layoutDetailsItem(theme: theme, title: attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth, items: subitems, contentSize: contentSize, safeInset: safeInset, rtl: rtl, initiallyExpanded: expanded, index: detailsIndex)
|
||||||
|
|
||||||
return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem])
|
return InstantPageLayout(origin: CGPoint(), contentSize: detailsItem.frame.size, items: [detailsItem])
|
||||||
|
|
||||||
case let .relatedArticles(title, articles):
|
case let .relatedArticles(title, articles):
|
||||||
var contentSize = CGSize(width: boundingWidth, height: 0.0)
|
var contentSize = CGSize(width: boundingWidth, height: 0.0)
|
||||||
var items: [InstantPageItem] = []
|
var items: [InstantPageItem] = []
|
||||||
@ -713,7 +718,16 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
|||||||
cover = media[coverId] as? TelegramMediaImage
|
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
|
contentSize.height += item.frame.height
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import TelegramCore
|
|||||||
func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat {
|
func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat {
|
||||||
if let upper = upper, let lower = lower {
|
if let upper = upper, let lower = lower {
|
||||||
switch (upper, lower) {
|
switch (upper, lower) {
|
||||||
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil):
|
case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, nil), (.anchor, _), (_, .anchor):
|
||||||
return 0.0
|
return 0.0
|
||||||
case (.divider, _), (_, .divider):
|
case (.divider, _), (_, .divider):
|
||||||
return 25.0
|
return 25.0
|
||||||
@ -49,7 +49,7 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) ->
|
|||||||
}
|
}
|
||||||
} else if let lower = lower {
|
} else if let lower = lower {
|
||||||
switch lower {
|
switch lower {
|
||||||
case .cover, .channelBanner:
|
case .cover, .channelBanner, .details, .anchor:
|
||||||
return 0.0
|
return 0.0
|
||||||
default:
|
default:
|
||||||
return 24.0
|
return 24.0
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
protocol InstantPageNode {
|
protocol InstantPageNode {
|
||||||
func updateIsVisible(_ isVisible: Bool)
|
func updateIsVisible(_ isVisible: Bool)
|
||||||
@ -7,4 +8,6 @@ protocol InstantPageNode {
|
|||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)?
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)?
|
||||||
func updateHiddenMedia(media: InstantPageMedia?)
|
func updateHiddenMedia(media: InstantPageMedia?)
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme)
|
func update(strings: PresentationStrings, theme: InstantPageTheme)
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AsyncDisplayKit
|
|||||||
final class InstantPagePeerReferenceItem: InstantPageItem {
|
final class InstantPagePeerReferenceItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
|
|
||||||
let initialPeer: Peer
|
let initialPeer: Peer
|
||||||
@ -19,7 +20,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
|
|||||||
self.rtl = rtl
|
self.rtl = rtl
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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) {
|
private func applyThemeAndStrings(themeUpdated: Bool) {
|
||||||
if let peer = self.peer {
|
if let peer = self.peer {
|
||||||
self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: self.theme.panelPrimaryColor)
|
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 interactive: Bool
|
||||||
|
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
|
|
||||||
init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool) {
|
init(frame: CGRect, webPage: TelegramMediaWebpage, media: InstantPageMedia, interactive: Bool) {
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
@ -23,7 +24,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem {
|
|||||||
self.interactive = interactive
|
self.interactive = interactive
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ final class InstantPageShapeItem: InstantPageItem {
|
|||||||
|
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
let wantsNode: Bool = false
|
let wantsNode: Bool = false
|
||||||
|
let separatesTiles: Bool = false
|
||||||
|
|
||||||
init(frame: CGRect, shapeFrame: CGRect, shape: InstantPageShape, color: UIColor) {
|
init(frame: CGRect, shapeFrame: CGRect, shape: InstantPageShape, color: UIColor) {
|
||||||
self.frame = frame
|
self.frame = frame
|
||||||
@ -56,7 +57,7 @@ final class InstantPageShapeItem: InstantPageItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
|
|||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let webPage: TelegramMediaWebpage
|
let webPage: TelegramMediaWebpage
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
let medias: [InstantPageMedia]
|
let medias: [InstantPageMedia]
|
||||||
|
|
||||||
init(frame: CGRect, webPage: TelegramMediaWebpage, medias: [InstantPageMedia]) {
|
init(frame: CGRect, webPage: TelegramMediaWebpage, medias: [InstantPageMedia]) {
|
||||||
@ -15,7 +16,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
|
|||||||
self.medias = medias
|
self.medias = medias
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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
|
self.pagerNode.internalIsVisible = isVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,6 +100,7 @@ final class InstantPageTableItem: InstantPageItem {
|
|||||||
let horizontalInset: CGFloat
|
let horizontalInset: CGFloat
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = false
|
||||||
|
|
||||||
let theme: InstantPageTheme
|
let theme: InstantPageTheme
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ final class InstantPageTableItem: InstantPageItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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 cell in self.item.cells {
|
||||||
for item in cell.additionalItems {
|
for item in cell.additionalItems {
|
||||||
if item.wantsNode {
|
if item.wantsNode {
|
||||||
if let node = item.node(account: account, strings: strings, theme: theme, openMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in}, updateWebEmbedHeight: { _ in }, 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)
|
node.frame = item.frame.offsetBy(dx: cell.frame.minX, dy: cell.frame.minY)
|
||||||
self.addSubnode(node)
|
self.addSubnode(node)
|
||||||
}
|
}
|
||||||
@ -290,7 +291,9 @@ final class InstantPageTableNode: ASScrollNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateIsVisible(_ isVisible: Bool) {
|
func updateIsVisible(_ isVisible: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, () -> UIView?)? {
|
||||||
@ -298,7 +301,6 @@ final class InstantPageTableNode: ASScrollNode, InstantPageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateHiddenMedia(media: InstantPageMedia?) {
|
func updateHiddenMedia(media: InstantPageMedia?) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
|
|||||||
@ -57,6 +57,7 @@ final class InstantPageTextItem: InstantPageItem {
|
|||||||
var alignment: NSTextAlignment = .natural
|
var alignment: NSTextAlignment = .natural
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
let wantsNode: Bool = false
|
let wantsNode: Bool = false
|
||||||
|
let separatesTiles: Bool = false
|
||||||
var selectable: Bool = true
|
var selectable: Bool = true
|
||||||
|
|
||||||
var containsRTL: Bool {
|
var containsRTL: Bool {
|
||||||
@ -309,7 +310,7 @@ final class InstantPageTextItem: InstantPageItem {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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
|
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 {
|
if string.length == 0 {
|
||||||
return ([], CGSize())
|
return ([], CGSize())
|
||||||
}
|
}
|
||||||
@ -490,10 +491,24 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lineCharacterCount > 0 {
|
if lineCharacterCount > 0 {
|
||||||
let line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
|
var line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0)
|
||||||
let lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
|
var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil))
|
||||||
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
|
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 strikethroughItems: [InstantPageTextStrikethroughItem] = []
|
||||||
var markedItems: [InstantPageTextMarkedItem] = []
|
var markedItems: [InstantPageTextMarkedItem] = []
|
||||||
|
|
||||||
@ -563,8 +578,12 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
|||||||
currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
|
currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
|
||||||
|
|
||||||
lastIndex += lineCharacterCount
|
lastIndex += lineCharacterCount
|
||||||
|
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ enum InstantPageTextCategoryType {
|
|||||||
case paragraph
|
case paragraph
|
||||||
case caption
|
case caption
|
||||||
case table
|
case table
|
||||||
|
case article
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InstantPageTextCategories {
|
struct InstantPageTextCategories {
|
||||||
@ -48,6 +49,7 @@ struct InstantPageTextCategories {
|
|||||||
let paragraph: InstantPageTextAttributes
|
let paragraph: InstantPageTextAttributes
|
||||||
let caption: InstantPageTextAttributes
|
let caption: InstantPageTextAttributes
|
||||||
let table: InstantPageTextAttributes
|
let table: InstantPageTextAttributes
|
||||||
|
let article: InstantPageTextAttributes
|
||||||
|
|
||||||
func attributes(type: InstantPageTextCategoryType, link: Bool) -> InstantPageTextAttributes {
|
func attributes(type: InstantPageTextCategoryType, link: Bool) -> InstantPageTextAttributes {
|
||||||
switch type {
|
switch type {
|
||||||
@ -63,11 +65,13 @@ struct InstantPageTextCategories {
|
|||||||
return self.caption.withUnderline(link)
|
return self.caption.withUnderline(link)
|
||||||
case .table:
|
case .table:
|
||||||
return self.table.withUnderline(link)
|
return self.table.withUnderline(link)
|
||||||
|
case .article:
|
||||||
|
return self.article.withUnderline(link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextCategories {
|
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),
|
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),
|
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)),
|
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,
|
serif: false,
|
||||||
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
|
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)),
|
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)),
|
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)),
|
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,
|
serif: false,
|
||||||
codeBlockBackgroundColor: UIColor(rgb: 0xefe7d6),
|
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)),
|
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)),
|
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)),
|
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,
|
serif: false,
|
||||||
codeBlockBackgroundColor: UIColor(rgb: 0x555556),
|
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)),
|
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)),
|
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)),
|
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,
|
serif: false,
|
||||||
codeBlockBackgroundColor: UIColor(rgb: 0x131313),
|
codeBlockBackgroundColor: UIColor(rgb: 0x131313),
|
||||||
|
|||||||
@ -14,16 +14,15 @@ final class InstantPageTile {
|
|||||||
for item in self.items {
|
for item in self.items {
|
||||||
item.drawInTile(context: context)
|
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)
|
context.translateBy(x: self.frame.minX, y: self.frame.minY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFloat) -> [InstantPageTile] {
|
func instantPageTilesFromLayout(_ layout: InstantPageLayout, boundingWidth: CGFloat) -> [InstantPageTile] {
|
||||||
var tileByOrigin: [Int: InstantPageTile] = [:]
|
var tileByOrigin: [Int : InstantPageTile] = [:]
|
||||||
let tileHeight: CGFloat = 256.0
|
let tileHeight: CGFloat = 256.0
|
||||||
|
|
||||||
|
var tileHoles: [CGRect] = []
|
||||||
for item in layout.items {
|
for item in layout.items {
|
||||||
if !item.wantsNode {
|
if !item.wantsNode {
|
||||||
let topTileIndex = max(0, Int(floor(item.frame.minY - 10.0) / tileHeight))
|
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)
|
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
|
return lhs.frame.minY < rhs.frame.minY
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AsyncDisplayKit
|
|||||||
final class InstantPageWebEmbedItem: InstantPageItem {
|
final class InstantPageWebEmbedItem: InstantPageItem {
|
||||||
var frame: CGRect
|
var frame: CGRect
|
||||||
let wantsNode: Bool = true
|
let wantsNode: Bool = true
|
||||||
|
let separatesTiles: Bool = true
|
||||||
let medias: [InstantPageMedia] = []
|
let medias: [InstantPageMedia] = []
|
||||||
|
|
||||||
let url: String?
|
let url: String?
|
||||||
@ -19,7 +20,7 @@ final class InstantPageWebEmbedItem: InstantPageItem {
|
|||||||
self.enableScrolling = enableScrolling
|
self.enableScrolling = enableScrolling
|
||||||
}
|
}
|
||||||
|
|
||||||
func node(account: Account, strings: PresentationStrings, theme: InstantPageTheme, openMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (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)
|
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 TelegramCore
|
||||||
import WebKit
|
import WebKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
|
||||||
private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler {
|
private class WeakInstantPageWebEmbedNodeMessageHandler: NSObject, WKScriptMessageHandler {
|
||||||
private let f: (WKScriptMessage) -> ()
|
private let f: (WKScriptMessage) -> ()
|
||||||
@ -119,6 +120,9 @@ final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode {
|
|||||||
func updateIsVisible(_ isVisible: Bool) {
|
func updateIsVisible(_ isVisible: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
}
|
||||||
|
|
||||||
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
func update(strings: PresentationStrings, theme: InstantPageTheme) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -181,7 +181,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let peerRevealOptions: [ItemListRevealOption]
|
let peerRevealOptions: [ItemListRevealOption]
|
||||||
if item.editable && item.enabled {
|
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 {
|
} else {
|
||||||
peerRevealOptions = []
|
peerRevealOptions = []
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,8 +125,9 @@ final class OngoingCallContext {
|
|||||||
private let audioSessionDisposable = MetaDisposable()
|
private let audioSessionDisposable = MetaDisposable()
|
||||||
private var networkTypeDisposable: Disposable?
|
private var networkTypeDisposable: Disposable?
|
||||||
|
|
||||||
init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, 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
|
let _ = setupLogs
|
||||||
|
OngoingCallThreadLocalContext.applyServerConfig(serializedData)
|
||||||
|
|
||||||
self.internalId = internalId
|
self.internalId = internalId
|
||||||
self.callSessionManager = callSessionManager
|
self.callSessionManager = callSessionManager
|
||||||
|
|||||||
@ -50,6 +50,7 @@ typedef NS_ENUM(int32_t, OngoingCallNetworkType) {
|
|||||||
@interface OngoingCallThreadLocalContext : NSObject
|
@interface OngoingCallThreadLocalContext : NSObject
|
||||||
|
|
||||||
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
|
||||||
|
+ (void)applyServerConfig:(NSString * _Nullable)data;
|
||||||
|
|
||||||
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState);
|
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallState);
|
||||||
@property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile);
|
@property (nonatomic, copy) void (^ _Nullable callEnded)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#import "OngoingCallThreadLocalContext.h"
|
#import "OngoingCallThreadLocalContext.h"
|
||||||
|
|
||||||
#import "../../libtgvoip/VoIPController.h"
|
#import "../../libtgvoip/VoIPController.h"
|
||||||
|
#import "../../libtgvoip/VoIPServerConfig.h"
|
||||||
#import "../../libtgvoip/os/darwin/SetupLogging.h"
|
#import "../../libtgvoip/os/darwin/SetupLogging.h"
|
||||||
|
|
||||||
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
|
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
|
||||||
@ -180,6 +181,36 @@ static int callControllerNetworkTypeForType(OngoingCallNetworkType type) {
|
|||||||
TGVoipLoggingFunction = loggingFunction;
|
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 {
|
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueue> _Nonnull)queue proxy:(VoipProxyServer * _Nullable)proxy networkType:(OngoingCallNetworkType)networkType {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
|
|||||||
@ -222,13 +222,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||||||
|
|
||||||
displayData = value.item.displayData
|
displayData = value.item.displayData
|
||||||
|
|
||||||
let baseColor: UIColor
|
let baseColor = strongSelf.theme.list.itemSecondaryTextColor
|
||||||
if strongSelf.theme.list.itemPrimaryTextColor.isEqual(strongSelf.theme.list.itemAccentColor) {
|
|
||||||
baseColor = strongSelf.theme.list.controlSecondaryColor
|
|
||||||
} else {
|
|
||||||
baseColor = strongSelf.theme.list.itemPrimaryTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.order != strongSelf.currentOrder {
|
if value.order != strongSelf.currentOrder {
|
||||||
strongSelf.updateOrder?(value.order)
|
strongSelf.updateOrder?(value.order)
|
||||||
strongSelf.currentOrder = value.order
|
strongSelf.currentOrder = value.order
|
||||||
|
|||||||
@ -218,7 +218,7 @@ public final class PresentationCall {
|
|||||||
private var droppedCall = false
|
private var droppedCall = false
|
||||||
private var dropCallKitCallTimer: SwiftSignalKit.Timer?
|
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.account = account
|
||||||
self.audioSession = audioSession
|
self.audioSession = audioSession
|
||||||
self.callSessionManager = callSessionManager
|
self.callSessionManager = callSessionManager
|
||||||
@ -230,7 +230,7 @@ public final class PresentationCall {
|
|||||||
self.isOutgoing = isOutgoing
|
self.isOutgoing = isOutgoing
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
|
||||||
self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, 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
|
var didReceiveAudioOutputs = false
|
||||||
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
|
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
|
||||||
|
|||||||
@ -4,19 +4,6 @@ import TelegramCore
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Display
|
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? {
|
private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, settings: VoiceCallSettings?) -> CallKitIntegration? {
|
||||||
let enabled = settings?.enableSystemIntegration ?? true
|
let enabled = settings?.enableSystemIntegration ?? true
|
||||||
return enabled ? integration : nil
|
return enabled ? integration : nil
|
||||||
@ -198,6 +185,25 @@ public final class PresentationCallManager {
|
|||||||
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.callSettings = (callSettings, configuration)
|
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) {
|
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) {
|
||||||
if let firstState = ringingStates.first {
|
if let firstState = ringingStates.first {
|
||||||
if self.currentCall == nil {
|
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.currentCall = call
|
||||||
self.currentCallPromise.set(.single(call))
|
self.currentCallPromise.set(.single(call))
|
||||||
self.hasActiveCallsPromise.set(true)
|
self.hasActiveCallsPromise.set(true)
|
||||||
@ -312,7 +318,7 @@ public final class PresentationCallManager {
|
|||||||
currentCall.rejectBusy()
|
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.currentCall = call
|
||||||
strongSelf.currentCallPromise.set(.single(call))
|
strongSelf.currentCallPromise.set(.single(call))
|
||||||
strongSelf.hasActiveCallsPromise.set(true)
|
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 currentSessionInfo(PresentationTheme, String)
|
||||||
case pendingSessionsHeader(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 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 otherSessionsHeader(PresentationTheme, String)
|
||||||
case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
|
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)
|
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 {
|
switch self {
|
||||||
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo:
|
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo:
|
||||||
return RecentSessionsSection.currentSession.rawValue
|
return RecentSessionsSection.currentSession.rawValue
|
||||||
case .pendingSessionsHeader, .pendingSession:
|
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
|
||||||
return RecentSessionsSection.pendingSessions.rawValue
|
return RecentSessionsSection.pendingSessions.rawValue
|
||||||
case .otherSessionsHeader, .session, .website:
|
case .otherSessionsHeader, .session, .website:
|
||||||
return RecentSessionsSection.otherSessions.rawValue
|
return RecentSessionsSection.otherSessions.rawValue
|
||||||
@ -106,8 +107,10 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
|||||||
return .index(5)
|
return .index(5)
|
||||||
case let .pendingSession(_, _, _, _, session, _, _, _):
|
case let .pendingSession(_, _, _, _, session, _, _, _):
|
||||||
return .session(session.hash)
|
return .session(session.hash)
|
||||||
case .otherSessionsHeader:
|
case .pendingSessionsInfo:
|
||||||
return .index(6)
|
return .index(6)
|
||||||
|
case .otherSessionsHeader:
|
||||||
|
return .index(7)
|
||||||
case let .session(_, _, _, _, session, _, _, _):
|
case let .session(_, _, _, _, session, _, _, _):
|
||||||
return .session(session.hash)
|
return .session(session.hash)
|
||||||
case let .website(_, _, _, _, website, _, _, _, _):
|
case let .website(_, _, _, _, website, _, _, _, _):
|
||||||
@ -153,6 +156,12 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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):
|
case let .otherSessionsHeader(lhsTheme, lhsText):
|
||||||
if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -244,14 +253,16 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
|
|||||||
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section)
|
||||||
case let .pendingSessionsHeader(theme, text):
|
case let .pendingSessionsHeader(theme, text):
|
||||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
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):
|
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
|
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)
|
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
||||||
}, removeSession: { id in
|
}, removeSession: { id in
|
||||||
arguments.removeSession(id)
|
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):
|
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
|
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)
|
arguments.setSessionIdWithRevealedOptions(previousId, id)
|
||||||
@ -339,14 +350,14 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
|
|||||||
|
|
||||||
let filteredPendingSessions: [RecentAccountSession] = sessions.filter({ $0.flags.contains(.passwordPending) })
|
let filteredPendingSessions: [RecentAccountSession] = sessions.filter({ $0.flags.contains(.passwordPending) })
|
||||||
if !filteredPendingSessions.isEmpty {
|
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 {
|
for i in 0 ..< filteredPendingSessions.count {
|
||||||
if !existingSessionIds.contains(filteredPendingSessions[i].hash) {
|
if !existingSessionIds.contains(filteredPendingSessions[i].hash) {
|
||||||
existingSessionIds.insert(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(.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))
|
entries.append(.otherSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_OtherSessions))
|
||||||
@ -425,38 +436,53 @@ public func recentSessionsController(account: Account) -> ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, removeSession: { sessionId in
|
}, removeSession: { sessionId in
|
||||||
updateState {
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
return $0.withUpdatedRemovingSessionId(sessionId)
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
let dismissAction: () -> Void = { [weak controller] in
|
||||||
|
controller?.dismissAnimated()
|
||||||
}
|
}
|
||||||
|
controller.setItemGroups([
|
||||||
let applySessions: Signal<Void, NoError> = sessionsPromise.get()
|
ActionSheetItemGroup(items: [
|
||||||
|> filter { $0 != nil }
|
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
|
||||||
|> take(1)
|
dismissAction()
|
||||||
|> deliverOnMainQueue
|
|
||||||
|> mapToSignal { sessions -> Signal<Void, NoError> in
|
updateState {
|
||||||
if let sessions = sessions {
|
return $0.withUpdatedRemovingSessionId(sessionId)
|
||||||
var updatedSessions = sessions
|
|
||||||
for i in 0 ..< updatedSessions.count {
|
|
||||||
if updatedSessions[i].hash == sessionId {
|
|
||||||
updatedSessions.remove(at: i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sessionsPromise.set(.single(updatedSessions))
|
|
||||||
}
|
let applySessions: Signal<Void, NoError> = sessionsPromise.get()
|
||||||
|
|> filter { $0 != nil }
|
||||||
return .complete()
|
|> take(1)
|
||||||
}
|
|> deliverOnMainQueue
|
||||||
|
|> mapToSignal { sessions -> Signal<Void, NoError> in
|
||||||
removeSessionDisposable.set((terminateAccountSession(account: account, hash: sessionId) |> then((applySessions |> mapError { _ in TerminateSessionError.generic })) |> deliverOnMainQueue).start(error: { _ in
|
if let sessions = sessions {
|
||||||
updateState {
|
var updatedSessions = sessions
|
||||||
return $0.withUpdatedRemovingSessionId(nil)
|
for i in 0 ..< updatedSessions.count {
|
||||||
}
|
if updatedSessions[i].hash == sessionId {
|
||||||
}, completed: {
|
updatedSessions.remove(at: i)
|
||||||
updateState {
|
break
|
||||||
return $0.withUpdatedRemovingSessionId(nil)
|
}
|
||||||
}
|
}
|
||||||
}))
|
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: {
|
}, terminateOtherSessions: {
|
||||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
let controller = ActionSheetController(presentationTheme: presentationData.theme)
|
||||||
|
|||||||
@ -569,7 +569,7 @@ struct SecureIdDocumentFormState: FormControllerInnerState {
|
|||||||
result.append(.entry(SecureIdDocumentFormEntry.expiryDate(document.expiryDate, expiryDateError)))
|
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
|
let type = identity.document?.type
|
||||||
|
|
||||||
if let last = result.last, case .spacer = last {
|
if let last = result.last, case .spacer = last {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ public enum VoiceCallDataSaving: Int32 {
|
|||||||
|
|
||||||
public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
||||||
public var dataSaving: VoiceCallDataSaving
|
public var dataSaving: VoiceCallDataSaving
|
||||||
public var p2pMode: VoiceCallP2PMode?
|
public var legacyP2PMode: VoiceCallP2PMode?
|
||||||
public var enableSystemIntegration: Bool
|
public var enableSystemIntegration: Bool
|
||||||
|
|
||||||
public static var defaultSettings: VoiceCallSettings {
|
public static var defaultSettings: VoiceCallSettings {
|
||||||
@ -20,23 +20,23 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
|||||||
|
|
||||||
init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode?, enableSystemIntegration: Bool) {
|
init(dataSaving: VoiceCallDataSaving, p2pMode: VoiceCallP2PMode?, enableSystemIntegration: Bool) {
|
||||||
self.dataSaving = dataSaving
|
self.dataSaving = dataSaving
|
||||||
self.p2pMode = p2pMode
|
self.legacyP2PMode = p2pMode
|
||||||
self.enableSystemIntegration = enableSystemIntegration
|
self.enableSystemIntegration = enableSystemIntegration
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))!
|
self.dataSaving = VoiceCallDataSaving(rawValue: decoder.decodeInt32ForKey("ds", orElse: 0))!
|
||||||
if let value = decoder.decodeOptionalInt32ForKey("p2pMode") {
|
if let value = decoder.decodeOptionalInt32ForKey("p2pMode") {
|
||||||
self.p2pMode = VoiceCallP2PMode(rawValue: value) ?? .contacts
|
self.legacyP2PMode = VoiceCallP2PMode(rawValue: value)
|
||||||
} else {
|
} else {
|
||||||
self.p2pMode = nil
|
self.legacyP2PMode = nil
|
||||||
}
|
}
|
||||||
self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0
|
self.enableSystemIntegration = decoder.decodeInt32ForKey("enableSystemIntegration", orElse: 1) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds")
|
encoder.encodeInt32(self.dataSaving.rawValue, forKey: "ds")
|
||||||
if let p2pMode = self.p2pMode {
|
if let p2pMode = self.legacyP2PMode {
|
||||||
encoder.encodeInt32(p2pMode.rawValue, forKey: "p2pMode")
|
encoder.encodeInt32(p2pMode.rawValue, forKey: "p2pMode")
|
||||||
} else {
|
} else {
|
||||||
encoder.encodeNil(forKey: "p2pMode")
|
encoder.encodeNil(forKey: "p2pMode")
|
||||||
@ -56,7 +56,7 @@ public struct VoiceCallSettings: PreferencesEntry, Equatable {
|
|||||||
if lhs.dataSaving != rhs.dataSaving {
|
if lhs.dataSaving != rhs.dataSaving {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.p2pMode != rhs.p2pMode {
|
if lhs.legacyP2PMode != rhs.legacyP2PMode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.enableSystemIntegration != rhs.enableSystemIntegration {
|
if lhs.enableSystemIntegration != rhs.enableSystemIntegration {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user