Skip to content

Commit b510455

Browse files
author
Gringo2
committed
Phase 0: Add tap highlight and tune spring parameters for tab bar
- Add specularHighlightLayer to LiquidLensView with 40ms tap flash - Implement animateTapHighlight(at:) with thread safety and animation cleanup - Tune spring duration: 0.4s → 0.45s (matches contest spec 420-480ms) - Reduce scale: 1.15x → 1.04x (below 1.06x overshoot limit) - Add safety: main thread assertion and prevent animation stacking Contest alignment: - 40ms tap highlight (spec requirement) - Spring within 420-480ms range - Scale under max overshoot - GPU blur unchanged (CAFilter) Files modified: - LiquidLensView.swift: +32 lines (tap highlight layer + method) - TabBarComponent.swift: +5 lines (trigger + spring tuning)
1 parent 68a75ff commit b510455

2 files changed

Lines changed: 50 additions & 3 deletions

File tree

‎submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public final class LiquidLensView: UIView {
9292
private let liftedContainerView: UIView
9393
public let contentView: UIView
9494
private let restingBackgroundView: RestingBackgroundView
95+
private let specularHighlightLayer: CAGradientLayer
9596

9697
private var legacySelectionView: GlassBackgroundView.ContentImageView?
9798
private var legacyContentMaskView: UIView?
@@ -129,6 +130,16 @@ public final class LiquidLensView: UIView {
129130
self.liftedContainerView = UIView()
130131

131132
self.restingBackgroundView = RestingBackgroundView()
133+
134+
self.specularHighlightLayer = CAGradientLayer()
135+
self.specularHighlightLayer.type = .radial
136+
self.specularHighlightLayer.colors = [
137+
UIColor(white: 1.0, alpha: 0.3).cgColor,
138+
UIColor(white: 1.0, alpha: 0.0).cgColor
139+
]
140+
self.specularHighlightLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
141+
self.specularHighlightLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
142+
self.specularHighlightLayer.opacity = 0.0
132143

133144
super.init(frame: frame)
134145

@@ -139,6 +150,9 @@ public final class LiquidLensView: UIView {
139150
self.backgroundView.contentView.addSubview(self.containerView)
140151
self.containerView.isUserInteractionEnabled = false
141152

153+
// Add specular highlight layer above content
154+
self.containerView.layer.addSublayer(self.specularHighlightLayer)
155+
142156
if #available(iOS 26.0, *) {
143157
if let viewClass = NSClassFromString("_UILiquidLensView") as AnyObject as? NSObjectProtocol {
144158
let allocSelector = NSSelectorFromString("alloc")
@@ -222,6 +236,34 @@ public final class LiquidLensView: UIView {
222236
required init?(coder: NSCoder) {
223237
fatalError("init(coder:) has not been implemented")
224238
}
239+
240+
public func animateTapHighlight(at location: CGPoint) {
241+
assert(Thread.isMainThread, "animateTapHighlight must be called on main thread")
242+
243+
// Remove previous animation to prevent stacking on rapid taps
244+
self.specularHighlightLayer.removeAnimation(forKey: "tapHighlight")
245+
246+
// 40ms opacity spike for tap feedback
247+
let animation = CABasicAnimation(keyPath: "opacity")
248+
animation.fromValue = 0.0
249+
animation.toValue = 1.0
250+
animation.duration = 0.04
251+
animation.autoreverses = true
252+
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
253+
254+
// Position highlight at tap location
255+
CATransaction.begin()
256+
CATransaction.setDisableActions(true)
257+
self.specularHighlightLayer.frame = CGRect(
258+
x: location.x - 40.0,
259+
y: location.y - 40.0,
260+
width: 80.0,
261+
height: 80.0
262+
)
263+
CATransaction.commit()
264+
265+
self.specularHighlightLayer.add(animation, forKey: "tapHighlight")
266+
}
225267

226268
public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, isDark: Bool, isLifted: Bool, transition: ComponentTransition) {
227269
let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, isDark: isDark, isLifted: isLifted)

‎submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,12 @@ public final class TabBarComponent: Component {
286286
if let itemId = self.item(at: recognizer.location(in: self)), let itemView = self.itemViews[itemId]?.view {
287287
let startX = itemView.frame.minX - 4.0
288288
self.selectionGestureState = (startX, startX)
289-
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
289+
290+
// Trigger tap highlight at touch location
291+
let tapLocation = recognizer.location(in: self.liquidLensView)
292+
self.liquidLensView.animateTapHighlight(at: tapLocation)
293+
294+
self.state?.updated(transition: .spring(duration: 0.45), isLocal: true)
290295
}
291296
case .changed:
292297
if var selectionGestureState = self.selectionGestureState {
@@ -303,7 +308,7 @@ public final class TabBarComponent: Component {
303308
self.overrideSelectedItemId = itemId
304309
item.action(false)
305310
}
306-
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
311+
self.state?.updated(transition: .spring(duration: 0.45), isLocal: true)
307312
default:
308313
break
309314
}
@@ -437,7 +442,7 @@ public final class TabBarComponent: Component {
437442
itemTransition.setFrame(view: itemComponentView, frame: itemFrame)
438443
itemTransition.setPosition(view: selectedItemComponentView, position: itemFrame.center)
439444
itemTransition.setBounds(view: selectedItemComponentView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
440-
itemTransition.setScale(view: selectedItemComponentView, scale: self.selectionGestureState != nil ? 1.15 : 1.0)
445+
itemTransition.setScale(view: selectedItemComponentView, scale: self.selectionGestureState != nil ? 1.04 : 1.0)
441446

442447
if let previousComponent, previousComponent.selectedId != item.id, isItemSelected {
443448
itemComponentView.playSelectionAnimation()

0 commit comments

Comments
 (0)