mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Bot start button improvements
This commit is contained in:
@@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
@@ -29,11 +30,93 @@ public enum TooltipActiveTextAction {
|
||||
case longTap
|
||||
}
|
||||
|
||||
private func generateArrowImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 14.0, height: 8.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
context.setLineWidth(1.0 + UIScreenPixel)
|
||||
context.setLineCap(.round)
|
||||
|
||||
let arrowBounds = bounds.insetBy(dx: 1.0, dy: 1.0)
|
||||
context.move(to: arrowBounds.origin)
|
||||
context.addLine(to: CGPoint(x: arrowBounds.midX, y: arrowBounds.maxY))
|
||||
context.addLine(to: CGPoint(x: arrowBounds.maxX, y: arrowBounds.minY))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
private class DownArrowsIconNode: ASDisplayNode {
|
||||
private let topArrow: ASImageNode
|
||||
private let bottomArrow: ASImageNode
|
||||
|
||||
override init() {
|
||||
self.topArrow = ASImageNode()
|
||||
self.topArrow.displaysAsynchronously = false
|
||||
self.topArrow.image = generateArrowImage()
|
||||
|
||||
self.bottomArrow = ASImageNode()
|
||||
self.bottomArrow.displaysAsynchronously = false
|
||||
self.bottomArrow.image = self.topArrow.image
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.topArrow)
|
||||
self.addSubnode(self.bottomArrow)
|
||||
|
||||
if let image = self.topArrow.image {
|
||||
self.topArrow.frame = CGRect(origin: .zero, size: image.size)
|
||||
self.bottomArrow.frame = CGRect(origin: CGPoint(x: 0.0, y: 7.0), size: image.size)
|
||||
}
|
||||
}
|
||||
|
||||
func setupAnimations() {
|
||||
guard self.bottomArrow.layer.animation(forKey: "position") == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
self.supernode?.layer.animateKeyframes(values: [
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 1.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 1.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||
], duration: 1.1, keyPath: "position", additive: true)
|
||||
|
||||
self.bottomArrow.layer.animateKeyframes(values: [
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 4.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 4.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||
], duration: 1.1, keyPath: "position", additive: true, completion: { [weak self] _ in
|
||||
Queue.mainQueue().after(2.9) {
|
||||
self?.setupAnimations()
|
||||
}
|
||||
})
|
||||
|
||||
self.topArrow.layer.animateKeyframes(values: [
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 6.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 6.0)),
|
||||
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||
], duration: 1.1, keyPath: "position", additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
private let tooltipStyle: TooltipScreen.Style
|
||||
private let icon: TooltipScreen.Icon?
|
||||
private let customContentNode: TooltipCustomContentNode?
|
||||
private let location: TooltipScreen.Location
|
||||
var location: TooltipScreen.Location {
|
||||
didSet {
|
||||
if let layout = self.validLayout {
|
||||
self.updateLayout(layout: layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
private let displayDuration: TooltipScreen.DisplayDuration
|
||||
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
||||
private let requestDismiss: () -> Void
|
||||
@@ -50,6 +133,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
private let arrowContainer: ASDisplayNode
|
||||
private var arrowEffectView: UIView?
|
||||
private let animatedStickerNode: AnimatedStickerNode
|
||||
private var downArrowsNode: DownArrowsIconNode?
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var isArrowInverted: Bool = false
|
||||
@@ -138,6 +222,24 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
self.arrowContainer.view.addSubview(self.arrowEffectView!)
|
||||
|
||||
let maskLayer = CAShapeLayer()
|
||||
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
|
||||
maskLayer.path = path.cgPath
|
||||
}
|
||||
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
|
||||
self.arrowContainer.layer.mask = maskLayer
|
||||
} else if case .default = style {
|
||||
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
self.backgroundContainerNode.clipsToBounds = true
|
||||
self.backgroundContainerNode.cornerRadius = 14.0
|
||||
if #available(iOS 13.0, *) {
|
||||
self.backgroundContainerNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
fontSize = 14.0
|
||||
|
||||
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
self.arrowContainer.view.addSubview(self.arrowEffectView!)
|
||||
|
||||
let maskLayer = CAShapeLayer()
|
||||
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
|
||||
maskLayer.path = path.cgPath
|
||||
@@ -179,7 +281,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
} else if case .top = location {
|
||||
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
self.containerNode.clipsToBounds = true
|
||||
self.containerNode.cornerRadius = 9.0
|
||||
self.containerNode.cornerRadius = 14.0
|
||||
if #available(iOS 13.0, *) {
|
||||
self.containerNode.layer.cornerCurve = .continuous
|
||||
}
|
||||
@@ -204,6 +306,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
case .info:
|
||||
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_infotip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
self.animatedStickerNode.automaticallyLoadFirstFrame = true
|
||||
case .downArrows:
|
||||
self.downArrowsNode = DownArrowsIconNode()
|
||||
}
|
||||
|
||||
super.init()
|
||||
@@ -227,6 +331,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
self.containerNode.addSubnode(self.animatedStickerNode)
|
||||
if let downArrowsNode = self.downArrowsNode {
|
||||
self.containerNode.addSubnode(downArrowsNode)
|
||||
}
|
||||
self.scrollingContainer.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.scrollingContainer)
|
||||
|
||||
@@ -298,7 +405,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
let sideInset: CGFloat = self.inset + layout.safeInsets.left
|
||||
let bottomInset: CGFloat = 10.0
|
||||
let contentInset: CGFloat = 11.0
|
||||
let contentVerticalInset: CGFloat = 11.0
|
||||
let contentVerticalInset: CGFloat = 8.0
|
||||
let animationSize: CGSize
|
||||
let animationInset: CGFloat
|
||||
let animationSpacing: CGFloat
|
||||
@@ -308,6 +415,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
animationSize = CGSize()
|
||||
animationInset = 0.0
|
||||
animationSpacing = 0.0
|
||||
case .downArrows:
|
||||
animationSize = CGSize(width: 24.0, height: 32.0)
|
||||
animationInset = (40.0 - animationSize.width) / 2.0
|
||||
animationSpacing = 8.0
|
||||
case .chatListPress:
|
||||
animationSize = CGSize(width: 32.0, height: 32.0)
|
||||
animationInset = (70.0 - animationSize.width) / 2.0
|
||||
@@ -412,8 +523,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize))
|
||||
|
||||
transition.updateFrame(node: self.animatedStickerNode, frame: CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)))
|
||||
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
||||
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
|
||||
self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
||||
|
||||
if let downArrowsNode = self.downArrowsNode {
|
||||
let arrowsSize = CGSize(width: 16.0, height: 16.0)
|
||||
transition.updateFrame(node: downArrowsNode, frame: CGRect(origin: CGPoint(x: animationFrame.midX - arrowsSize.width / 2.0, y: animationFrame.midY - arrowsSize.height / 2.0), size: arrowsSize))
|
||||
downArrowsNode.setupAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
@@ -472,7 +590,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
animationDelay = 0.6
|
||||
case .info:
|
||||
animationDelay = 0.2
|
||||
case .none:
|
||||
case .none, .downArrows:
|
||||
animationDelay = 0.0
|
||||
}
|
||||
|
||||
@@ -527,6 +645,7 @@ public final class TooltipScreen: ViewController {
|
||||
public enum Icon {
|
||||
case info
|
||||
case chatListPress
|
||||
case downArrows
|
||||
}
|
||||
|
||||
public enum DismissOnTouch {
|
||||
@@ -548,6 +667,7 @@ public final class TooltipScreen: ViewController {
|
||||
public enum DisplayDuration {
|
||||
case `default`
|
||||
case custom(Double)
|
||||
case infinite
|
||||
}
|
||||
|
||||
public enum Style {
|
||||
@@ -562,7 +682,13 @@ public final class TooltipScreen: ViewController {
|
||||
private let style: TooltipScreen.Style
|
||||
private let icon: TooltipScreen.Icon?
|
||||
private let customContentNode: TooltipCustomContentNode?
|
||||
private let location: TooltipScreen.Location
|
||||
public var location: TooltipScreen.Location {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.location = self.location
|
||||
}
|
||||
}
|
||||
}
|
||||
private let displayDuration: DisplayDuration
|
||||
private let inset: CGFloat
|
||||
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
||||
@@ -580,6 +706,8 @@ public final class TooltipScreen: ViewController {
|
||||
|
||||
private var dismissTimer: Foundation.Timer?
|
||||
|
||||
public var alwaysVisible = false
|
||||
|
||||
public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
|
||||
self.account = account
|
||||
self.text = text
|
||||
@@ -615,6 +743,7 @@ public final class TooltipScreen: ViewController {
|
||||
|
||||
public func resetDismissTimeout(duration: TooltipScreen.DisplayDuration? = nil) {
|
||||
self.dismissTimer?.invalidate()
|
||||
self.dismissTimer = nil
|
||||
|
||||
let timeout: Double
|
||||
switch duration ?? self.displayDuration {
|
||||
@@ -622,6 +751,8 @@ public final class TooltipScreen: ViewController {
|
||||
timeout = 5.0
|
||||
case let .custom(value):
|
||||
timeout = value
|
||||
case .infinite:
|
||||
return
|
||||
}
|
||||
|
||||
final class TimerTarget: NSObject {
|
||||
@@ -658,7 +789,7 @@ public final class TooltipScreen: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
if let validLayout = self.validLayout, !self.alwaysVisible {
|
||||
if validLayout.size.width != layout.size.width {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user