mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Poll and sticker improvements
This commit is contained in:
parent
203acd8567
commit
c8d87f8998
@ -530,7 +530,7 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
if isPlaying {
|
||||
self.play()
|
||||
} else{
|
||||
self.stop()
|
||||
self.pause()
|
||||
}
|
||||
}
|
||||
let canDisplayFirstFrame = self.automaticallyLoadFirstFrame && self.isDisplaying
|
||||
@ -542,79 +542,161 @@ public final class AnimatedStickerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private var isSetUpForPlayback = false
|
||||
|
||||
public func play(firstFrame: Bool = false) {
|
||||
let directData = self.directData
|
||||
let cachedData = self.cachedData
|
||||
let queue = self.queue
|
||||
let timerHolder = self.timer
|
||||
let frameSourceHolder = self.frameSource
|
||||
self.queue.async { [weak self] in
|
||||
var maybeFrameSource: AnimatedStickerFrameSource?
|
||||
let notifyUpdated: (() -> Void)? = nil
|
||||
if let directData = directData {
|
||||
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
|
||||
} else if let (cachedData, cachedDataComplete) = cachedData {
|
||||
if #available(iOS 9.0, *) {
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
|
||||
notifyUpdated?()
|
||||
if self.isSetUpForPlayback {
|
||||
let directData = self.directData
|
||||
let cachedData = self.cachedData
|
||||
let queue = self.queue
|
||||
let timerHolder = self.timer
|
||||
let frameSourceHolder = self.frameSource
|
||||
self.queue.async { [weak self] in
|
||||
var maybeFrameSource: AnimatedStickerFrameSource? = frameSourceHolder.with { $0 }?.syncWith { $0 }?.value
|
||||
if maybeFrameSource == nil {
|
||||
let notifyUpdated: (() -> Void)? = nil
|
||||
if let directData = directData {
|
||||
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
|
||||
} else if let (cachedData, cachedDataComplete) = cachedData {
|
||||
if #available(iOS 9.0, *) {
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
|
||||
notifyUpdated?()
|
||||
})
|
||||
}
|
||||
}
|
||||
let _ = frameSourceHolder.swap(maybeFrameSource.flatMap { maybeFrameSource in
|
||||
return QueueLocalObject(queue: queue, generate: {
|
||||
return AnimatedStickerFrameSourceWrapper(maybeFrameSource)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
let _ = frameSourceHolder.swap(maybeFrameSource.flatMap { maybeFrameSource in
|
||||
return QueueLocalObject(queue: queue, generate: {
|
||||
return AnimatedStickerFrameSourceWrapper(maybeFrameSource)
|
||||
})
|
||||
})
|
||||
guard let frameSource = maybeFrameSource else {
|
||||
return
|
||||
}
|
||||
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
|
||||
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
|
||||
})
|
||||
timerHolder.swap(nil)?.invalidate()
|
||||
|
||||
let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0
|
||||
let frameRate = frameSource.frameRate
|
||||
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: !firstFrame, completion: {
|
||||
let maybeFrame = frameQueue.syncWith { frameQueue in
|
||||
return frameQueue.take()
|
||||
guard let frameSource = maybeFrameSource else {
|
||||
return
|
||||
}
|
||||
if let maybeFrame = maybeFrame, let frame = maybeFrame {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, completion: {
|
||||
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
|
||||
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
|
||||
})
|
||||
timerHolder.swap(nil)?.invalidate()
|
||||
|
||||
let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0
|
||||
let frameRate = frameSource.frameRate
|
||||
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: !firstFrame, completion: {
|
||||
let maybeFrame = frameQueue.syncWith { frameQueue in
|
||||
return frameQueue.take()
|
||||
}
|
||||
if let maybeFrame = maybeFrame, let frame = maybeFrame {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
|
||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
})
|
||||
|
||||
if case .once = strongSelf.playbackMode, frame.isLastFrame {
|
||||
strongSelf.stop()
|
||||
strongSelf.isPlaying = false
|
||||
}
|
||||
})
|
||||
|
||||
if case .once = strongSelf.playbackMode, frame.isLastFrame {
|
||||
strongSelf.stop()
|
||||
strongSelf.isPlaying = false
|
||||
|
||||
let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0
|
||||
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp)))
|
||||
}
|
||||
|
||||
let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0
|
||||
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp)))
|
||||
}
|
||||
frameQueue.with { frameQueue in
|
||||
frameQueue.generateFramesIfNeeded()
|
||||
}
|
||||
}, queue: queue)
|
||||
let _ = timerHolder.swap(timer)
|
||||
timer.start()
|
||||
}
|
||||
} else {
|
||||
self.isSetUpForPlayback = true
|
||||
let directData = self.directData
|
||||
let cachedData = self.cachedData
|
||||
let queue = self.queue
|
||||
let timerHolder = self.timer
|
||||
let frameSourceHolder = self.frameSource
|
||||
self.queue.async { [weak self] in
|
||||
var maybeFrameSource: AnimatedStickerFrameSource?
|
||||
let notifyUpdated: (() -> Void)? = nil
|
||||
if let directData = directData {
|
||||
maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3)
|
||||
} else if let (cachedData, cachedDataComplete) = cachedData {
|
||||
if #available(iOS 9.0, *) {
|
||||
maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {
|
||||
notifyUpdated?()
|
||||
})
|
||||
}
|
||||
}
|
||||
frameQueue.with { frameQueue in
|
||||
frameQueue.generateFramesIfNeeded()
|
||||
let _ = frameSourceHolder.swap(maybeFrameSource.flatMap { maybeFrameSource in
|
||||
return QueueLocalObject(queue: queue, generate: {
|
||||
return AnimatedStickerFrameSourceWrapper(maybeFrameSource)
|
||||
})
|
||||
})
|
||||
guard let frameSource = maybeFrameSource else {
|
||||
return
|
||||
}
|
||||
}, queue: queue)
|
||||
let _ = timerHolder.swap(timer)
|
||||
timer.start()
|
||||
let frameQueue = QueueLocalObject<AnimatedStickerFrameQueue>(queue: queue, generate: {
|
||||
return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource)
|
||||
})
|
||||
timerHolder.swap(nil)?.invalidate()
|
||||
|
||||
let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0
|
||||
let frameRate = frameSource.frameRate
|
||||
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: !firstFrame, completion: {
|
||||
let maybeFrame = frameQueue.syncWith { frameQueue in
|
||||
return frameQueue.take()
|
||||
}
|
||||
if let maybeFrame = maybeFrame, let frame = maybeFrame {
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
})
|
||||
|
||||
if case .once = strongSelf.playbackMode, frame.isLastFrame {
|
||||
strongSelf.stop()
|
||||
strongSelf.isPlaying = false
|
||||
}
|
||||
|
||||
let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0
|
||||
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp)))
|
||||
}
|
||||
}
|
||||
frameQueue.with { frameQueue in
|
||||
frameQueue.generateFramesIfNeeded()
|
||||
}
|
||||
}, queue: queue)
|
||||
let _ = timerHolder.swap(timer)
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
self.timer.swap(nil)?.invalidate()
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
self.isSetUpForPlayback = false
|
||||
self.reportedStarted = false
|
||||
self.timer.swap(nil)?.invalidate()
|
||||
if self.playToCompletionOnStop {
|
||||
|
@ -1116,6 +1116,7 @@ public func createPollController(context: AccountContext, peer: Peer, isQuiz: Bo
|
||||
}
|
||||
controller.isOpaqueWhenInOverlay = true
|
||||
controller.blocksBackgroundWhenInOverlay = true
|
||||
controller.acceptsFocusWhenInOverlay = true
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
controller.alwaysSynchronous = true
|
||||
|
||||
|
@ -96,6 +96,17 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
var isInFocus: Bool = false {
|
||||
didSet {
|
||||
if self.isInFocus != oldValue {
|
||||
self.inFocusUpdated(isInFocus: self.isInFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
func inFocusUpdated(isInFocus: Bool) {
|
||||
self.state.top?.value.isInFocus = isInFocus
|
||||
}
|
||||
|
||||
private var currentKeyboardLeftEdge: CGFloat = 0.0
|
||||
private var additionalKeyboardLeftEdgeOffset: CGFloat = 0.0
|
||||
|
||||
@ -322,12 +333,14 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
if pending.isReady {
|
||||
self.state.pending = nil
|
||||
let previous = self.state.top
|
||||
previous?.value.isInFocus = false
|
||||
self.state.top = pending.value
|
||||
var updatedLayout = layout
|
||||
if pending.value.value.view.disableAutomaticKeyboardHandling.isEmpty {
|
||||
updatedLayout = updatedLayout.withUpdatedInputHeight(nil)
|
||||
}
|
||||
self.topTransition(from: previous, to: pending.value, transitionType: pending.transitionType, layout: updatedLayout, transition: pending.transition)
|
||||
self.state.top?.value.isInFocus = self.isInFocus
|
||||
statusBarTransition = pending.transition
|
||||
if !self.isReady {
|
||||
self.isReady = true
|
||||
@ -338,6 +351,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
|
||||
if controllers.isEmpty && self.state.top != nil {
|
||||
let previous = self.state.top
|
||||
previous?.value.isInFocus = false
|
||||
self.state.top = nil
|
||||
self.topTransition(from: previous, to: nil, transitionType: .pop, layout: layout, transition: .immediate)
|
||||
}
|
||||
|
@ -933,6 +933,44 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.statusBarHost?.setStatusBarHidden(statusBarHidden, animated: animateStatusBarStyleTransition)
|
||||
}
|
||||
|
||||
var foundControllerInFocus = false
|
||||
for container in self.overlayContainers.reversed() {
|
||||
if foundControllerInFocus {
|
||||
container.controller.isInFocus = false
|
||||
} else if container.controller.acceptsFocusWhenInOverlay {
|
||||
foundControllerInFocus = true
|
||||
container.controller.isInFocus = true
|
||||
}
|
||||
}
|
||||
|
||||
for container in self.modalContainers.reversed() {
|
||||
if foundControllerInFocus {
|
||||
container.container.isInFocus = false
|
||||
} else {
|
||||
foundControllerInFocus = true
|
||||
container.container.isInFocus = true
|
||||
}
|
||||
}
|
||||
|
||||
if let rootContainer = self.rootContainer {
|
||||
switch rootContainer {
|
||||
case let .flat(container):
|
||||
if foundControllerInFocus {
|
||||
container.isInFocus = false
|
||||
} else {
|
||||
foundControllerInFocus = true
|
||||
container.isInFocus = true
|
||||
}
|
||||
case let .split(split):
|
||||
if foundControllerInFocus {
|
||||
split.isInFocus = false
|
||||
} else {
|
||||
foundControllerInFocus = true
|
||||
split.isInFocus = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.isUpdatingContainers = false
|
||||
|
||||
if notifyGlobalOverlayControllersUpdated {
|
||||
|
@ -27,6 +27,18 @@ final class NavigationSplitContainer: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var isInFocus: Bool = false {
|
||||
didSet {
|
||||
if self.isInFocus != oldValue {
|
||||
self.inFocusUpdated(isInFocus: self.isInFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
func inFocusUpdated(isInFocus: Bool) {
|
||||
self.masterContainer.isInFocus = isInFocus
|
||||
self.detailContainer.isInFocus = isInFocus
|
||||
}
|
||||
|
||||
init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void, scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) {
|
||||
self.theme = theme
|
||||
|
||||
|
@ -103,9 +103,14 @@ public enum TabBarItemContextActionType {
|
||||
|
||||
public final var isOpaqueWhenInOverlay: Bool = false
|
||||
public final var blocksBackgroundWhenInOverlay: Bool = false
|
||||
public final var acceptsFocusWhenInOverlay: Bool = false
|
||||
public final var automaticallyControlPresentationContextLayout: Bool = true
|
||||
public var updateTransitionWhenPresentedAsModal: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
|
||||
public func requestUpdateParameters() {
|
||||
self.modalStyleOverlayTransitionFactorUpdated?(.immediate)
|
||||
}
|
||||
|
||||
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
|
||||
return self.supportedOrientations
|
||||
}
|
||||
@ -272,6 +277,16 @@ public enum TabBarItemContextActionType {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public internal(set) var isInFocus: Bool = false {
|
||||
didSet {
|
||||
if self.isInFocus != oldValue {
|
||||
self.inFocusUpdated(isInFocus: self.isInFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
open func inFocusUpdated(isInFocus: Bool) {
|
||||
}
|
||||
|
||||
public var attemptNavigation: (@escaping () -> Void) -> Bool = { _ in
|
||||
return true
|
||||
|
@ -800,6 +800,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
}
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.isOpaqueWhenInOverlay = true
|
||||
}
|
||||
|
||||
|
@ -326,6 +326,7 @@ final class BubbleSettingsController: ViewController {
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings))
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
self.navigationItem.title = self.presentationData.strings.Appearance_BubbleCorners_Title
|
||||
|
@ -498,6 +498,7 @@ final class TextSizeSelectionController: ViewController {
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings))
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
self.navigationItem.title = self.presentationData.strings.Appearance_TextSize_Title
|
||||
|
@ -63,6 +63,7 @@ public final class ThemePreviewController: ViewController {
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings))
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.navigationPresentation = .modal
|
||||
|
||||
var hasInstallsCount = false
|
||||
|
@ -75,6 +75,7 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
self.stickerPackContents.set(loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: stickerPack, forceActualized: true))
|
||||
@ -257,6 +258,8 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese
|
||||
} else {
|
||||
return
|
||||
}
|
||||
self.acceptsFocusWhenInOverlay = false
|
||||
self.requestUpdateParameters()
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
|
@ -375,6 +375,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
@ -1699,7 +1700,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
for contentNode in itemNode.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessagePollBubbleContentNode {
|
||||
let sourceNode = contentNode.solutionTipSourceNode
|
||||
strongSelf.controllerInteraction?.displayPollSolution(solution, sourceNode)
|
||||
strongSelf.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1948,149 +1949,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
strongSelf.presentPollCreation(isQuiz: isQuiz)
|
||||
}, displayPollSolution: { [weak self] solution, sourceNode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
/*var foundItemNode: ListViewItemNode?
|
||||
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if sourceNode.view.isDescendant(of: itemNode.view) {
|
||||
foundItemNode = itemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
if let foundItemNode = foundItemNode {
|
||||
|
||||
let absoluteFrame = sourceNode.view.convert(sourceNode.bounds, to: strongSelf.view).insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0)
|
||||
let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: nil, location: absoluteFrame, shouldDismissOnTouch: { point in
|
||||
return .dismiss(consume: absoluteFrame.contains(point))
|
||||
}, openActiveTextItem: { item, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .url(url):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.url(url), nil)
|
||||
}
|
||||
case let .mention(peerId, mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
|
||||
}
|
||||
case let .textMention(mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeerMention(mention)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
|
||||
}
|
||||
case let .botCommand(command):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.sendBotCommand(nil, command)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.command(command), nil)
|
||||
}
|
||||
case let .hashtag(hashtag):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
tooltipScreen.becameDismissed = { tooltipScreen in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.removeAll(where: { $0.0 === tooltipScreen })
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode))
|
||||
strongSelf.present(tooltipScreen, in: .current)
|
||||
}*/
|
||||
|
||||
var found = false
|
||||
strongSelf.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
if controller.text == solution.text && controller.textEntities == solution.entities {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if found {
|
||||
return
|
||||
}
|
||||
|
||||
let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in
|
||||
return .ignore
|
||||
}, openActiveTextItem: { item, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .url(url):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.url(url), nil)
|
||||
}
|
||||
case let .mention(peerId, mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
|
||||
}
|
||||
case let .textMention(mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeerMention(mention)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
|
||||
}
|
||||
case let .botCommand(command):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.sendBotCommand(nil, command)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.command(command), nil)
|
||||
}
|
||||
case let .hashtag(hashtag):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
/*tooltipScreen.becameDismissed = { tooltipScreen in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.removeAll(where: { $0.0 === tooltipScreen })
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode))*/
|
||||
|
||||
strongSelf.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
strongSelf.present(tooltipScreen, in: .current)
|
||||
self?.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: false)
|
||||
}, requestMessageUpdate: { [weak self] id in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
|
||||
@ -5010,6 +4869,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
controller.dismissWithCommitAction()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
self.sendMessageActionsController?.dismiss()
|
||||
}
|
||||
@ -5050,6 +4915,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
override public func inFocusUpdated(isInFocus: Bool) {
|
||||
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
@ -5425,6 +5294,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePollTooltipMessageState(animated: Bool) {
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
|
||||
for contentNode in itemNode.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessagePollBubbleContentNode {
|
||||
contentNode.updatePollTooltipMessageState(animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateItemNodesSearchTextHighlightStates() {
|
||||
var searchString: String?
|
||||
var resultsMessageIndices: [MessageIndex]?
|
||||
@ -5953,6 +5834,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout)
|
||||
legacyController.blocksBackgroundWhenInOverlay = true
|
||||
legacyController.acceptsFocusWhenInOverlay = true
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
legacyController.controllerLoaded = { [weak legacyController] in
|
||||
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
@ -6438,6 +6320,164 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}))
|
||||
}
|
||||
|
||||
private func displayPollSolution(solution: TelegramMediaPollResults.Solution, sourceNode: ASDisplayNode, isAutomatic: Bool) {
|
||||
var maybeFoundItemNode: ChatMessageItemView?
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if sourceNode.view.isDescendant(of: itemNode.view) {
|
||||
maybeFoundItemNode = itemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
guard let foundItemNode = maybeFoundItemNode, let item = foundItemNode.item else {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
if let foundItemNode = foundItemNode {
|
||||
|
||||
let absoluteFrame = sourceNode.view.convert(sourceNode.bounds, to: strongSelf.view).insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0)
|
||||
let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: nil, location: absoluteFrame, shouldDismissOnTouch: { point in
|
||||
return .dismiss(consume: absoluteFrame.contains(point))
|
||||
}, openActiveTextItem: { item, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .url(url):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.url(url), nil)
|
||||
}
|
||||
case let .mention(peerId, mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
|
||||
}
|
||||
case let .textMention(mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeerMention(mention)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
|
||||
}
|
||||
case let .botCommand(command):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.sendBotCommand(nil, command)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.command(command), nil)
|
||||
}
|
||||
case let .hashtag(hashtag):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
tooltipScreen.becameDismissed = { tooltipScreen in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.removeAll(where: { $0.0 === tooltipScreen })
|
||||
}
|
||||
strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode))
|
||||
strongSelf.present(tooltipScreen, in: .current)
|
||||
}*/
|
||||
|
||||
var found = false
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
if controller.text == solution.text && controller.textEntities == solution.entities {
|
||||
found = true
|
||||
controller.dismiss()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if found {
|
||||
return
|
||||
}
|
||||
|
||||
let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in
|
||||
return .ignore
|
||||
}, openActiveTextItem: { [weak self] item, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case let .url(url):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.openUrl(url, concealed: false)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.url(url), nil)
|
||||
}
|
||||
case let .mention(peerId, mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeer(peerId, .default, nil)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil)
|
||||
}
|
||||
case let .textMention(mention):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openPeerMention(mention)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.mention(mention), nil)
|
||||
}
|
||||
case let .botCommand(command):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.sendBotCommand(nil, command)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.command(command), nil)
|
||||
}
|
||||
case let .hashtag(hashtag):
|
||||
switch action {
|
||||
case .tap:
|
||||
strongSelf.controllerInteraction?.openHashtag(nil, hashtag)
|
||||
case .longTap:
|
||||
strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let messageId = item.message.id
|
||||
self.controllerInteraction?.currentPollMessageWithTooltip = messageId
|
||||
self.updatePollTooltipMessageState(animated: !isAutomatic)
|
||||
|
||||
tooltipScreen.willBecomeDismissed = { [weak self] tooltipScreen in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
//strongSelf.currentMessageTooltipScreens.removeAll(where: { $0.0 === tooltipScreen })
|
||||
if strongSelf.controllerInteraction?.currentPollMessageWithTooltip == messageId {
|
||||
strongSelf.controllerInteraction?.currentPollMessageWithTooltip = nil
|
||||
strongSelf.updatePollTooltipMessageState(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
//strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode))
|
||||
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
self.present(tooltipScreen, in: .current)
|
||||
}
|
||||
|
||||
private func presentPollCreation(isQuiz: Bool? = nil) {
|
||||
if case .peer = self.chatLocation, let peer = self.presentationInterfaceState.renderedPeer?.peer {
|
||||
self.effectiveNavigationController?.pushViewController(createPollController(context: self.context, peer: peer, isQuiz: isQuiz, completion: { [weak self] message in
|
||||
|
@ -112,12 +112,14 @@ public final class ChatControllerInteraction {
|
||||
let requestMessageUpdate: (MessageId) -> Void
|
||||
let cancelInteractiveKeyboardGestures: () -> Void
|
||||
|
||||
var canPlayMedia: Bool = false
|
||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
||||
var selectionState: ChatInterfaceSelectionState?
|
||||
var highlightedState: ChatInterfaceHighlightedState?
|
||||
var contextHighlightedState: ChatInterfaceHighlightedState?
|
||||
var automaticMediaDownloadSettings: MediaAutoDownloadSettings
|
||||
var pollActionState: ChatInterfacePollActionState
|
||||
var currentPollMessageWithTooltip: MessageId?
|
||||
var stickerSettings: ChatInterfaceStickerSettings
|
||||
var searchTextHighightState: (String, [MessageIndex])?
|
||||
var seenOneTimeAnimatedMedia = Set<MessageId>()
|
||||
|
@ -458,6 +458,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private var isInFocus: Bool = false
|
||||
|
||||
func inFocusUpdated(isInFocus: Bool) {
|
||||
self.isInFocus = isInFocus
|
||||
|
||||
self.inputMediaNode?.simulateUpdateLayout(isVisible: isInFocus)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition protoTransition: ContainedViewLayoutTransition, listViewTransaction:
|
||||
(ListViewUpdateSizeAndInsets, CGFloat, Bool, @escaping () -> Void) -> Void) {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
@ -647,7 +655,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.insertSubnode(inputNode, aboveSubnode: self.inputPanelBackgroundNode)
|
||||
}
|
||||
}
|
||||
inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: true)
|
||||
inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus)
|
||||
} else if let inputNode = self.inputNode {
|
||||
dismissedInputNode = inputNode
|
||||
self.inputNode = nil
|
||||
|
@ -1346,6 +1346,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
}
|
||||
|
||||
func simulateUpdateLayout(isVisible: Bool) {
|
||||
if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, _) = self.validLayout {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
|
||||
var searchMode: ChatMediaInputSearchMode?
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
|
||||
|
@ -87,13 +87,15 @@ final class TrendingPanePackEntry: Identifiable, Comparable {
|
||||
|
||||
func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem {
|
||||
let info = self.info
|
||||
let itemContext = StickerPaneSearchGlobalItemContext()
|
||||
itemContext.canPlayMedia = true
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: {
|
||||
interaction.openPack(info)
|
||||
}, install: {
|
||||
interaction.installPack(info)
|
||||
}, getItemIsPreviewed: { item in
|
||||
return interaction.getItemIsPreviewed(item)
|
||||
})
|
||||
}, itemContext: itemContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -802,7 +802,7 @@ private let labelsFont = Font.regular(14.0)
|
||||
|
||||
private final class SolutionButtonNode: HighlightableButtonNode {
|
||||
private let pressed: () -> Void
|
||||
private let iconNode: ASImageNode
|
||||
let iconNode: ASImageNode
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var incoming: Bool?
|
||||
@ -1542,6 +1542,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: resultSize.width, height: 44.0))
|
||||
|
||||
strongSelf.updateSelection()
|
||||
strongSelf.updatePollTooltipMessageState(animated: false)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -1740,6 +1741,23 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePollTooltipMessageState(animated: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let displaySolutionButton = item.message.id != item.controllerInteraction.currentPollMessageWithTooltip
|
||||
if displaySolutionButton != !self.solutionButtonNode.iconNode.alpha.isZero {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if animated {
|
||||
transition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
transition.updateAlpha(node: self.solutionButtonNode.iconNode, alpha: displaySolutionButton ? 1.0 : 0.0)
|
||||
transition.updateSublayerTransformScale(node: self.solutionButtonNode, scale: displaySolutionButton ? 1.0 : 0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerAvatarReference: Equatable {
|
||||
|
@ -81,6 +81,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
|
@ -21,6 +21,7 @@ private final class FeaturedInteraction {
|
||||
let openPack: (ItemCollectionInfo) -> Void
|
||||
let getItemIsPreviewed: (StickerPackItem) -> Bool
|
||||
let openSearch: () -> Void
|
||||
let itemContext = StickerPaneSearchGlobalItemContext()
|
||||
|
||||
init(installPack: @escaping (ItemCollectionInfo, Bool) -> Void, openPack: @escaping (ItemCollectionInfo) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, openSearch: @escaping () -> Void) {
|
||||
self.installPack = installPack
|
||||
@ -95,7 +96,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
interaction.installPack(info, !self.installed)
|
||||
}, getItemIsPreviewed: { item in
|
||||
return interaction.getItemIsPreviewed(item)
|
||||
})
|
||||
}, itemContext: interaction.itemContext)
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +212,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
private var canLoadMore: Bool = true
|
||||
private var isLoadingMore: Bool = false
|
||||
|
||||
private var interaction: FeaturedInteraction?
|
||||
|
||||
private var enqueuedTransitions: [FeaturedTransition] = []
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
@ -292,14 +295,6 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
)
|
||||
|
||||
self.searchNode = FeaturedPaneSearchContentNode(
|
||||
context: context,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
inputNodeInteraction: inputNodeInteraction,
|
||||
controller: controller,
|
||||
sendSticker: sendSticker
|
||||
)
|
||||
self.searchNode?.updateActivity = { [weak self] activity in
|
||||
self?.controller?.searchNavigationNode?.setActivity(activity)
|
||||
}
|
||||
@ -416,6 +411,17 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
openSearch: {
|
||||
}
|
||||
)
|
||||
self.interaction = interaction
|
||||
|
||||
self.searchNode = FeaturedPaneSearchContentNode(
|
||||
context: context,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
inputNodeInteraction: inputNodeInteraction,
|
||||
controller: controller,
|
||||
sendSticker: sendSticker,
|
||||
itemContext: interaction.itemContext
|
||||
)
|
||||
|
||||
let previousEntries = Atomic<[FeaturedEntry]?>(value: nil)
|
||||
let context = self.context
|
||||
@ -670,6 +676,16 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
}))
|
||||
}
|
||||
|
||||
func inFocusUpdated(isInFocus: Bool) {
|
||||
self.interaction?.itemContext.canPlayMedia = isInFocus
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode {
|
||||
itemNode.updateCanPlayMedia()
|
||||
}
|
||||
}
|
||||
self.searchNode?.updateCanPlayMedia()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let firstTime = self.validLayout == nil
|
||||
|
||||
@ -817,6 +833,8 @@ final class FeaturedStickersScreen: ViewController {
|
||||
private func updatePresentationData() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
self.searchNavigationNode?.updatePresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
|
||||
self.controllerNode.updatePresentationData(presentationData: presentationData)
|
||||
@ -847,6 +865,10 @@ final class FeaturedStickersScreen: ViewController {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override public func inFocusUpdated(isInFocus: Bool) {
|
||||
self.controllerNode.inFocusUpdated(isInFocus: isInFocus)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
@ -990,7 +1012,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
|
||||
}
|
||||
}
|
||||
|
||||
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> GridItem {
|
||||
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, itemContext: StickerPaneSearchGlobalItemContext) -> GridItem {
|
||||
switch self {
|
||||
case let .sticker(_, code, stickerItem, theme):
|
||||
return StickerPaneSearchStickerItem(account: account, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in
|
||||
@ -1003,7 +1025,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
|
||||
interaction.install(info, topItems, !installed)
|
||||
}, getItemIsPreviewed: { item in
|
||||
return interaction.getItemIsPreviewed(item)
|
||||
})
|
||||
}, itemContext: itemContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1018,7 +1040,7 @@ private struct FeaturedSearchGridTransition {
|
||||
let animated: Bool
|
||||
}
|
||||
|
||||
private func preparedFeaturedSearchEntryTransition(account: Account, theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [FeaturedSearchEntry], to toEntries: [FeaturedSearchEntry], interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> FeaturedSearchGridTransition {
|
||||
private func preparedFeaturedSearchEntryTransition(account: Account, theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [FeaturedSearchEntry], to toEntries: [FeaturedSearchEntry], interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, itemContext: StickerPaneSearchGlobalItemContext) -> FeaturedSearchGridTransition {
|
||||
let stationaryItems: GridNodeStationaryItems = .none
|
||||
let scrollToItem: GridNodeScrollToItem? = nil
|
||||
var animated = false
|
||||
@ -1027,8 +1049,8 @@ private func preparedFeaturedSearchEntryTransition(account: Account, theme: Pres
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction)) }
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, itemContext: itemContext), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, itemContext: itemContext)) }
|
||||
|
||||
let firstIndexInSectionOffset = 0
|
||||
|
||||
@ -1041,6 +1063,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
private var interaction: StickerPaneSearchInteraction?
|
||||
private weak var controller: FeaturedStickersScreen?
|
||||
private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
private let itemContext: StickerPaneSearchGlobalItemContext
|
||||
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
@ -1073,11 +1096,12 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
return !self.gridNode.isHidden
|
||||
}
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, inputNodeInteraction: ChatMediaInputNodeInteraction, controller: FeaturedStickersScreen, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) {
|
||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, inputNodeInteraction: ChatMediaInputNodeInteraction, controller: FeaturedStickersScreen, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, itemContext: StickerPaneSearchGlobalItemContext) {
|
||||
self.context = context
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.controller = controller
|
||||
self.sendSticker = sendSticker
|
||||
self.itemContext = itemContext
|
||||
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -1405,7 +1429,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
let previousEntries = strongSelf.currentEntries.swap(entries)
|
||||
let transition = preparedFeaturedSearchEntryTransition(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, from: previousEntries ?? [], to: entries, interaction: interaction, inputNodeInteraction: strongSelf.inputNodeInteraction)
|
||||
let transition = preparedFeaturedSearchEntryTransition(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, from: previousEntries ?? [], to: entries, interaction: interaction, inputNodeInteraction: strongSelf.inputNodeInteraction, itemContext: strongSelf.itemContext)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
|
||||
if displayResults {
|
||||
@ -1515,4 +1539,12 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func updateCanPlayMedia() {
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode {
|
||||
itemNode.updateCanPlayMedia()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -431,6 +431,7 @@ public func pollResultsController(context: AccountContext, messageId: MessageId,
|
||||
}
|
||||
controller.isOpaqueWhenInOverlay = true
|
||||
controller.blocksBackgroundWhenInOverlay = true
|
||||
controller.acceptsFocusWhenInOverlay = true
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -104,13 +104,15 @@ private enum StickerSearchEntry: Identifiable, Comparable {
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node, rect)
|
||||
})
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
let itemContext = StickerPaneSearchGlobalItemContext()
|
||||
itemContext.canPlayMedia = true
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: {
|
||||
interaction.open(info)
|
||||
}, install: {
|
||||
interaction.install(info, topItems, !installed)
|
||||
}, getItemIsPreviewed: { item in
|
||||
return interaction.getItemIsPreviewed(item)
|
||||
})
|
||||
}, itemContext: itemContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ final class StickerPaneSearchGlobalSection: GridSection {
|
||||
}
|
||||
}
|
||||
|
||||
final class StickerPaneSearchGlobalItemContext {
|
||||
var canPlayMedia: Bool = false
|
||||
}
|
||||
|
||||
final class StickerPaneSearchGlobalItem: GridItem {
|
||||
let account: Account
|
||||
@ -48,13 +51,14 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
let open: () -> Void
|
||||
let install: () -> Void
|
||||
let getItemIsPreviewed: (StickerPackItem) -> Bool
|
||||
let itemContext: StickerPaneSearchGlobalItemContext
|
||||
|
||||
let section: GridSection? = StickerPaneSearchGlobalSection()
|
||||
var fillsRowWithHeight: CGFloat? {
|
||||
return self.grid ? nil : (128.0 + (self.topSeparator ? 12.0 : 0.0))
|
||||
}
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) {
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -69,6 +73,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
self.open = open
|
||||
self.install = install
|
||||
self.getItemIsPreviewed = getItemIsPreviewed
|
||||
self.itemContext = itemContext
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
@ -109,19 +114,37 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
private let preloadedStickerPackThumbnailDisposable = MetaDisposable()
|
||||
|
||||
private var preloadedThumbnail = false
|
||||
private var canPlay = false
|
||||
|
||||
private var canPlayMedia: Bool = false {
|
||||
didSet {
|
||||
if self.canPlayMedia != oldValue {
|
||||
self.updatePlayback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var isVisibleInGrid: Bool {
|
||||
didSet {
|
||||
if oldValue != self.isVisibleInGrid {
|
||||
for node in self.itemNodes {
|
||||
node.visibility = self.isVisibleInGrid
|
||||
}
|
||||
self.updatePlayback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePlayback() {
|
||||
let canPlay = self.canPlayMedia && self.isVisibleInGrid
|
||||
if canPlay != self.canPlay {
|
||||
self.canPlay = canPlay
|
||||
|
||||
for node in self.itemNodes {
|
||||
node.visibility = self.canPlay
|
||||
}
|
||||
|
||||
if let item = self.item, self.isVisibleInGrid, !self.preloadedThumbnail {
|
||||
self.preloadedThumbnail = true
|
||||
|
||||
if let item = self.item, self.isVisibleInGrid, !self.preloadedThumbnail {
|
||||
self.preloadedThumbnail = true
|
||||
|
||||
self.preloadedStickerPackThumbnailDisposable.set(preloadedStickerPackThumbnail(account: item.account, info: item.info, items: item.topItems).start())
|
||||
}
|
||||
self.preloadedStickerPackThumbnailDisposable.set(preloadedStickerPackThumbnail(account: item.account, info: item.info, items: item.topItems).start())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,6 +264,14 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
self.updatePreviewing(animated: false)
|
||||
}
|
||||
|
||||
func updateCanPlayMedia() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
self.canPlayMedia = item.itemContext.canPlayMedia
|
||||
}
|
||||
|
||||
override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
@ -356,7 +387,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
node = strongSelf.itemNodes[i]
|
||||
} else {
|
||||
node = TrendingTopItemNode()
|
||||
node.visibility = strongSelf.isVisibleInGrid
|
||||
node.visibility = strongSelf.canPlay
|
||||
strongSelf.itemNodes.append(node)
|
||||
strongSelf.addSubnode(node)
|
||||
}
|
||||
@ -376,6 +407,8 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
strongSelf.itemNodes.remove(at: i)
|
||||
}
|
||||
}
|
||||
|
||||
self.canPlayMedia = item.itemContext.canPlayMedia
|
||||
}
|
||||
|
||||
@objc func installPressed() {
|
||||
|
@ -357,6 +357,7 @@ public final class TooltipScreen: ViewController {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public var willBecomeDismissed: ((TooltipScreen) -> Void)?
|
||||
public var becameDismissed: ((TooltipScreen) -> Void)?
|
||||
|
||||
public init(text: String, textEntities: [MessageTextEntity] = [], icon: TooltipScreen.Icon?, location: TooltipScreen.Location, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void = { _, _ in }) {
|
||||
@ -381,7 +382,7 @@ public final class TooltipScreen: ViewController {
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 15.0, execute: { [weak self] in
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5.0, execute: { [weak self] in
|
||||
self?.dismiss()
|
||||
})
|
||||
}
|
||||
@ -418,6 +419,7 @@ public final class TooltipScreen: ViewController {
|
||||
return
|
||||
}
|
||||
self.isDismissed = true
|
||||
self.willBecomeDismissed?(self)
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
Loading…
x
Reference in New Issue
Block a user