Bot start button improvements

This commit is contained in:
Ilya Laktyushin
2023-03-17 22:55:37 +04:00
parent de8aea109f
commit 25d7aaf7e3
18 changed files with 607 additions and 133 deletions

View File

@@ -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()
}