Various improvements

This commit is contained in:
Isaac 2026-03-06 18:13:48 +01:00
parent 3ca012d610
commit bb23c6f653
18 changed files with 839 additions and 165 deletions

View file

@ -1813,7 +1813,7 @@ swift_library(
ios_ui_test_suite(
name = "iOSAppUITestSuite",
bundle_id = "org.telegram.Telegram-iOS-uitests",
minimum_os_version = "13.0",
minimum_os_version = minimum_os_version,
runners = [
":iPhone-17__26.2",
],

View file

@ -438,11 +438,31 @@ public final class ChatPresentationInterfaceState: Equatable {
}
public struct PersistentPeerData: Codable, Equatable {
public var topicListPanelLocation: Bool
public enum TopicListPanelLocation: Int32 {
case top = 0
case side = 1
case bottom = 2
}
public init(topicListPanelLocation: Bool) {
private enum CodingKeys: String, CodingKey {
case topicListPanelLocation
}
public var topicListPanelLocation: TopicListPanelLocation
public init(topicListPanelLocation: TopicListPanelLocation) {
self.topicListPanelLocation = topicListPanelLocation
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.topicListPanelLocation = TopicListPanelLocation(rawValue: try container.decode(Int32.self, forKey: .topicListPanelLocation)) ?? .top
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(Int32(self.topicListPanelLocation.rawValue), forKey: .topicListPanelLocation)
}
}
public final class RemovePaidMessageFeeData: Equatable {
@ -660,7 +680,7 @@ public final class ChatPresentationInterfaceState: Equatable {
self.alwaysShowGiftButton = false
self.disallowedGifts = nil
self.persistentData = PersistentPeerData(
topicListPanelLocation: false
topicListPanelLocation: .top
)
self.removePaidMessageFeeData = nil
self.viewForumAsMessages = false

View file

@ -911,6 +911,10 @@ public extension CALayer {
static func displacementMap() -> NSObject? {
return makeDisplacementMapFilter()
}
static func colorMatrix() -> NSObject? {
return makeColorMatrixFilter()
}
}
public extension CALayer {

View file

@ -362,8 +362,11 @@ static CGRect viewFrame(UIView *view)
_deleteButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, (self.bounds.size.height - 45.0f) / 2.0f, 45.0f, 45.0f)];
[_deleteButton setImage:deleteImage forState:UIControlStateNormal];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_deleteButton.adjustsImageWhenDisabled = false;
_deleteButton.adjustsImageWhenHighlighted = false;
#pragma clang diagnostic pop
[_deleteButton addTarget:self action:@selector(deleteButtonPressed) forControlEvents:UIControlEventTouchUpInside];
if (!_forStory) {
[self addSubview:_deleteButton];
@ -379,7 +382,10 @@ static CGRect viewFrame(UIView *view)
_sendButton.alpha = 0.0f;
_sendButton.exclusiveTouch = true;
[_sendButton setImage:_assets.sendImage forState:UIControlStateNormal];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_sendButton.adjustsImageWhenHighlighted = false;
#pragma clang diagnostic pop
[_sendButton addTarget:self action:@selector(sendButtonPressed) forControlEvents:UIControlEventTouchUpInside];
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonLongPressed:)];

View file

@ -34,14 +34,20 @@
_leftSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 16, height)];
_leftSegmentView.exclusiveTouch = true;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_leftSegmentView.adjustsImageWhenHighlighted = false;
#pragma clang diagnostic pop
[_leftSegmentView setBackgroundImage:TGComponentsImageNamed(@"VideoMessageLeftHandle") forState:UIControlStateNormal];
_leftSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -25, -5, -10);
[self addSubview:_leftSegmentView];
_rightSegmentView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 16, height)];
_rightSegmentView.exclusiveTouch = true;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_rightSegmentView.adjustsImageWhenHighlighted = false;
#pragma clang diagnostic pop
[_rightSegmentView setBackgroundImage:TGComponentsImageNamed(@"VideoMessageRightHandle") forState:UIControlStateNormal];
_rightSegmentView.hitTestEdgeInsets = UIEdgeInsetsMake(-5, -10, -5, -25);
[self addSubview:_rightSegmentView];

View file

@ -287,6 +287,8 @@ static NSData *base64_decode(NSString *str) {
size_t outlen = block_size;
OSStatus status = noErr;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
status = SecKeyEncrypt(keyRef,
kSecPaddingNone,
srcbuf + idx,
@ -294,6 +296,7 @@ static NSData *base64_decode(NSString *str) {
outbuf,
&outlen
);
#pragma clang diagnostic pop
if (status != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)status);
ret = nil;
@ -343,6 +346,8 @@ static NSData *base64_decode(NSString *str) {
size_t outlen = block_size;
OSStatus status = noErr;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
status = SecKeyDecrypt(keyRef,
kSecPaddingNone,
srcbuf + idx,
@ -350,6 +355,7 @@ static NSData *base64_decode(NSString *str) {
outbuf,
&outlen
);
#pragma clang diagnostic pop
if (status != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)status);
ret = nil;

View file

@ -1722,7 +1722,7 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
}
transition.setPosition(view: self.contentContainer, position: CGRect(origin: CGPoint(), size: size).center)
transition.setBounds(view: self.contentContainer, bounds: CGRect(origin: CGPoint(), size: size))
self.contentContainer.update(size: size, cornerRadius: min(30.0, size.height * 0.5), transition: transition)
self.contentContainer.update(size: size, cornerRadius: min(30.0, size.height * 0.5), isDark: presentationData.theme.overallDarkAppearance, transition: transition)
//let backgroundContainerFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -self.backgroundContainerInset, dy: -self.backgroundContainerInset)

View file

@ -818,7 +818,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let transitionInfo = reference.transitionInfo() {
if let referenceView = transitionInfo.referenceView as? ContextExtractableContainer {
if #available(iOS 26.0, *) {
contextExtractableContainer = (referenceView, convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view))
if transitionInfo.referenceView.bounds.width == transitionInfo.referenceView.bounds.height {
contextExtractableContainer = (referenceView, convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view))
}
}
}

View file

@ -310,6 +310,8 @@ public class GlassBackgroundView: UIView {
}
private let legacyView: LegacyGlassView?
private let legacyHighlightContainerView: UIView?
private var glassHighlightRecognizer: GlassHighlightGestureRecognizer?
private let nativeView: UIVisualEffectView?
private let nativeViewClippingContext: ClippingShapeContext?
@ -339,6 +341,7 @@ public class GlassBackgroundView: UIView {
public override init(frame: CGRect) {
if #available(iOS 26.0, *), !GlassBackgroundView.useCustomGlassImpl {
self.legacyView = nil
self.legacyHighlightContainerView = nil
let glassEffect = UIGlassEffect(style: .regular)
glassEffect.isInteractive = false
@ -355,6 +358,10 @@ public class GlassBackgroundView: UIView {
self.shadowView = nil
} else {
self.legacyView = LegacyGlassView(frame: CGRect())
let legacyHighlightContainerView = UIView()
legacyHighlightContainerView.isUserInteractionEnabled = false
legacyHighlightContainerView.clipsToBounds = true
self.legacyHighlightContainerView = legacyHighlightContainerView
self.nativeView = nil
self.nativeViewClippingContext = nil
self.nativeParamsView = nil
@ -384,18 +391,29 @@ public class GlassBackgroundView: UIView {
}
if let legacyView = self.legacyView {
self.addSubview(legacyView)
let glassHighlightRecognizer = GlassHighlightGestureRecognizer(target: self, action: #selector(self.onHighlightGesture(_:)))
glassHighlightRecognizer.highlightContainerView = self.legacyHighlightContainerView
self.glassHighlightRecognizer = glassHighlightRecognizer
self.addGestureRecognizer(glassHighlightRecognizer)
glassHighlightRecognizer.isEnabled = false
}
if let foregroundView = self.foregroundView {
self.addSubview(foregroundView)
foregroundView.mask = self.maskContainerView
}
self.addSubview(self.contentContainer)
if let legacyHighlightContainerView = self.legacyHighlightContainerView {
self.addSubview(legacyHighlightContainerView)
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func onHighlightGesture(_ recognizer: GlassHighlightGestureRecognizer) {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.isUserInteractionEnabled {
return nil
@ -421,6 +439,10 @@ public class GlassBackgroundView: UIView {
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) {
let shape: Shape = .roundedRect(cornerRadius: cornerRadius)
if let glassHighlightRecognizer = self.glassHighlightRecognizer {
glassHighlightRecognizer.isEnabled = isInteractive
}
if let nativeView = self.nativeView, let nativeViewClippingContext = self.nativeViewClippingContext, (nativeView.bounds.size != size || nativeViewClippingContext.shape != shape) {
nativeViewClippingContext.update(shape: shape, size: size, transition: transition)
@ -455,6 +477,16 @@ public class GlassBackgroundView: UIView {
}
transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size))
transition.setAlpha(view: legacyView, alpha: isVisible ? 1.0 : 0.0)
transition.setPosition(view: self.contentView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setBounds(view: self.contentView, bounds: CGRect(origin: CGPoint(), size: size))
}
if let legacyHighlightContainerView = self.legacyHighlightContainerView {
transition.setFrame(view: legacyHighlightContainerView, frame: CGRect(origin: CGPoint(), size: size))
switch shape {
case let .roundedRect(cornerRadius):
transition.setCornerRadius(layer: legacyHighlightContainerView.layer, cornerRadius: cornerRadius)
}
}
let shadowInset: CGFloat = 32.0
@ -548,6 +580,9 @@ public class GlassBackgroundView: UIView {
}
}
foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, borderWidthFactor: borderWidthFactor, isDark: isDark, fillColor: fillColor)
#if DEBUG
//foregroundView.image = nil
#endif
transition.setAlpha(view: foregroundView, alpha: isVisible ? 1.0 : 0.0)
} else {
if let nativeParamsView = self.nativeParamsView, let nativeView = self.nativeView {
@ -717,6 +752,31 @@ public final class GlassBackgroundContainerView: UIView {
}
for view in self.contentView.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
#if DEBUG
func findMatrix(layer: CALayer) -> AnyObject? {
for filter in layer.filters ?? [] {
if "\(filter)".contains("vibrantColorMatrix") {
return filter as AnyObject
}
}
for sublayer in layer.sublayers ?? [] {
if let result = findMatrix(layer: sublayer) {
return result
}
}
return nil
}
/*if let filter = findMatrix(layer: self.layer) as? NSObject {
var matrix: [Float32] = .init(repeating: 0, count: 20)
let matrixValues = filter.value(forKey: "inputColorMatrix") as! NSValue
matrixValues.getValue(&matrix, size: 4 * 20)
assert(true)
}*/
#endif
return result
}
}

View file

@ -138,21 +138,40 @@ final class LegacyGlassView: UIView {
}
if previousParams?.style != style {
if let blurFilter = CALayer.blur() {
if let blurFilter = CALayer.blur(), let colorMatrixFilter = CALayer.colorMatrix() {
switch style {
case .clear:
blurFilter.setValue(6.0 as NSNumber, forKey: "inputRadius")
if DeviceMetrics.performance.isGraphicallyCapable {
blurFilter.setValue(2.0 as NSNumber, forKey: "inputRadius")
} else {
blurFilter.setValue(6.0 as NSNumber, forKey: "inputRadius")
}
case .normal:
blurFilter.setValue(2.0 as NSNumber, forKey: "inputRadius")
}
backdropLayer.filters = [blurFilter]
var matrix: [Float32] = [
2.6705, -1.1087999, -0.1117, 0.0, 0.049999997,
-0.3295, 1.8914, -0.111899994, 0.0, 0.049999997,
-0.3297, -1.1084, 2.8881, 0.0, 0.049999997,
0.0, 0.0, 0.0, 1.0, 0.0
]
colorMatrixFilter.setValue(NSValue(bytes: &matrix, objCType: "{CAColorMatrix=ffffffffffffffffffff}"), forKey: "inputColorMatrix")
colorMatrixFilter.setValue(true as NSNumber, forKey: "inputBackdropAware")
switch style {
case .clear:
backdropLayer.filters = [blurFilter]
case .normal:
backdropLayer.filters = [colorMatrixFilter, blurFilter]
}
}
}
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius)
transition.setFrame(layer: backdropLayer, frame: CGRect(origin: CGPoint(), size: size))
if !"".isEmpty {
if DeviceMetrics.performance.isGraphicallyCapable {
let size = CGSize(width: max(1.0, size.width), height: max(1.0, size.height))
let cornerRadius = min(min(size.width, size.height) * 0.5, cornerRadius)
let displacementMagnitudePoints: CGFloat = 20.0

View file

@ -0,0 +1,352 @@
import Foundation
import UIKit
import Display
final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
var highlightContainerView: UIView?
private var touchEffect: TouchEffect?
private var initialTouchLocation: CGPoint?
weak var touchEffectView: UIView?
var parameters = TouchEffect.Parameters() {
didSet {
self.touchEffect?.setParameters(self.parameters, animated: false)
}
}
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.delegate = self
self.cancelsTouchesInView = false
self.delaysTouchesBegan = false
self.delaysTouchesEnded = false
self.requiresExclusiveTouchType = false
}
override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
override func reset() {
if let touchEffect = self.touchEffect {
touchEffect.setIsTracking(false)
}
self.touchEffect = nil
self.initialTouchLocation = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let view = self.touchEffectView ?? self.view, let touch = touches.first {
let touchLocation = touch.location(in: view)
let touchEffect = TouchEffect(view: view, highlightContainerView: self.highlightContainerView)
touchEffect.setParameters(self.parameters, animated: false)
if let highlightContainerView = self.highlightContainerView {
touchEffect.setTouchLocation(touch.location(in: highlightContainerView), animated: false)
}
touchEffect.setStretchVector(.zero, animated: false)
self.touchEffect = touchEffect
self.initialTouchLocation = touchLocation
touchEffect.setIsTracking(true)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
if let touchEffect = self.touchEffect {
touchEffect.setIsTracking(false)
}
self.touchEffect = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
if let touchEffect = self.touchEffect {
touchEffect.setIsTracking(false)
}
self.touchEffect = nil
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
guard let touchEffect = self.touchEffect,
let view = self.touchEffectView ?? self.view,
let touch = touches.first,
let initialTouchLocation = self.initialTouchLocation else {
return
}
let touchLocation = touch.location(in: view)
if let highlightContainerView = self.highlightContainerView {
touchEffect.setTouchLocation(touch.location(in: highlightContainerView), animated: false)
}
touchEffect.setStretchVector(
CGPoint(
x: touchLocation.x - initialTouchLocation.x,
y: touchLocation.y - initialTouchLocation.y
),
animated: false
)
}
}
final class TouchEffect {
private struct State: Equatable {
var isTracking: Bool
var stretchVector: CGPoint
var touchLocation: CGPoint?
}
struct SpringParameters {
var mass: CGFloat
var stiffness: CGFloat
var damping: CGFloat
var initialVelocity: CGFloat
}
struct Parameters {
var liftOn = SpringParameters(
mass: 1.36,
stiffness: 568.0,
damping: 39.7,
initialVelocity: 0.0
)
var liftOff = SpringParameters(
mass: 2.0,
stiffness: 460.0,
damping: 21.8,
initialVelocity: 0.0
)
var pressedSizeIncrease: CGFloat = 20.0
}
private weak var view: UIView?
private weak var highlightContainerView: UIView?
private let radialHighlightLayer: CAGradientLayer = {
let layer = CAGradientLayer()
layer.type = .radial
let baseGradientAlpha: CGFloat = 0.5
let numSteps = 8
let firstStep = 1
let firstLocation = 0.5
let colors = (0 ..< numSteps).map { i -> UIColor in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value)
}
}
let locations = (0 ..< numSteps).map { i -> CGFloat in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
layer.colors = colors.map(\.cgColor)
layer.locations = locations.map { $0 as NSNumber }
layer.startPoint = CGPoint(x: 0.5, y: 0.5)
layer.endPoint = CGPoint(x: 1.0, y: 1.0)
layer.opacity = 0.0
layer.actions = [
"position": NSNull(),
"bounds": NSNull(),
"opacity": NSNull()
]
return layer
}()
private var state = State(isTracking: false, stretchVector: .zero, touchLocation: nil)
private var appliedState: State?
var parameters = Parameters()
init(view: UIView, highlightContainerView: UIView?) {
self.view = view
self.highlightContainerView = highlightContainerView
if let highlightContainerView {
highlightContainerView.layer.addSublayer(self.radialHighlightLayer)
}
}
deinit {
self.radialHighlightLayer.removeFromSuperlayer()
}
private func currentTransform(for state: State, view: UIView) -> CATransform3D {
let referenceView = self.highlightContainerView ?? view
let viewWidth = max(1.0, referenceView.bounds.width)
let viewHeight = max(1.0, referenceView.bounds.height)
let aspectRatio = viewWidth / viewHeight
let baseScaleX: CGFloat
let baseScaleY: CGFloat
if state.isTracking {
if viewWidth < viewHeight {
baseScaleY = 1.0 + self.parameters.pressedSizeIncrease / viewHeight
baseScaleX = baseScaleY
} else {
baseScaleX = 1.0 + self.parameters.pressedSizeIncrease / viewWidth
baseScaleY = baseScaleX
}
} else {
baseScaleX = 1.0
baseScaleY = 1.0
}
guard state.isTracking else {
return CATransform3DScale(CATransform3DIdentity, baseScaleX, baseScaleY, 1.0)
}
let stretchVector = state.stretchVector
let adjustedX = stretchVector.x / aspectRatio
let length = sqrt(pow(adjustedX, 2) + pow(stretchVector.y, 2))
guard length != 0.0 else {
return CATransform3DScale(CATransform3DIdentity, baseScaleX, baseScaleY, 1.0)
}
let normal = CGPoint(
x: adjustedX / length,
y: stretchVector.y / length
)
let k: CGFloat = -1.0 / ((length / viewHeight) / (5.0 * aspectRatio) + 1.0) + 1.0
let additionalMaxScale = (viewHeight + 16.0 / aspectRatio) / viewHeight - 1.0
let t = additionalMaxScale * k * aspectRatio
let maxOffset: CGFloat = 24.0
if abs(normal.x) > abs(normal.y) {
let diff = abs(normal.x) - abs(normal.y)
var transform = CATransform3DIdentity
transform.m11 = baseScaleX * (1.0 + t * diff)
transform.m22 = baseScaleY * (1.0 / (1.0 + t * diff))
transform.m41 = normal.x * maxOffset * k
transform.m42 = normal.y * maxOffset * k
return transform
} else {
let diff = abs(normal.y) - abs(normal.x)
var transform = CATransform3DIdentity
transform.m11 = baseScaleX * (1.0 / (1.0 + t * diff))
transform.m22 = baseScaleY * (1.0 + t * diff)
transform.m41 = normal.x * maxOffset * k
transform.m42 = normal.y * maxOffset * k
return transform
}
}
private func currentSpringParameters(from previousState: State?, to state: State) -> SpringParameters {
guard let previousState, previousState != state else {
return state.isTracking ? self.parameters.liftOn : self.parameters.liftOff
}
if !previousState.isTracking && state.isTracking {
return self.parameters.liftOn
} else {
return self.parameters.liftOff
}
}
private func updateRadialHighlight(animated: Bool) {
guard self.highlightContainerView != nil else {
return
}
let baseAlpha: Float = 0.1
let targetOpacity: Float = self.state.isTracking ? baseAlpha : 0.0
let size = CGSize(width: 300.0, height: 300.0)
if let touchLocation = self.state.touchLocation {
self.radialHighlightLayer.bounds = CGRect(origin: CGPoint(), size: size)
self.radialHighlightLayer.position = touchLocation
}
if animated {
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = self.radialHighlightLayer.presentation()?.opacity ?? self.radialHighlightLayer.opacity
self.radialHighlightLayer.opacity = targetOpacity
animation.toValue = targetOpacity
animation.duration = self.state.isTracking ? 0.12 : 0.22
animation.timingFunction = CAMediaTimingFunction(name: self.state.isTracking ? .easeOut : .easeInEaseOut)
self.radialHighlightLayer.add(animation, forKey: "opacity")
} else {
self.radialHighlightLayer.opacity = targetOpacity
}
}
func applyCurrentTransform(animated: Bool = true) {
guard let view = self.view else {
return
}
let targetTransform = self.currentTransform(for: self.state, view: view)
if !animated {
view.layer.removeAnimation(forKey: "sublayerTransform")
view.layer.sublayerTransform = targetTransform
self.updateRadialHighlight(animated: false)
self.appliedState = self.state
return
}
let springParameters = self.currentSpringParameters(from: self.appliedState, to: self.state)
let animation = CASpringAnimation(keyPath: "sublayerTransform")
animation.fromValue = NSValue(caTransform3D: view.layer.presentation()?.sublayerTransform ?? view.layer.sublayerTransform)
animation.toValue = NSValue(caTransform3D: targetTransform)
animation.mass = springParameters.mass
animation.stiffness = springParameters.stiffness
animation.damping = springParameters.damping
animation.initialVelocity = springParameters.initialVelocity
animation.duration = animation.settlingDuration
animation.fillMode = .both
animation.isRemovedOnCompletion = false
view.layer.sublayerTransform = targetTransform
view.layer.add(animation, forKey: "sublayerTransform")
self.updateRadialHighlight(animated: true)
self.appliedState = self.state
}
func setParameters(_ parameters: Parameters, animated: Bool = false) {
self.parameters = parameters
self.applyCurrentTransform(animated: animated)
}
func setIsTracking(_ value: Bool, animated: Bool = true) {
let nextState = State(
isTracking: value,
stretchVector: value ? self.state.stretchVector : .zero,
touchLocation: value ? self.state.touchLocation : self.state.touchLocation
)
guard self.state != nextState else {
return
}
self.state = nextState
self.applyCurrentTransform(animated: animated)
}
func setTouchLocation(_ touchLocation: CGPoint, animated: Bool = false) {
let nextState = State(isTracking: self.state.isTracking, stretchVector: self.state.stretchVector, touchLocation: touchLocation)
guard self.state != nextState else {
return
}
self.state = nextState
self.applyCurrentTransform(animated: animated)
}
func setStretchVector(_ stretchVector: CGPoint, animated: Bool = false) {
let nextState = State(isTracking: self.state.isTracking, stretchVector: stretchVector, touchLocation: self.state.touchLocation)
guard self.state != nextState else {
return
}
self.state = nextState
self.applyCurrentTransform(animated: animated)
}
}

View file

@ -251,13 +251,27 @@ public final class HeaderPanelContainerComponent: Component {
self.backgroundContainer.update(size: CGSize(width: backgroundSize.width + 32.0 * 2.0, height: backgroundSize.height + 32.0 * 2.0), isDark: component.theme.overallDarkAppearance, transition: transition)
let backgroundFrame = CGRect(origin: CGPoint(x: 32.0 + sideInset, y: 32.0), size: CGSize(width: size.width - sideInset * 2.0, height: backgroundSize.height))
var panelCount = 0
if component.tabs != nil {
panelCount += 1
}
panelCount += component.panels.count
var cornerRadius: CGFloat = 0.0
if panelCount == 1 {
cornerRadius = backgroundFrame.height * 0.5
} else {
cornerRadius = 20.0
}
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: component.preferClearGlass ? .clear : .panel), isInteractive: true, transition: transition)
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: cornerRadius, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: component.preferClearGlass ? .clear : .panel), isInteractive: true, transition: transition)
transition.setAlpha(view: self.backgroundContainer, alpha: (component.tabs != nil || !component.panels.isEmpty) ? 1.0 : 0.0)
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
self.contentContainer.layer.cornerRadius = 20.0
transition.setCornerRadius(layer: self.contentContainer.layer, cornerRadius: min(cornerRadius, backgroundFrame.height * 0.5))
return size
}

View file

@ -4,6 +4,7 @@ import Display
import ComponentFlow
import Display
import UIKitRuntimeUtils
import GlassBackgroundComponent
@inline(__always)
private func getMethod<T>(object: NSObject, selector: String) -> T? {
@ -98,13 +99,11 @@ public protocol LensTransitionContainerEffectView: UIView {
}
public protocol LensTransitionContainerProtocol: UIView {
var effectView: LensTransitionContainerEffectView { get }
var contentsEffectView: UIView { get }
var contentsView: UIView { get }
func animateIn(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView)
func animateOut(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView)
func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition)
func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, transition: ComponentTransition)
}
@available(iOS 26.0, *)
@ -228,7 +227,7 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
self.cancelAnimationsRecursively(layer: sdfElementLayer)
}
}
private struct TransitionKeyframes {
let bakedSizes: [CGSize]
let bakedPositions: [CGPoint]
@ -242,7 +241,7 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
private func makeForwardTransitionKeyframes(fromRect: CGRect, toRect: CGRect, toCornerRadius: CGFloat) -> TransitionKeyframes {
let sourceMaxEdgeDistance: CGFloat = 20.0
let sourceSuckDurationFraction: CGFloat = 0.9
let sourceFinalInsideDistance: CGFloat = -8.0
let sourceFinalFurthestInsideDistance: CGFloat = -8.0
let sourceFinalInsideStartFraction: CGFloat = 0.65
let sourceFullInsideInset: CGFloat = max(1.0, min(fromRect.width, fromRect.height) * 0.5)
let sampleCount = 30
@ -257,7 +256,6 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
let centerLineLength = hypot(centerLineDelta.x, centerLineDelta.y)
let centerLineDirection: CGPoint = centerLineLength > 1e-6 ? CGPoint(x: centerLineDelta.x / centerLineLength, y: centerLineDelta.y / centerLineLength) : CGPoint(x: 1.0, y: 0.0)
let centerLineMinDistance: CGFloat = -centerLineLength
let centerLineMaxDistance: CGFloat = 0.0
func isPointInsideRoundedRect(point: CGPoint, rectCenter: CGPoint, rectSize: CGSize, cornerRadius: CGFloat) -> Bool {
let halfWidth = rectSize.width * 0.5
@ -386,6 +384,72 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
return CGPoint(x: rectCenter.x + nearestLocal.x, y: rectCenter.y + nearestLocal.y)
}
func boundaryPointOnRoundedRectRay(rectCenter: CGPoint, rectSize: CGSize, cornerRadius: CGFloat, direction: CGPoint) -> CGPoint {
let halfWidth = rectSize.width * 0.5
let halfHeight = rectSize.height * 0.5
if halfWidth <= 0.0 || halfHeight <= 0.0 {
return rectCenter
}
let radius = max(0.0, min(cornerRadius, min(halfWidth, halfHeight)))
let innerX = max(0.0, halfWidth - radius)
let innerY = max(0.0, halfHeight - radius)
let dirLength = hypot(direction.x, direction.y)
let dir: CGPoint
if dirLength > 1e-6 {
dir = CGPoint(x: direction.x / dirLength, y: direction.y / dirLength)
} else {
dir = CGPoint(x: 1.0, y: 0.0)
}
let dx = abs(dir.x)
let dy = abs(dir.y)
@inline(__always)
func signedPoint(_ x: CGFloat, _ y: CGFloat, _ direction: CGPoint) -> CGPoint {
let sx: CGFloat = direction.x >= 0.0 ? 1.0 : -1.0
let sy: CGFloat = direction.y >= 0.0 ? 1.0 : -1.0
return CGPoint(x: x * sx, y: y * sy)
}
if dx > 1e-6 {
let tVertical = halfWidth / dx
let yAtVertical = tVertical * dy
if yAtVertical <= innerY + 1e-6 {
let local = signedPoint(halfWidth, yAtVertical, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
}
if dy > 1e-6 {
let tHorizontal = halfHeight / dy
let xAtHorizontal = tHorizontal * dx
if xAtHorizontal <= innerX + 1e-6 {
let local = signedPoint(xAtHorizontal, halfHeight, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
}
if radius <= 1e-6 {
let tx = dx > 1e-6 ? (halfWidth / dx) : CGFloat.greatestFiniteMagnitude
let ty = dy > 1e-6 ? (halfHeight / dy) : CGFloat.greatestFiniteMagnitude
let t = min(tx, ty)
let local = signedPoint(dx * t, dy * t, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
let a = dx * dx + dy * dy
let b = -2.0 * (dx * innerX + dy * innerY)
let c = innerX * innerX + innerY * innerY - radius * radius
let discriminant = max(0.0, b * b - 4.0 * a * c)
let sqrtDiscriminant = sqrt(discriminant)
let t1 = (-b - sqrtDiscriminant) / (2.0 * a)
let t2 = (-b + sqrtDiscriminant) / (2.0 * a)
let tCorner = max(0.0, max(t1, t2))
let cornerLocal = signedPoint(dx * tCorner, dy * tCorner, dir)
return CGPoint(x: rectCenter.x + cornerLocal.x, y: rectCenter.y + cornerLocal.y)
}
var bakedSizes: [CGSize] = []
var bakedPositions: [CGPoint] = []
var localPositions: [CGPoint] = []
@ -444,14 +508,11 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
}
let lineDirection = centerLineDirection
let nearestEdgePoint = nearestBoundaryPointOnRoundedRect(
point: CGPoint(
x: blobCenter.x + lineDirection.x * 10000.0,
y: blobCenter.y + lineDirection.y * 10000.0
),
let nearestEdgePoint = boundaryPointOnRoundedRectRay(
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
cornerRadius: scaledCornerRadius,
direction: lineDirection
)
let normalizedSuckProgress = max(0.0, min(1.0, t / sourceSuckDurationFraction))
@ -462,17 +523,20 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
let normalizedFinalInsideProgress = max(0.0, min(1.0, (t - sourceFinalInsideStartFraction) / max(0.001, 1.0 - sourceFinalInsideStartFraction)))
let oneMinusFinalInsideProgress = 1.0 - normalizedFinalInsideProgress
let finalInsideEase = 1.0 - oneMinusFinalInsideProgress * oneMinusFinalInsideProgress * oneMinusFinalInsideProgress
let sourceInsetDistance = baseSourceInsetDistance + (sourceFinalInsideDistance - baseSourceInsetDistance) * finalInsideEase
let sourceHalfWidth = fromRect.width * 0.5
let sourceHalfHeight = fromRect.height * 0.5
let sourceHalfExtentAlongRay = abs(lineDirection.x) * sourceHalfWidth + abs(lineDirection.y) * sourceHalfHeight
let sourceFinalInsideDistance = sourceFinalFurthestInsideDistance - sourceHalfExtentAlongRay * 2.0
let sourceInsetDistance = baseSourceInsetDistance + (sourceFinalInsideDistance - baseSourceInsetDistance) * finalInsideEase
let sourceCenterDistance = sourceInsetDistance + sourceHalfExtentAlongRay
let sourcePosition = CGPoint(
x: nearestEdgePoint.x + lineDirection.x * sourceCenterDistance,
y: nearestEdgePoint.y + lineDirection.y * sourceCenterDistance
)
let centerLineDistance = (sourcePosition.x - fromCenter.x) * centerLineDirection.x + (sourcePosition.y - fromCenter.y) * centerLineDirection.y
let clampedCenterLineDistance = max(centerLineMinDistance, min(centerLineMaxDistance, centerLineDistance))
let centerLineOutwardAllowance = max(0.0, centerLineDistance) * finalInsideEase
let centerLineUpperBound = sourceInsetDistance < 0.0 ? centerLineOutwardAllowance : 0.0
let clampedCenterLineDistance = max(centerLineMinDistance, min(centerLineUpperBound, centerLineDistance))
var projectedSourcePosition = CGPoint(
x: fromCenter.x + centerLineDirection.x * clampedCenterLineDistance,
y: fromCenter.y + centerLineDirection.y * clampedCenterLineDistance
@ -481,26 +545,19 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
x: projectedSourcePosition.x - centerLineDirection.x * sourceHalfExtentAlongRay,
y: projectedSourcePosition.y - centerLineDirection.y * sourceHalfExtentAlongRay
)
let sourceNearestBoundaryPoint = nearestBoundaryPointOnRoundedRect(
point: sourceNearestPoint,
let sourceNearestBoundaryPoint = boundaryPointOnRoundedRectRay(
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
cornerRadius: scaledCornerRadius,
direction: lineDirection
)
let sourceNearestDistance = hypot(
sourceNearestPoint.x - sourceNearestBoundaryPoint.x,
sourceNearestPoint.y - sourceNearestBoundaryPoint.y
)
let sourceNearestSignedDistance: CGFloat = isPointInsideRoundedRect(
point: sourceNearestPoint,
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
) ? -sourceNearestDistance : sourceNearestDistance
let sourceNearestSignedDistance: CGFloat =
(sourceNearestPoint.x - sourceNearestBoundaryPoint.x) * lineDirection.x +
(sourceNearestPoint.y - sourceNearestBoundaryPoint.y) * lineDirection.y
let centerCorrection = sourceNearestSignedDistance - sourceInsetDistance
if abs(centerCorrection) > 0.01 {
let correctedCenterLineDistance = centerLineDistance - centerCorrection
let clampedCorrectedCenterLineDistance = max(centerLineMinDistance, min(centerLineMaxDistance, correctedCenterLineDistance))
let clampedCorrectedCenterLineDistance = max(centerLineMinDistance, min(centerLineUpperBound, correctedCenterLineDistance))
projectedSourcePosition = CGPoint(
x: fromCenter.x + centerLineDirection.x * clampedCorrectedCenterLineDistance,
y: fromCenter.y + centerLineDirection.y * clampedCorrectedCenterLineDistance
@ -551,7 +608,7 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
let sourceMaxEdgeDistance: CGFloat = 20.0
let sourceSuckDurationFraction: CGFloat = 0.9
let sourceFinalInsideDistance: CGFloat = -8.0
let sourceFinalFurthestInsideDistance: CGFloat = -8.0
let sourceFinalInsideStartFraction: CGFloat = 0.65
let sourceFullInsideInset: CGFloat = max(1.0, min(fromRect.width, fromRect.height) * 0.5)
let sampleCount = 30
@ -566,7 +623,6 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
let centerLineLength = hypot(centerLineDelta.x, centerLineDelta.y)
let centerLineDirection: CGPoint = centerLineLength > 1e-6 ? CGPoint(x: centerLineDelta.x / centerLineLength, y: centerLineDelta.y / centerLineLength) : CGPoint(x: 1.0, y: 0.0)
let centerLineMinDistance: CGFloat = -centerLineLength
let centerLineMaxDistance: CGFloat = 0.0
func isPointInsideRoundedRect(point: CGPoint, rectCenter: CGPoint, rectSize: CGSize, cornerRadius: CGFloat) -> Bool {
let halfWidth = rectSize.width * 0.5
@ -698,6 +754,72 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
return CGPoint(x: rectCenter.x + nearestLocal.x, y: rectCenter.y + nearestLocal.y)
}
func boundaryPointOnRoundedRectRay(rectCenter: CGPoint, rectSize: CGSize, cornerRadius: CGFloat, direction: CGPoint) -> CGPoint {
let halfWidth = rectSize.width * 0.5
let halfHeight = rectSize.height * 0.5
if halfWidth <= 0.0 || halfHeight <= 0.0 {
return rectCenter
}
let radius = max(0.0, min(cornerRadius, min(halfWidth, halfHeight)))
let innerX = max(0.0, halfWidth - radius)
let innerY = max(0.0, halfHeight - radius)
let dirLength = hypot(direction.x, direction.y)
let dir: CGPoint
if dirLength > 1e-6 {
dir = CGPoint(x: direction.x / dirLength, y: direction.y / dirLength)
} else {
dir = CGPoint(x: 1.0, y: 0.0)
}
let dx = abs(dir.x)
let dy = abs(dir.y)
@inline(__always)
func signedPoint(_ x: CGFloat, _ y: CGFloat, _ direction: CGPoint) -> CGPoint {
let sx: CGFloat = direction.x >= 0.0 ? 1.0 : -1.0
let sy: CGFloat = direction.y >= 0.0 ? 1.0 : -1.0
return CGPoint(x: x * sx, y: y * sy)
}
if dx > 1e-6 {
let tVertical = halfWidth / dx
let yAtVertical = tVertical * dy
if yAtVertical <= innerY + 1e-6 {
let local = signedPoint(halfWidth, yAtVertical, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
}
if dy > 1e-6 {
let tHorizontal = halfHeight / dy
let xAtHorizontal = tHorizontal * dx
if xAtHorizontal <= innerX + 1e-6 {
let local = signedPoint(xAtHorizontal, halfHeight, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
}
if radius <= 1e-6 {
let tx = dx > 1e-6 ? (halfWidth / dx) : CGFloat.greatestFiniteMagnitude
let ty = dy > 1e-6 ? (halfHeight / dy) : CGFloat.greatestFiniteMagnitude
let t = min(tx, ty)
let local = signedPoint(dx * t, dy * t, dir)
return CGPoint(x: rectCenter.x + local.x, y: rectCenter.y + local.y)
}
let a = dx * dx + dy * dy
let b = -2.0 * (dx * innerX + dy * innerY)
let c = innerX * innerX + innerY * innerY - radius * radius
let discriminant = max(0.0, b * b - 4.0 * a * c)
let sqrtDiscriminant = sqrt(discriminant)
let t1 = (-b - sqrtDiscriminant) / (2.0 * a)
let t2 = (-b + sqrtDiscriminant) / (2.0 * a)
let tCorner = max(0.0, max(t1, t2))
let cornerLocal = signedPoint(dx * tCorner, dy * tCorner, dir)
return CGPoint(x: rectCenter.x + cornerLocal.x, y: rectCenter.y + cornerLocal.y)
}
var bakedSizes: [CGSize] = []
var bakedPositions: [CGPoint] = []
var localPositions: [CGPoint] = []
@ -757,14 +879,11 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
}
let lineDirection = centerLineDirection
let nearestEdgePoint = nearestBoundaryPointOnRoundedRect(
point: CGPoint(
x: blobCenter.x + lineDirection.x * 10000.0,
y: blobCenter.y + lineDirection.y * 10000.0
),
let nearestEdgePoint = boundaryPointOnRoundedRectRay(
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
cornerRadius: scaledCornerRadius,
direction: lineDirection
)
let normalizedSuckProgress = max(0.0, min(1.0, t / sourceSuckDurationFraction))
@ -775,17 +894,20 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
let normalizedFinalInsideProgress = max(0.0, min(1.0, (t - sourceFinalInsideStartFraction) / max(0.001, 1.0 - sourceFinalInsideStartFraction)))
let oneMinusFinalInsideProgress = 1.0 - normalizedFinalInsideProgress
let finalInsideEase = 1.0 - oneMinusFinalInsideProgress * oneMinusFinalInsideProgress * oneMinusFinalInsideProgress
let sourceInsetDistance = baseSourceInsetDistance + (sourceFinalInsideDistance - baseSourceInsetDistance) * finalInsideEase
let sourceHalfWidth = fromRect.width * 0.5
let sourceHalfHeight = fromRect.height * 0.5
let sourceHalfExtentAlongRay = abs(lineDirection.x) * sourceHalfWidth + abs(lineDirection.y) * sourceHalfHeight
let sourceFinalInsideDistance = sourceFinalFurthestInsideDistance - sourceHalfExtentAlongRay * 2.0
let sourceInsetDistance = baseSourceInsetDistance + (sourceFinalInsideDistance - baseSourceInsetDistance) * finalInsideEase
let sourceCenterDistance = sourceInsetDistance + sourceHalfExtentAlongRay
let sourcePosition = CGPoint(
x: nearestEdgePoint.x + lineDirection.x * sourceCenterDistance,
y: nearestEdgePoint.y + lineDirection.y * sourceCenterDistance
)
let centerLineDistance = (sourcePosition.x - fromCenter.x) * centerLineDirection.x + (sourcePosition.y - fromCenter.y) * centerLineDirection.y
let clampedCenterLineDistance = max(centerLineMinDistance, min(centerLineMaxDistance, centerLineDistance))
let centerLineOutwardAllowance = max(0.0, centerLineDistance) * finalInsideEase
let centerLineUpperBound = sourceInsetDistance < 0.0 ? centerLineOutwardAllowance : 0.0
let clampedCenterLineDistance = max(centerLineMinDistance, min(centerLineUpperBound, centerLineDistance))
var projectedSourcePosition = CGPoint(
x: fromCenter.x + centerLineDirection.x * clampedCenterLineDistance,
y: fromCenter.y + centerLineDirection.y * clampedCenterLineDistance
@ -794,26 +916,19 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
x: projectedSourcePosition.x - centerLineDirection.x * sourceHalfExtentAlongRay,
y: projectedSourcePosition.y - centerLineDirection.y * sourceHalfExtentAlongRay
)
let sourceNearestBoundaryPoint = nearestBoundaryPointOnRoundedRect(
point: sourceNearestPoint,
let sourceNearestBoundaryPoint = boundaryPointOnRoundedRectRay(
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
cornerRadius: scaledCornerRadius,
direction: lineDirection
)
let sourceNearestDistance = hypot(
sourceNearestPoint.x - sourceNearestBoundaryPoint.x,
sourceNearestPoint.y - sourceNearestBoundaryPoint.y
)
let sourceNearestSignedDistance: CGFloat = isPointInsideRoundedRect(
point: sourceNearestPoint,
rectCenter: blobCenter,
rectSize: scaledSize,
cornerRadius: scaledCornerRadius
) ? -sourceNearestDistance : sourceNearestDistance
let sourceNearestSignedDistance: CGFloat =
(sourceNearestPoint.x - sourceNearestBoundaryPoint.x) * lineDirection.x +
(sourceNearestPoint.y - sourceNearestBoundaryPoint.y) * lineDirection.y
let centerCorrection = sourceNearestSignedDistance - sourceInsetDistance
if abs(centerCorrection) > 0.01 {
let correctedCenterLineDistance = centerLineDistance - centerCorrection
let clampedCorrectedCenterLineDistance = max(centerLineMinDistance, min(centerLineMaxDistance, correctedCenterLineDistance))
let clampedCorrectedCenterLineDistance = max(centerLineMinDistance, min(centerLineUpperBound, correctedCenterLineDistance))
projectedSourcePosition = CGPoint(
x: fromCenter.x + centerLineDirection.x * clampedCorrectedCenterLineDistance,
y: fromCenter.y + centerLineDirection.y * clampedCorrectedCenterLineDistance
@ -996,20 +1111,13 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
self.setIsFilterActive(isFilterActive: true)
sourceEffectView.setTransitionFraction(value: 0.0, duration: 0.0)
sourceEffectView.setTransitionFraction(value: 1.0, duration: 0.3)
//sourceEffectView.backgroundColor = .blue
let sourceMaxEdgeDistance: CGFloat = 20.0
let sourceEaseOutDurationFraction: CGFloat = 0.35
let sourceCenterPullStartFraction: CGFloat
let sourceCenterPullDurationFraction: CGFloat
let centerDistance = sqrt(pow(fromRect.midX - toRect.midX, 2.0) + pow(fromRect.midY - toRect.midY, 2.0))
if centerDistance >= 100.0 {
sourceCenterPullStartFraction = 0.2
sourceCenterPullDurationFraction = 0.3
} else {
sourceCenterPullStartFraction = 0.0
sourceCenterPullDurationFraction = 0.0
}
let sourceSuckDurationFraction: CGFloat = 0.9
let sourceFinalFurthestInsideDistance: CGFloat = -8.0
let sourceFinalInsideStartFraction: CGFloat = 0.65
let sourceFullInsideInset: CGFloat = max(1.0, min(fromRect.width, fromRect.height) * 0.5)
let sampleCount = 36
let sampleEndIndex = CGFloat(sampleCount - 1)
@ -1158,24 +1266,47 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
cornerRadius: radius,
direction: centerLineDirection
)
let outwardDirection = normalized(
CGPoint(
x: sourceBoundaryPoint.x - centerLinePosition.x,
y: sourceBoundaryPoint.y - centerLinePosition.y
)
)
let normalizedSourceProgress = max(0.0, min(1.0, t / sourceEaseOutDurationFraction))
let oneMinusSourceProgress = 1.0 - normalizedSourceProgress
let sourceEaseOut = 1.0 - oneMinusSourceProgress * oneMinusSourceProgress * oneMinusSourceProgress
let sourceEdgeOffset = lerp(-sourceMaxEdgeDistance, sourceMaxEdgeDistance, sourceEaseOut)
let sourceEdgePosition = CGPoint(
x: sourceBoundaryPoint.x + outwardDirection.x * sourceEdgeOffset,
y: sourceBoundaryPoint.y + outwardDirection.y * sourceEdgeOffset
)
let normalizedCenterPullProgress = max(0.0, min(1.0, (t - sourceCenterPullStartFraction) / max(0.001, sourceCenterPullDurationFraction)))
let oneMinusCenterPull = 1.0 - normalizedCenterPullProgress
let lineDirection = centerLineDirection
let sourceHalfWidth = fromRect.width * 0.5
let sourceHalfHeight = fromRect.height * 0.5
let sourceHalfExtentAlongRay = abs(lineDirection.x) * sourceHalfWidth + abs(lineDirection.y) * sourceHalfHeight
let reverseT = 1.0 - t
let normalizedSuckProgress = max(0.0, min(1.0, reverseT / sourceSuckDurationFraction))
let oneMinusSuckProgress = 1.0 - normalizedSuckProgress
let suckFraction = 1.0 - oneMinusSuckProgress * oneMinusSuckProgress * oneMinusSuckProgress
let reverseAnimatedInsetDistance = sourceMaxEdgeDistance - suckFraction * (sourceMaxEdgeDistance + sourceFullInsideInset)
let normalizedFinalInsideProgress = max(0.0, min(1.0, (reverseT - sourceFinalInsideStartFraction) / max(0.001, 1.0 - sourceFinalInsideStartFraction)))
let oneMinusFinalInsideProgress = 1.0 - normalizedFinalInsideProgress
let finalInsideEase = 1.0 - oneMinusFinalInsideProgress * oneMinusFinalInsideProgress * oneMinusFinalInsideProgress
let sourceFinalInsideDistance = sourceFinalFurthestInsideDistance - sourceHalfExtentAlongRay * 2.0
let reverseInsetDistance = reverseAnimatedInsetDistance + (sourceFinalInsideDistance - reverseAnimatedInsetDistance) * finalInsideEase
let oneMinusCenterPull = 1.0 - t
let centerPullEase = 1.0 - oneMinusCenterPull * oneMinusCenterPull * oneMinusCenterPull
sourcePositions.append(lerpPoint(sourceEdgePosition, centerLinePosition, centerPullEase))
let sourceBoundaryDistanceFromCenter =
(sourceBoundaryPoint.x - centerLinePosition.x) * lineDirection.x +
(sourceBoundaryPoint.y - centerLinePosition.y) * lineDirection.y
let sourceCenterInsetDistance = -(sourceBoundaryDistanceFromCenter + sourceHalfExtentAlongRay)
let sourceInsetDistance = lerp(reverseInsetDistance, sourceCenterInsetDistance, centerPullEase)
let sourceCenterDistance = sourceInsetDistance + sourceHalfExtentAlongRay
var projectedSourcePosition = CGPoint(
x: sourceBoundaryPoint.x + lineDirection.x * sourceCenterDistance,
y: sourceBoundaryPoint.y + lineDirection.y * sourceCenterDistance
)
let sourceNearestPoint = CGPoint(
x: projectedSourcePosition.x - lineDirection.x * sourceHalfExtentAlongRay,
y: projectedSourcePosition.y - lineDirection.y * sourceHalfExtentAlongRay
)
let sourceNearestSignedDistance =
(sourceNearestPoint.x - sourceBoundaryPoint.x) * lineDirection.x +
(sourceNearestPoint.y - sourceBoundaryPoint.y) * lineDirection.y
if sourceNearestSignedDistance > sourceMaxEdgeDistance {
let centerCorrection = sourceNearestSignedDistance - sourceMaxEdgeDistance
projectedSourcePosition = CGPoint(
x: projectedSourcePosition.x - lineDirection.x * centerCorrection,
y: projectedSourcePosition.y - lineDirection.y * centerCorrection
)
}
sourcePositions.append(projectedSourcePosition)
}
if let finalContainerPosition = containerPositions.last {
@ -1314,7 +1445,7 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
)
}
public func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, transition: ComponentTransition) {
let bounds = CGRect(origin: .zero, size: size)
let center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
@ -1342,24 +1473,18 @@ final class LensTransitionContainerImpl: UIView, LensTransitionContainerProtocol
}
private final class LensTransitionContainerFallbackImpl: UIView, LensTransitionContainerProtocol {
let effectView: LensTransitionContainerEffectView
private let containerView: UIView
let contentsEffectView: UIView
let contentsView: UIView
private let backgroundView: GlassBackgroundView
init(effectView: LensTransitionContainerEffectView) {
self.effectView = effectView
self.containerView = UIView()
self.contentsEffectView = UIView()
self.contentsView = UIView()
public var contentsView: UIView {
return self.backgroundView.contentView
}
override init(frame: CGRect) {
self.backgroundView = GlassBackgroundView()
super.init(frame: .zero)
super.init(frame: frame)
self.addSubview(self.containerView)
self.containerView.addSubview(self.effectView)
self.containerView.addSubview(self.contentsEffectView)
self.contentsEffectView.addSubview(self.contentsView)
self.contentsView.clipsToBounds = true
self.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
@ -1367,59 +1492,42 @@ private final class LensTransitionContainerFallbackImpl: UIView, LensTransitionC
}
func animateIn(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView) {
self.update(size: fromRect.size, cornerRadius: fromCornerRadius, transition: .immediate)
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseInOut], animations: {
self.containerView.center = CGPoint(x: toRect.midX, y: toRect.midY)
self.update(size: toRect.size, cornerRadius: toCornerRadius, transition: .immediate)
})
}
func animateOut(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView) {
self.update(size: fromRect.size, cornerRadius: fromCornerRadius, transition: .immediate)
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseInOut], animations: {
self.containerView.center = CGPoint(x: toRect.midX, y: toRect.midY)
self.update(size: toRect.size, cornerRadius: toCornerRadius, transition: .immediate)
})
}
func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
transition.setBounds(view: self.containerView, bounds: CGRect(origin: .zero, size: size))
transition.setPosition(view: self.containerView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setBounds(view: self.contentsEffectView, bounds: CGRect(origin: .zero, size: size))
transition.setPosition(view: self.contentsEffectView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setBounds(view: self.contentsView, bounds: CGRect(origin: .zero, size: size))
transition.setPosition(view: self.contentsView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setCornerRadius(layer: self.contentsView.layer, cornerRadius: cornerRadius)
transition.setBounds(view: self.effectView, bounds: CGRect(origin: .zero, size: size))
transition.setPosition(view: self.effectView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
self.effectView.updateCornerRadius(duration: 0.0, keyframes: [cornerRadius])
func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, transition: ComponentTransition) {
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: size))
transition.setPosition(view: self.backgroundView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
self.backgroundView.update(size: size, cornerRadius: cornerRadius, isDark: isDark, tintColor: .init(kind: .panel), transition: transition)
}
}
public final class LensTransitionContainer: UIView {
private let impl: (UIView & LensTransitionContainerProtocol)
public var effectView: LensTransitionContainerEffectView {
return self.impl.effectView
}
public var contentsEffectView: UIView {
return self.impl.contentsEffectView
public var effectView: UIView? {
if #available(iOS 26.0, *) {
if let impl = self.impl as? LensTransitionContainerImpl {
return impl.effectView
} else {
return nil
}
} else {
return nil
}
}
public var contentsView: UIView {
return self.impl.contentsView
return impl.contentsView
}
public init(effectView: LensTransitionContainerEffectView) {
if #available(iOS 26.0, *) {
self.impl = LensTransitionContainerImpl(effectView: effectView)
} else {
self.impl = LensTransitionContainerFallbackImpl(effectView: effectView)
self.impl = LensTransitionContainerFallbackImpl(frame: CGRect())
}
super.init(frame: .zero)
@ -1443,8 +1551,8 @@ public final class LensTransitionContainer: UIView {
self.impl.animateOut(fromRect: fromRect, toRect: toRect, fromCornerRadius: fromCornerRadius, toCornerRadius: toCornerRadius, isDark: isDark, sourceEffectView: sourceEffectView)
}
public func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
self.impl.update(size: size, cornerRadius: cornerRadius, transition: transition)
public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, transition: ComponentTransition) {
self.impl.update(size: size, cornerRadius: cornerRadius, isDark: isDark, transition: transition)
}
}

View file

@ -662,7 +662,7 @@ extension ChatControllerImpl {
guard let self else {
return
}
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, self.presentationInterfaceState.persistentData.topicListPanelLocation == true, self.presentationInterfaceState.chatLocation.threadId != nil {
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, self.presentationInterfaceState.persistentData.topicListPanelLocation == .side, self.presentationInterfaceState.chatLocation.threadId != nil {
self.updateChatLocationThread(threadId: nil, animationDirection: .left)
} else {
if self.attemptNavigation({ [weak self] in
@ -4540,7 +4540,14 @@ extension ChatControllerImpl {
}
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { presentationInterfaceState in
var persistentData = presentationInterfaceState.persistentData
persistentData.topicListPanelLocation = !persistentData.topicListPanelLocation
switch persistentData.topicListPanelLocation {
case .top:
persistentData.topicListPanelLocation = .side
case .side:
persistentData.topicListPanelLocation = .bottom
case .bottom:
persistentData.topicListPanelLocation = .top
}
return presentationInterfaceState.updatedPersistentData(persistentData)
})
}, updateDisplayHistoryFilterAsList: { [weak self] displayAsList in

View file

@ -249,6 +249,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
private var floatingTopicsPanelContainer: ChatControllerTitlePanelNodeContainer
private var floatingTopicsPanel: (view: ComponentView<ChatSidePanelEnvironment>, component: ChatFloatingTopicsPanel)?
private var headerPanelsView: ComponentView<Empty>?
private var footerPanelsView: ComponentView<Empty>?
private var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
private var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
@ -762,6 +763,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.usePlainInputSeparator = false
self.plainInputSeparatorAlpha = nil
}
self.inputPanelBackgroundNode.isUserInteractionEnabled = false
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme, preferClearGlass: self.chatPresentationInterfaceState.preferredGlassType == .clear, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, backgroundNode: self.backgroundNode, isChatRotated: historyNodeRotated)
self.navigateButtons.accessibilityElementsHidden = true
@ -1347,13 +1349,19 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight)
var headerPanels: [HeaderPanelContainerComponent.Panel] = []
var footerPanels: [HeaderPanelContainerComponent.Panel] = []
if let headerTopicsPanel = headerTopicsPanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
headerPanels.append(HeaderPanelContainerComponent.Panel(
if let headerTopicsPanel = headerTopicsPanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
let panel = HeaderPanelContainerComponent.Panel(
key: "topics",
orderIndex: 0,
component: headerTopicsPanel
))
)
if self.chatPresentationInterfaceState.persistentData.topicListPanelLocation == .top {
headerPanels.append(panel)
} else {
footerPanels.append(panel)
}
}
if let mediaPlayback = self.controller?.globalControlPanelsContextState?.mediaPlayback {
headerPanels.append(HeaderPanelContainerComponent.Panel(
@ -2232,6 +2240,59 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
var containerInsets = insets
if let dismissAsOverlayLayout = self.dismissAsOverlayLayout {
if let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow {
containerInsets = dismissAsOverlayLayout.insets(options: [])
containerInsets.bottom = max(inputNodeHeightAndOverflow.0 + inputNodeHeightAndOverflow.1, insets.bottom)
} else {
containerInsets = dismissAsOverlayLayout.insets(options: [.input])
}
}
var footerPanelsSize: CGSize?
if !footerPanels.isEmpty {
let footerPanelsView: ComponentView<Empty>
var footerPanelsTransition = ComponentTransition(transition)
if let current = self.footerPanelsView {
footerPanelsView = current
} else {
footerPanelsTransition = footerPanelsTransition.withAnimation(.none)
footerPanelsView = ComponentView()
self.footerPanelsView = footerPanelsView
}
var footerPanelsWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right + 16.0
if containerInsets.bottom <= 32.0 {
footerPanelsWidth -= 36.0
}
let footerPanelsSizeValue = footerPanelsView.update(
transition: footerPanelsTransition,
component: AnyComponent(HeaderPanelContainerComponent(
theme: self.chatPresentationInterfaceState.theme,
preferClearGlass: self.chatPresentationInterfaceState.preferredGlassType == .clear,
tabs: nil,
panels: footerPanels
)),
environment: {},
containerSize: CGSize(width: footerPanelsWidth, height: layout.size.height)
)
footerPanelsSize = footerPanelsSizeValue
floatingTopicsPanelInsets.bottom += footerPanelsSizeValue.height
} else if let footerPanelsView = self.footerPanelsView {
self.footerPanelsView = nil
if let footerPanelsComponentView = footerPanelsView.view {
transition.updateAlpha(layer: footerPanelsComponentView.layer, alpha: 0.0, completion: { [weak footerPanelsComponentView] _ in
footerPanelsComponentView?.removeFromSuperview()
})
}
}
if let footerPanelsSize {
inputPanelsHeight += 12.0 + footerPanelsSize.height
}
if self.dismissedAsOverlay {
inputPanelsHeight = 0.0
}
@ -2308,16 +2369,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: scrollContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
}
var containerInsets = insets
if let dismissAsOverlayLayout = self.dismissAsOverlayLayout {
if let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow {
containerInsets = dismissAsOverlayLayout.insets(options: [])
containerInsets.bottom = max(inputNodeHeightAndOverflow.0 + inputNodeHeightAndOverflow.1, insets.bottom)
} else {
containerInsets = dismissAsOverlayLayout.insets(options: [.input])
}
}
let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight + 8.0 + 8.0, right: 0.0)
self.visibleAreaInset = visibleAreaInset
@ -2567,6 +2618,17 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
sidePanelTopInset += headerPanelsSize.height + 2.0
}
if let footerPanelsComponentView = self.footerPanelsView?.view, let footerPanelsSize {
let footerPanelsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - footerPanelsSize.width) * 0.5), y: layout.size.height - (containerInsets.bottom + inputPanelsHeight + 8.0)), size: footerPanelsSize)
var footerPanelsTransition = ComponentTransition(transition)
if footerPanelsComponentView.superview == nil {
footerPanelsTransition.animateAlpha(view: footerPanelsComponentView, from: 0.0, to: 1.0)
footerPanelsTransition = footerPanelsTransition.withAnimation(.none)
self.floatingTopicsPanelContainer.view.addSubview(footerPanelsComponentView)
}
footerPanelsTransition.setFrame(view: footerPanelsComponentView, frame: footerPanelsFrame)
}
let floatingTopicsPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 0.0, height: layout.size.height))
transition.updateFrame(node: self.floatingTopicsPanelContainer, frame: floatingTopicsPanelContainerFrame)
if let floatingTopicsPanel = self.floatingTopicsPanel {
@ -5076,6 +5138,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let headerPanelsComponentView = self.headerPanelsView?.view as? HeaderPanelContainerComponent.View, let topicsPanelView = headerPanelsComponentView.panel(forKey: AnyHashable("topics")) as? ChatTopicsHeaderPanelComponent.View {
leftIndex = topicsPanelView.topicIndex(threadId: fromLocation)
rightIndex = topicsPanelView.topicIndex(threadId: toLocation)
} else if let footerPanelsComponentView = self.footerPanelsView?.view as? HeaderPanelContainerComponent.View, let topicsPanelView = footerPanelsComponentView.panel(forKey: AnyHashable("topics")) as? ChatTopicsHeaderPanelComponent.View {
leftIndex = topicsPanelView.topicIndex(threadId: fromLocation)
rightIndex = topicsPanelView.topicIndex(threadId: toLocation)
}
}
guard let leftIndex, let rightIndex else {

View file

@ -252,7 +252,7 @@ func headerTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterf
}
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), chatPresentationInterfaceState.search == nil {
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if !topicListDisplayModeOnTheSide {
return AnyComponent(ChatTopicsHeaderPanelComponent(
context: context,
@ -282,7 +282,7 @@ func headerTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterf
if !chatPresentationInterfaceState.viewForumAsMessages {
return nil
}
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if !topicListDisplayModeOnTheSide {
return AnyComponent(ChatTopicsHeaderPanelComponent(
context: context,
@ -314,7 +314,7 @@ func headerTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterf
return nil
}
}
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if !topicListDisplayModeOnTheSide {
return AnyComponent(ChatTopicsHeaderPanelComponent(
context: context,
@ -367,7 +367,7 @@ func floatingTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInte
}
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = chatPresentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), chatPresentationInterfaceState.search == nil {
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if topicListDisplayModeOnTheSide {
return ChatFloatingTopicsPanel(
context: context,
@ -399,7 +399,7 @@ func floatingTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInte
if !chatPresentationInterfaceState.viewForumAsMessages {
return nil
}
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if topicListDisplayModeOnTheSide {
return ChatFloatingTopicsPanel(
context: context,
@ -433,7 +433,7 @@ func floatingTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInte
return nil
}
}
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation
let topicListDisplayModeOnTheSide = chatPresentationInterfaceState.persistentData.topicListPanelLocation == .side
if topicListDisplayModeOnTheSide {
return ChatFloatingTopicsPanel(
context: context,

View file

@ -34,6 +34,7 @@ NSObject * _Nullable makeLuminanceToAlphaFilter();
NSObject * _Nullable makeColorInvertFilter();
NSObject * _Nullable makeMonochromeFilter();
NSObject * _Nullable makeDisplacementMapFilter();
NSObject * _Nullable makeColorMatrixFilter();
void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots);
bool getLayerDisableScreenshots(CALayer * _Nonnull layer);

View file

@ -290,6 +290,10 @@ NSObject * _Nullable makeDisplacementMapFilter() {
}
}
NSObject * _Nullable makeColorMatrixFilter() {
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"colorMatrix"];
}
static const void *layerDisableScreenshotsKey = &layerDisableScreenshotsKey;
void setLayerDisableScreenshots(CALayer * _Nonnull layer, bool disableScreenshots) {