mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
9ea80ff9dc
@ -1091,6 +1091,127 @@ private struct DownloadItem: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func filteredPeerSearchQueryResults(value: ([FoundPeer], [FoundPeer]), scope: TelegramSearchPeersScope) -> ([FoundPeer], [FoundPeer]) {
|
||||||
|
switch scope {
|
||||||
|
case .everywhere:
|
||||||
|
return value
|
||||||
|
case .channels:
|
||||||
|
return (
|
||||||
|
value.0.filter { peer in
|
||||||
|
if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value.1.filter { peer in
|
||||||
|
if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class GlobalPeerSearchContext {
|
||||||
|
private struct SearchKey: Hashable {
|
||||||
|
var query: String
|
||||||
|
|
||||||
|
init(query: String) {
|
||||||
|
self.query = query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class QueryContext {
|
||||||
|
var value: ([FoundPeer], [FoundPeer])?
|
||||||
|
let subscribers = Bag<(TelegramSearchPeersScope, (([FoundPeer], [FoundPeer])) -> Void)>()
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Impl {
|
||||||
|
private let queue: Queue
|
||||||
|
private var queryContexts: [SearchKey: QueryContext] = [:]
|
||||||
|
|
||||||
|
init(queue: Queue) {
|
||||||
|
self.queue = queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchRemotePeers(engine: TelegramEngine, query: String, scope: TelegramSearchPeersScope, onNext: @escaping (([FoundPeer], [FoundPeer])) -> Void) -> Disposable {
|
||||||
|
let searchKey = SearchKey(query: query)
|
||||||
|
let queryContext: QueryContext
|
||||||
|
if let current = self.queryContexts[searchKey] {
|
||||||
|
queryContext = current
|
||||||
|
|
||||||
|
if let value = queryContext.value {
|
||||||
|
onNext(filteredPeerSearchQueryResults(value: value, scope: scope))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryContext = QueryContext()
|
||||||
|
self.queryContexts[searchKey] = queryContext
|
||||||
|
queryContext.disposable.set((engine.contacts.searchRemotePeers(
|
||||||
|
query: query,
|
||||||
|
scope: .everywhere
|
||||||
|
)
|
||||||
|
|> delay(0.4, queue: Queue.mainQueue())
|
||||||
|
|> deliverOn(self.queue)).start(next: { [weak queryContext] value in
|
||||||
|
guard let queryContext else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
queryContext.value = value
|
||||||
|
for (scope, f) in queryContext.subscribers.copyItems() {
|
||||||
|
f(filteredPeerSearchQueryResults(value: value, scope: scope))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = queryContext.subscribers.add((scope, onNext))
|
||||||
|
|
||||||
|
let queue = self.queue
|
||||||
|
return ActionDisposable { [weak self, weak queryContext] in
|
||||||
|
queue.async {
|
||||||
|
guard let self, let queryContext else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let currentContext = self.queryContexts[searchKey], queryContext === queryContext else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentContext.subscribers.remove(index)
|
||||||
|
if currentContext.subscribers.isEmpty {
|
||||||
|
currentContext.disposable.dispose()
|
||||||
|
self.queryContexts.removeValue(forKey: searchKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let queue: Queue
|
||||||
|
private let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let queue = Queue.mainQueue()
|
||||||
|
self.queue = queue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return Impl(queue: queue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchRemotePeers(engine: TelegramEngine, query: String, scope: TelegramSearchPeersScope = .everywhere) -> Signal<([FoundPeer], [FoundPeer]), NoError> {
|
||||||
|
return self.impl.signalWith { impl, subscriber in
|
||||||
|
return impl.searchRemotePeers(engine: engine, query: query, scope: scope, onNext: subscriber.putNext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let animationCache: AnimationCache
|
private let animationCache: AnimationCache
|
||||||
@ -1099,6 +1220,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
private let peersFilter: ChatListNodePeersFilter
|
private let peersFilter: ChatListNodePeersFilter
|
||||||
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
|
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
|
private let globalPeerSearchContext: GlobalPeerSearchContext?
|
||||||
private let key: ChatListSearchPaneKey
|
private let key: ChatListSearchPaneKey
|
||||||
private let tagMask: EngineMessage.Tags?
|
private let tagMask: EngineMessage.Tags?
|
||||||
private let location: ChatListControllerLocation
|
private let location: ChatListControllerLocation
|
||||||
@ -1175,7 +1297,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
private var searchQueryDisposable: Disposable?
|
private var searchQueryDisposable: Disposable?
|
||||||
private var searchOptionsDisposable: Disposable?
|
private var searchOptionsDisposable: Disposable?
|
||||||
|
|
||||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?) {
|
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, searchQuery: Signal<String?, NoError>, searchOptions: Signal<ChatListSearchOptions?, NoError>, navigationController: NavigationController?, globalPeerSearchContext: GlobalPeerSearchContext?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
self.animationRenderer = animationRenderer
|
self.animationRenderer = animationRenderer
|
||||||
@ -1184,6 +1306,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
self.location = location
|
self.location = location
|
||||||
self.navigationController = navigationController
|
self.navigationController = navigationController
|
||||||
|
|
||||||
|
let globalPeerSearchContext = globalPeerSearchContext ?? GlobalPeerSearchContext()
|
||||||
|
|
||||||
|
self.globalPeerSearchContext = globalPeerSearchContext
|
||||||
|
|
||||||
var peersFilter = peersFilter
|
var peersFilter = peersFilter
|
||||||
if case .forum = location {
|
if case .forum = location {
|
||||||
//peersFilter.insert(.excludeRecent)
|
//peersFilter.insert(.excludeRecent)
|
||||||
@ -1788,18 +1914,16 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
foundRemotePeers = (
|
foundRemotePeers = (
|
||||||
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true))
|
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true))
|
||||||
|> then(
|
|> then(
|
||||||
context.engine.contacts.searchRemotePeers(query: query)
|
globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query)
|
||||||
|> map { ($0.0, $0.1, false) }
|
|> map { ($0.0, $0.1, false) }
|
||||||
|> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else if let query = query, case .channels = key {
|
} else if let query = query, case .channels = key {
|
||||||
foundRemotePeers = (
|
foundRemotePeers = (
|
||||||
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true))
|
.single((currentRemotePeersValue.0, currentRemotePeersValue.1, true))
|
||||||
|> then(
|
|> then(
|
||||||
context.engine.contacts.searchRemotePeers(query: query, scope: .channels)
|
globalPeerSearchContext.searchRemotePeers(engine: context.engine, query: query, scope: .channels)
|
||||||
|> map { ($0.0, $0.1, false) }
|
|> map { ($0.0, $0.1, false) }
|
||||||
|> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -127,10 +127,11 @@ private final class ChatListSearchPendingPane {
|
|||||||
location: ChatListControllerLocation,
|
location: ChatListControllerLocation,
|
||||||
searchQuery: Signal<String?, NoError>,
|
searchQuery: Signal<String?, NoError>,
|
||||||
searchOptions: Signal<ChatListSearchOptions?, NoError>,
|
searchOptions: Signal<ChatListSearchOptions?, NoError>,
|
||||||
|
globalPeerSearchContext: GlobalPeerSearchContext?,
|
||||||
key: ChatListSearchPaneKey,
|
key: ChatListSearchPaneKey,
|
||||||
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void
|
||||||
) {
|
) {
|
||||||
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: (key == .chats || key == .topics) ? peersFilter : [], requestPeerType: requestPeerType, location: location, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController)
|
let paneNode = ChatListSearchListPaneNode(context: context, animationCache: animationCache, animationRenderer: animationRenderer, updatedPresentationData: updatedPresentationData, interaction: interaction, key: key, peersFilter: (key == .chats || key == .topics) ? peersFilter : [], requestPeerType: requestPeerType, location: location, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController, globalPeerSearchContext: globalPeerSearchContext)
|
||||||
|
|
||||||
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode)
|
||||||
self.disposable = (paneNode.isReady
|
self.disposable = (paneNode.isReady
|
||||||
@ -156,6 +157,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||||||
private let location: ChatListControllerLocation
|
private let location: ChatListControllerLocation
|
||||||
private let searchQuery: Signal<String?, NoError>
|
private let searchQuery: Signal<String?, NoError>
|
||||||
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
|
private let searchOptions: Signal<ChatListSearchOptions?, NoError>
|
||||||
|
private let globalPeerSearchContext: GlobalPeerSearchContext
|
||||||
private let navigationController: NavigationController?
|
private let navigationController: NavigationController?
|
||||||
var interaction: ChatListSearchInteraction?
|
var interaction: ChatListSearchInteraction?
|
||||||
|
|
||||||
@ -198,6 +200,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||||||
self.searchQuery = searchQuery
|
self.searchQuery = searchQuery
|
||||||
self.searchOptions = searchOptions
|
self.searchOptions = searchOptions
|
||||||
self.navigationController = navigationController
|
self.navigationController = navigationController
|
||||||
|
self.globalPeerSearchContext = GlobalPeerSearchContext()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -432,6 +435,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD
|
|||||||
location: self.location,
|
location: self.location,
|
||||||
searchQuery: self.searchQuery,
|
searchQuery: self.searchQuery,
|
||||||
searchOptions: self.searchOptions,
|
searchOptions: self.searchOptions,
|
||||||
|
globalPeerSearchContext: self.globalPeerSearchContext,
|
||||||
key: key,
|
key: key,
|
||||||
hasBecomeReady: { [weak self] key in
|
hasBecomeReady: { [weak self] key in
|
||||||
let apply: () -> Void = {
|
let apply: () -> Void = {
|
||||||
|
@ -474,7 +474,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(revealed: Bool, animated: Bool = true) {
|
public func update(revealed: Bool, animated: Bool = true) {
|
||||||
guard self.isRevealed != revealed, let textNode = self.textNode else {
|
guard self.isRevealed != revealed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,11 +483,15 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
if revealed {
|
if revealed {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
||||||
transition.updateAlpha(node: self, alpha: 0.0)
|
transition.updateAlpha(node: self, alpha: 0.0)
|
||||||
|
if let textNode = self.textNode {
|
||||||
transition.updateAlpha(node: textNode, alpha: 1.0)
|
transition.updateAlpha(node: textNode, alpha: 1.0)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .linear) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .linear) : .immediate
|
||||||
transition.updateAlpha(node: self, alpha: 1.0)
|
transition.updateAlpha(node: self, alpha: 1.0)
|
||||||
|
if let textNode = self.textNode {
|
||||||
transition.updateAlpha(node: textNode, alpha: 0.0)
|
transition.updateAlpha(node: textNode, alpha: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
if self.isExploding {
|
if self.isExploding {
|
||||||
self.isExploding = false
|
self.isExploding = false
|
||||||
@ -497,7 +501,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func revealAtLocation(_ location: CGPoint) {
|
public func revealAtLocation(_ location: CGPoint) {
|
||||||
guard let (_, _, textColor, _, _) = self.currentParams, let textNode = self.textNode, !self.isRevealed else {
|
guard let (_, _, textColor, _, _) = self.currentParams, !self.isRevealed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,7 +511,7 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
self.isExploding = true
|
self.isExploding = true
|
||||||
|
|
||||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
self.emitterLayer?.setValue(location, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||||
|
|
||||||
let maskSize = self.emitterNode.frame.size
|
let maskSize = self.emitterNode.frame.size
|
||||||
Queue.concurrentDefaultQueue().async {
|
Queue.concurrentDefaultQueue().async {
|
||||||
@ -520,10 +524,15 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) { [weak self] in
|
||||||
textNode.alpha = 1.0
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let textNode = self.textNode {
|
||||||
|
textNode.alpha = 1.0
|
||||||
textNode.view.mask = self.textMaskNode.view
|
textNode.view.mask = self.textMaskNode.view
|
||||||
|
}
|
||||||
self.textSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
self.textSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||||
|
|
||||||
let xFactor = (location.x / self.emitterNode.frame.width - 0.5) * 2.0
|
let xFactor = (location.x / self.emitterNode.frame.width - 0.5) * 2.0
|
||||||
@ -539,8 +548,13 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.textSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height)
|
self.textSpotNode.layer.anchorPoint = CGPoint(x: location.x / self.emitterMaskNode.frame.width, y: location.y / self.emitterMaskNode.frame.height)
|
||||||
self.textSpotNode.position = location
|
self.textSpotNode.position = location
|
||||||
self.textSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { _ in
|
self.textSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let textNode = self.textNode {
|
||||||
textNode.view.mask = nil
|
textNode.view.mask = nil
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
|
||||||
@ -567,8 +581,10 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
|||||||
self.emitterMaskFillNode.layer.removeAllAnimations()
|
self.emitterMaskFillNode.layer.removeAllAnimations()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if let textNode = self.textNode {
|
||||||
textNode.alpha = 1.0
|
textNode.alpha = 1.0
|
||||||
textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
}
|
||||||
|
|
||||||
self.staticNode?.alpha = 0.0
|
self.staticNode?.alpha = 0.0
|
||||||
self.staticNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
self.staticNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||||
|
@ -111,7 +111,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
private var expandedBlockIds: Set<Int> = Set()
|
private var expandedBlockIds: Set<Int> = Set()
|
||||||
private var appliedExpandedBlockIds: Set<Int>?
|
private var appliedExpandedBlockIds: Set<Int>?
|
||||||
private var displayContentsUnderSpoilers: Bool = false
|
private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil)
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
@ -162,11 +162,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
||||||
}
|
}
|
||||||
self.textNode.textNode.requestDisplayContentsUnderSpoilers = { [weak self] in
|
self.textNode.textNode.requestDisplayContentsUnderSpoilers = { [weak self] location in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.updateDisplayContentsUnderSpoilers(value: true)
|
var mappedLocation: CGPoint?
|
||||||
|
if let location {
|
||||||
|
mappedLocation = self.textNode.textNode.layer.convert(location, to: self.layer)
|
||||||
|
}
|
||||||
|
self.updateDisplayContentsUnderSpoilers(value: true, at: mappedLocation)
|
||||||
}
|
}
|
||||||
self.textNode.textNode.canHandleTapAtPoint = { [weak self] point in
|
self.textNode.textNode.canHandleTapAtPoint = { [weak self] point in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -586,7 +590,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
cutout: nil,
|
cutout: nil,
|
||||||
insets: textInsets,
|
insets: textInsets,
|
||||||
lineColor: messageTheme.accentControlColor,
|
lineColor: messageTheme.accentControlColor,
|
||||||
displayContentsUnderSpoilers: displayContentsUnderSpoilers,
|
displayContentsUnderSpoilers: displayContentsUnderSpoilers.value,
|
||||||
customTruncationToken: customTruncationToken,
|
customTruncationToken: customTruncationToken,
|
||||||
expandedBlocks: expandedBlockIds
|
expandedBlocks: expandedBlockIds
|
||||||
))
|
))
|
||||||
@ -677,6 +681,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
strongSelf.appliedExpandedBlockIds = strongSelf.expandedBlockIds
|
strongSelf.appliedExpandedBlockIds = strongSelf.expandedBlockIds
|
||||||
|
|
||||||
|
var spoilerExpandRect: CGRect?
|
||||||
|
if let location = strongSelf.displayContentsUnderSpoilers.location {
|
||||||
|
spoilerExpandRect = textFrame.size.centered(around: CGPoint(x: location.x - textFrame.minX, y: location.y - textFrame.minY))
|
||||||
|
}
|
||||||
|
|
||||||
let _ = textApply(InteractiveTextNodeWithEntities.Arguments(
|
let _ = textApply(InteractiveTextNodeWithEntities.Arguments(
|
||||||
context: item.context,
|
context: item.context,
|
||||||
cache: item.controllerInteraction.presentationContext.animationCache,
|
cache: item.controllerInteraction.presentationContext.animationCache,
|
||||||
@ -685,7 +694,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
attemptSynchronous: synchronousLoads,
|
attemptSynchronous: synchronousLoads,
|
||||||
textColor: messageTheme.primaryTextColor,
|
textColor: messageTheme.primaryTextColor,
|
||||||
spoilerEffectColor: messageTheme.secondaryTextColor,
|
spoilerEffectColor: messageTheme.secondaryTextColor,
|
||||||
animation: animation
|
animation: animation,
|
||||||
|
animationArguments: InteractiveTextNode.AnimationArguments(
|
||||||
|
spoilerExpandRect: spoilerExpandRect
|
||||||
|
)
|
||||||
))
|
))
|
||||||
animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil)
|
||||||
|
|
||||||
@ -853,7 +865,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
let textNodeFrame = self.textNode.textNode.frame
|
let textNodeFrame = self.textNode.textNode.frame
|
||||||
let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)
|
let textLocalPoint = CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)
|
||||||
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) {
|
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(textLocalPoint) {
|
||||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers {
|
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)], !self.displayContentsUnderSpoilers.value {
|
||||||
return ChatMessageBubbleContentTapAction(content: .none)
|
return ChatMessageBubbleContentTapAction(content: .none)
|
||||||
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
} else if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
@ -1045,7 +1057,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !self.displayContentsUnderSpoilers {
|
if let spoilerRects = spoilerRects, !spoilerRects.isEmpty, !self.displayContentsUnderSpoilers.value {
|
||||||
} else if let rects = rects {
|
} else if let rects = rects {
|
||||||
let linkHighlightingNode: LinkHighlightingNode
|
let linkHighlightingNode: LinkHighlightingNode
|
||||||
if let current = self.linkHighlightingNode {
|
if let current = self.linkHighlightingNode {
|
||||||
@ -1315,11 +1327,11 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strongSelf.displayContentsUnderSpoilers, let textLayout = strongSelf.textNode.textNode.cachedLayout, textLayout.segments.contains(where: { !$0.spoilers.isEmpty }), let selectionRange {
|
if !strongSelf.displayContentsUnderSpoilers.value, let textLayout = strongSelf.textNode.textNode.cachedLayout, textLayout.segments.contains(where: { !$0.spoilers.isEmpty }), let selectionRange {
|
||||||
for segment in textLayout.segments {
|
for segment in textLayout.segments {
|
||||||
for (spoilerRange, _) in segment.spoilers {
|
for (spoilerRange, _) in segment.spoilers {
|
||||||
if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 {
|
if let intersection = selectionRange.intersection(spoilerRange), intersection.length > 0 {
|
||||||
strongSelf.updateDisplayContentsUnderSpoilers(value: true)
|
strongSelf.updateDisplayContentsUnderSpoilers(value: true, at: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1375,17 +1387,18 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.displayContentsUnderSpoilers {
|
if self.displayContentsUnderSpoilers.value {
|
||||||
self.updateDisplayContentsUnderSpoilers(value: false)
|
self.updateDisplayContentsUnderSpoilers(value: false, at: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateDisplayContentsUnderSpoilers(value: Bool) {
|
private func updateDisplayContentsUnderSpoilers(value: Bool, at location: CGPoint?) {
|
||||||
if self.displayContentsUnderSpoilers == value {
|
if self.displayContentsUnderSpoilers.value == value {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.displayContentsUnderSpoilers = value
|
self.displayContentsUnderSpoilers = (value, location)
|
||||||
|
self.displayContentsUnderSpoilers.location = nil
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
||||||
}
|
}
|
||||||
|
@ -769,7 +769,8 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
|||||||
let messagesContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
let messagesContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
|
||||||
|
|
||||||
self.messagesContainer.frame = messagesContainerFrame
|
self.messagesContainer.frame = messagesContainerFrame
|
||||||
return messagesContainerFrame.size
|
// 4.0 is a magic number to compensate for offset in other types of content
|
||||||
|
return CGSize(width: messagesContainerFrame.width, height: messagesContainerFrame.height - 4.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1073,6 +1073,14 @@ private func addAttachment(attachment: UIImage, line: InteractiveTextNodeLine, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecognizerDelegate {
|
open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecognizerDelegate {
|
||||||
|
public final class AnimationArguments {
|
||||||
|
public let spoilerExpandRect: CGRect?
|
||||||
|
|
||||||
|
public init(spoilerExpandRect: CGRect?) {
|
||||||
|
self.spoilerExpandRect = spoilerExpandRect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct RenderContentTypes: OptionSet {
|
public struct RenderContentTypes: OptionSet {
|
||||||
public var rawValue: Int
|
public var rawValue: Int
|
||||||
|
|
||||||
@ -1106,7 +1114,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
|
|
||||||
public var canHandleTapAtPoint: ((CGPoint) -> Bool)?
|
public var canHandleTapAtPoint: ((CGPoint) -> Bool)?
|
||||||
public var requestToggleBlockCollapsed: ((Int) -> Void)?
|
public var requestToggleBlockCollapsed: ((Int) -> Void)?
|
||||||
public var requestDisplayContentsUnderSpoilers: (() -> Void)?
|
public var requestDisplayContentsUnderSpoilers: ((CGPoint?) -> Void)?
|
||||||
private var tapRecognizer: UITapGestureRecognizer?
|
private var tapRecognizer: UITapGestureRecognizer?
|
||||||
|
|
||||||
public var currentText: NSAttributedString? {
|
public var currentText: NSAttributedString? {
|
||||||
@ -1676,7 +1684,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks)
|
return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateContentItems(animation: ListViewItemUpdateAnimation) {
|
private func updateContentItems(animation: ListViewItemUpdateAnimation, animationArguments: AnimationArguments?) {
|
||||||
guard let cachedLayout = self.cachedLayout else {
|
guard let cachedLayout = self.cachedLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1729,8 +1737,15 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
|
|
||||||
var contentItemAnimation = animation
|
var contentItemAnimation = animation
|
||||||
let contentItemLayer: TextContentItemLayer
|
let contentItemLayer: TextContentItemLayer
|
||||||
|
var itemSpoilerExpandRect: CGRect?
|
||||||
|
var itemAnimateContents = animateContents && contentItemAnimation.isAnimated
|
||||||
if let current = self.contentItemLayers[itemId] {
|
if let current = self.contentItemLayers[itemId] {
|
||||||
contentItemLayer = current
|
contentItemLayer = current
|
||||||
|
|
||||||
|
if animation.isAnimated, let spoilerExpandRect = animationArguments?.spoilerExpandRect {
|
||||||
|
itemSpoilerExpandRect = spoilerExpandRect.offsetBy(dx: -contentItemFrame.minX, dy: -contentItemFrame.minY)
|
||||||
|
itemAnimateContents = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
contentItemAnimation = .None
|
contentItemAnimation = .None
|
||||||
contentItemLayer = TextContentItemLayer()
|
contentItemLayer = TextContentItemLayer()
|
||||||
@ -1738,7 +1753,13 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
self.layer.addSublayer(contentItemLayer)
|
self.layer.addSublayer(contentItemLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItemLayer.update(item: contentItem, animation: contentItemAnimation, synchronously: synchronous, animateContents: animateContents && contentItemAnimation.isAnimated)
|
contentItemLayer.update(
|
||||||
|
item: contentItem,
|
||||||
|
animation: contentItemAnimation,
|
||||||
|
synchronously: synchronous,
|
||||||
|
animateContents: itemAnimateContents,
|
||||||
|
spoilerExpandRect: itemSpoilerExpandRect
|
||||||
|
)
|
||||||
|
|
||||||
contentItemAnimation.animator.updateFrame(layer: contentItemLayer, frame: contentItemFrame, completion: nil)
|
contentItemAnimation.animator.updateFrame(layer: contentItemLayer, frame: contentItemFrame, completion: nil)
|
||||||
}
|
}
|
||||||
@ -1779,7 +1800,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
let point = recognizer.location(in: self.view)
|
let point = recognizer.location(in: self.view)
|
||||||
if let cachedLayout = self.cachedLayout, !cachedLayout.displayContentsUnderSpoilers, let (_, attributes) = self.attributesAtPoint(point) {
|
if let cachedLayout = self.cachedLayout, !cachedLayout.displayContentsUnderSpoilers, let (_, attributes) = self.attributesAtPoint(point) {
|
||||||
if attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil {
|
if attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil {
|
||||||
self.requestDisplayContentsUnderSpoilers?()
|
self.requestDisplayContentsUnderSpoilers?(point)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1789,7 +1810,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func asyncLayout(_ maybeNode: InteractiveTextNode?) -> (InteractiveTextNodeLayoutArguments) -> (InteractiveTextNodeLayout, (ListViewItemUpdateAnimation) -> InteractiveTextNode) {
|
public static func asyncLayout(_ maybeNode: InteractiveTextNode?) -> (InteractiveTextNodeLayoutArguments) -> (InteractiveTextNodeLayout, (ListViewItemUpdateAnimation, AnimationArguments?) -> InteractiveTextNode) {
|
||||||
let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout
|
let existingLayout: InteractiveTextNodeLayout? = maybeNode?.cachedLayout
|
||||||
|
|
||||||
return { arguments in
|
return { arguments in
|
||||||
@ -1831,10 +1852,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
|
|
||||||
let node = maybeNode ?? InteractiveTextNode()
|
let node = maybeNode ?? InteractiveTextNode()
|
||||||
|
|
||||||
return (layout, { animation in
|
return (layout, { animation, animationArguments in
|
||||||
if node.cachedLayout !== layout {
|
if node.cachedLayout !== layout {
|
||||||
node.cachedLayout = layout
|
node.cachedLayout = layout
|
||||||
node.updateContentItems(animation: animation)
|
node.updateContentItems(animation: animation, animationArguments: animationArguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
return node
|
return node
|
||||||
@ -2202,7 +2223,13 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(item: TextContentItem, animation: ListViewItemUpdateAnimation, synchronously: Bool = false, animateContents: Bool = false) {
|
func update(
|
||||||
|
item: TextContentItem,
|
||||||
|
animation: ListViewItemUpdateAnimation,
|
||||||
|
synchronously: Bool,
|
||||||
|
animateContents: Bool,
|
||||||
|
spoilerExpandRect: CGRect?
|
||||||
|
) {
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
let contentFrame = CGRect(origin: CGPoint(), size: item.size)
|
let contentFrame = CGRect(origin: CGPoint(), size: item.size)
|
||||||
@ -2241,7 +2268,13 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.isAnimating = false
|
self.isAnimating = false
|
||||||
self.update(item: item, animation: .None, synchronously: true)
|
self.update(
|
||||||
|
item: item,
|
||||||
|
animation: .None,
|
||||||
|
synchronously: true,
|
||||||
|
animateContents: false,
|
||||||
|
spoilerExpandRect: nil
|
||||||
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
blockBackgroundView.layer.frame = blockBackgroundFrame
|
blockBackgroundView.layer.frame = blockBackgroundFrame
|
||||||
@ -2362,13 +2395,61 @@ final class TextContentItemLayer: SimpleLayer {
|
|||||||
|
|
||||||
self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask)
|
self.renderNode.params = RenderParams(size: contentFrame.size, item: item, mask: staticContentMask)
|
||||||
if synchronously {
|
if synchronously {
|
||||||
|
if let spoilerExpandRect {
|
||||||
|
let _ = spoilerExpandRect
|
||||||
|
|
||||||
|
self.renderNode.displayImmediately()
|
||||||
|
|
||||||
|
let maskFrame = self.renderNode.frame
|
||||||
|
|
||||||
|
let maskLayer = SimpleLayer()
|
||||||
|
maskLayer.frame = maskFrame
|
||||||
|
self.addSublayer(maskLayer)
|
||||||
|
|
||||||
|
let maskGradientLayer = SimpleGradientLayer()
|
||||||
|
maskGradientLayer.frame = CGRect(origin: CGPoint(), size: maskFrame.size)
|
||||||
|
setupSpoilerExpansionMaskGradient(
|
||||||
|
gradientLayer: maskGradientLayer,
|
||||||
|
centerLocation: CGPoint(
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5
|
||||||
|
),
|
||||||
|
radius: CGSize(
|
||||||
|
width: 1.5,
|
||||||
|
height: 1.5
|
||||||
|
),
|
||||||
|
inverse: false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
let previousContents = self.renderNode.layer.contents
|
let previousContents = self.renderNode.layer.contents
|
||||||
self.renderNode.displayImmediately()
|
self.renderNode.displayImmediately()
|
||||||
if animateContents, let previousContents {
|
if animateContents, let previousContents {
|
||||||
animation.transition.animateContents(layer: self.renderNode.layer, from: previousContents)
|
animation.transition.animateContents(layer: self.renderNode.layer, from: previousContents)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.renderNode.setNeedsDisplay()
|
self.renderNode.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupSpoilerExpansionMaskGradient(gradientLayer: SimpleGradientLayer, centerLocation: CGPoint, radius: CGSize, inverse: Bool) {
|
||||||
|
let startAlpha: CGFloat = inverse ? 0.0 : 1.0
|
||||||
|
let endAlpha: CGFloat = inverse ? 1.0 : 0.0
|
||||||
|
|
||||||
|
let locations: [CGFloat] = [0.0, 0.7, 0.95, 1.0]
|
||||||
|
let colors: [CGColor] = [
|
||||||
|
UIColor(rgb: 0xff0000, alpha: startAlpha).cgColor,
|
||||||
|
UIColor(rgb: 0xff0000, alpha: startAlpha).cgColor,
|
||||||
|
UIColor(rgb: 0xff0000, alpha: endAlpha).cgColor,
|
||||||
|
UIColor(rgb: 0xff0000, alpha: endAlpha).cgColor
|
||||||
|
]
|
||||||
|
|
||||||
|
gradientLayer.type = .radial
|
||||||
|
gradientLayer.colors = colors
|
||||||
|
gradientLayer.locations = locations.map { $0 as NSNumber }
|
||||||
|
gradientLayer.startPoint = centerLocation
|
||||||
|
|
||||||
|
let endEndPoint = CGPoint(x: (gradientLayer.startPoint.x + radius.width) * 1.0, y: (gradientLayer.startPoint.y + radius.height) * 1.0)
|
||||||
|
gradientLayer.endPoint = endEndPoint
|
||||||
|
}
|
||||||
|
@ -65,6 +65,7 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
public let textColor: UIColor
|
public let textColor: UIColor
|
||||||
public let spoilerEffectColor: UIColor
|
public let spoilerEffectColor: UIColor
|
||||||
public let animation: ListViewItemUpdateAnimation
|
public let animation: ListViewItemUpdateAnimation
|
||||||
|
public let animationArguments: InteractiveTextNode.AnimationArguments?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -74,7 +75,8 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
attemptSynchronous: Bool,
|
attemptSynchronous: Bool,
|
||||||
textColor: UIColor,
|
textColor: UIColor,
|
||||||
spoilerEffectColor: UIColor,
|
spoilerEffectColor: UIColor,
|
||||||
animation: ListViewItemUpdateAnimation
|
animation: ListViewItemUpdateAnimation,
|
||||||
|
animationArguments: InteractiveTextNode.AnimationArguments?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
@ -84,6 +86,7 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
self.spoilerEffectColor = spoilerEffectColor
|
self.spoilerEffectColor = spoilerEffectColor
|
||||||
self.animation = animation
|
self.animation = animation
|
||||||
|
self.animationArguments = animationArguments
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
||||||
@ -95,7 +98,8 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
attemptSynchronous: self.attemptSynchronous,
|
attemptSynchronous: self.attemptSynchronous,
|
||||||
textColor: self.textColor,
|
textColor: self.textColor,
|
||||||
spoilerEffectColor: self.spoilerEffectColor,
|
spoilerEffectColor: self.spoilerEffectColor,
|
||||||
animation: self.animation
|
animation: self.animation,
|
||||||
|
animationArguments: self.animationArguments
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,6 +117,7 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
|
|
||||||
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayerData] = [:]
|
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayerData] = [:]
|
||||||
private var dustEffectNodes: [Int: InvisibleInkDustNode] = [:]
|
private var dustEffectNodes: [Int: InvisibleInkDustNode] = [:]
|
||||||
|
private var displayContentsUnderSpoilers: Bool?
|
||||||
|
|
||||||
private var enableLooping: Bool = true
|
private var enableLooping: Bool = true
|
||||||
|
|
||||||
@ -215,11 +220,22 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
return (layout, { applyArguments in
|
return (layout, { applyArguments in
|
||||||
let animation: ListViewItemUpdateAnimation = applyArguments?.animation ?? .None
|
let animation: ListViewItemUpdateAnimation = applyArguments?.animation ?? .None
|
||||||
|
|
||||||
let result = apply(animation)
|
let result = apply(animation, applyArguments?.animationArguments)
|
||||||
|
|
||||||
if let maybeNode = maybeNode {
|
if let maybeNode = maybeNode {
|
||||||
if let applyArguments = applyArguments {
|
if let applyArguments = applyArguments {
|
||||||
maybeNode.updateInteractiveContents(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, textColor: applyArguments.textColor, spoilerEffectColor: applyArguments.spoilerEffectColor, animation: animation)
|
maybeNode.updateInteractiveContents(
|
||||||
|
context: applyArguments.context,
|
||||||
|
cache: applyArguments.cache,
|
||||||
|
renderer: applyArguments.renderer,
|
||||||
|
textLayout: layout,
|
||||||
|
placeholderColor: applyArguments.placeholderColor,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
textColor: applyArguments.textColor,
|
||||||
|
spoilerEffectColor: applyArguments.spoilerEffectColor,
|
||||||
|
animation: animation,
|
||||||
|
animationArguments: applyArguments.animationArguments
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return maybeNode
|
return maybeNode
|
||||||
@ -227,7 +243,18 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
let resultNode = InteractiveTextNodeWithEntities(textNode: result)
|
let resultNode = InteractiveTextNodeWithEntities(textNode: result)
|
||||||
|
|
||||||
if let applyArguments = applyArguments {
|
if let applyArguments = applyArguments {
|
||||||
resultNode.updateInteractiveContents(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, textColor: applyArguments.textColor, spoilerEffectColor: applyArguments.spoilerEffectColor, animation: .None)
|
resultNode.updateInteractiveContents(
|
||||||
|
context: applyArguments.context,
|
||||||
|
cache: applyArguments.cache,
|
||||||
|
renderer: applyArguments.renderer,
|
||||||
|
textLayout: layout,
|
||||||
|
placeholderColor: applyArguments.placeholderColor,
|
||||||
|
attemptSynchronousLoad: false,
|
||||||
|
textColor: applyArguments.textColor,
|
||||||
|
spoilerEffectColor: applyArguments.spoilerEffectColor,
|
||||||
|
animation: .None,
|
||||||
|
animationArguments: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultNode
|
return resultNode
|
||||||
@ -253,7 +280,8 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
attemptSynchronousLoad: Bool,
|
attemptSynchronousLoad: Bool,
|
||||||
textColor: UIColor,
|
textColor: UIColor,
|
||||||
spoilerEffectColor: UIColor,
|
spoilerEffectColor: UIColor,
|
||||||
animation: ListViewItemUpdateAnimation
|
animation: ListViewItemUpdateAnimation,
|
||||||
|
animationArguments: InteractiveTextNode.AnimationArguments?
|
||||||
) {
|
) {
|
||||||
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||||
|
|
||||||
@ -261,6 +289,8 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
if let textLayout {
|
if let textLayout {
|
||||||
displayContentsUnderSpoilers = textLayout.displayContentsUnderSpoilers
|
displayContentsUnderSpoilers = textLayout.displayContentsUnderSpoilers
|
||||||
}
|
}
|
||||||
|
let previousDisplayContentsUnderSpoilers = self.displayContentsUnderSpoilers
|
||||||
|
self.displayContentsUnderSpoilers = displayContentsUnderSpoilers
|
||||||
|
|
||||||
var nextIndexById: [Int64: Int] = [:]
|
var nextIndexById: [Int64: Int] = [:]
|
||||||
var validIds: [InlineStickerItemLayer.Key] = []
|
var validIds: [InlineStickerItemLayer.Key] = []
|
||||||
@ -345,7 +375,12 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
wordRects: segment.spoilerWords.map { $0.1.offsetBy(dx: segmentItem.contentOffset.x + 3.0, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) }
|
wordRects: segment.spoilerWords.map { $0.1.offsetBy(dx: segmentItem.contentOffset.x + 3.0, dy: segmentItem.contentOffset.y + 3.0).insetBy(dx: 1.0, dy: 1.0) }
|
||||||
)
|
)
|
||||||
|
|
||||||
animation.transition.updateAlpha(node: dustEffectNode, alpha: displayContentsUnderSpoilers ? 0.0 : 1.0)
|
if let previousDisplayContentsUnderSpoilers, previousDisplayContentsUnderSpoilers != displayContentsUnderSpoilers, displayContentsUnderSpoilers, let currentSpoilerExpandRect = animationArguments?.spoilerExpandRect {
|
||||||
|
let spoilerLocalPosition = self.textNode.layer.convert(currentSpoilerExpandRect.center, to: dustEffectNode.layer)
|
||||||
|
dustEffectNode.revealAtLocation(spoilerLocalPosition)
|
||||||
|
} else {
|
||||||
|
dustEffectNode.update(revealed: displayContentsUnderSpoilers, animated: previousDisplayContentsUnderSpoilers != nil && animation.isAnimated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,7 +552,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0)
|
transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0)
|
||||||
|
if self.navigationTransition == nil {
|
||||||
transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0)
|
transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0
|
self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0
|
||||||
|
|
||||||
|
@ -11043,7 +11043,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive, animateHeader: transition.isAnimated)
|
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: transition.isAnimated && self.headerNode.navigationTransition == nil)
|
||||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||||
if additive {
|
if additive {
|
||||||
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
||||||
@ -11416,7 +11416,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive, animateHeader: animateHeader)
|
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.savedMessagesPeer ?? self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: self.headerNode.navigationTransition == nil ? transition : .immediate, additive: additive, animateHeader: animateHeader && self.headerNode.navigationTransition == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||||
@ -12946,7 +12946,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: true)
|
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.savedMessagesPeer ?? self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, peerNotificationSettings: self.screenNode.data?.peerNotificationSettings, threadNotificationSettings: self.screenNode.data?.threadNotificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false, animateHeader: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
||||||
|
Loading…
x
Reference in New Issue
Block a user