mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
4842beb80d
20 changed files with 2531 additions and 301 deletions
|
|
@ -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",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -911,6 +911,10 @@ public extension CALayer {
|
|||
static func displacementMap() -> NSObject? {
|
||||
return makeDisplacementMapFilter()
|
||||
}
|
||||
|
||||
static func colorMatrix() -> NSObject? {
|
||||
return makeColorMatrixFilter()
|
||||
}
|
||||
}
|
||||
|
||||
public extension CALayer {
|
||||
|
|
|
|||
|
|
@ -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:)];
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1406,11 +1406,195 @@ private final class ItemSelectionRecognizer: UIGestureRecognizer {
|
|||
}
|
||||
}
|
||||
|
||||
private final class LensTransitionContainerEffectViewImpl: UIView, LensTransitionContainerEffectView {
|
||||
let glassView: UIVisualEffectView
|
||||
let contentView: UIView?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
init(contentView: UIView?) {
|
||||
self.glassView = UIVisualEffectView()
|
||||
self.contentView = contentView
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.glassView)
|
||||
if let contentView {
|
||||
self.glassView.contentView.addSubview(contentView)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
if #available(iOS 26.0, *) {
|
||||
let glassEffectValue: UIGlassEffect
|
||||
if theme.overallDarkAppearance {
|
||||
glassEffectValue = UIGlassEffect(style: .regular)
|
||||
//glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.025)
|
||||
} else {
|
||||
glassEffectValue = UIGlassEffect(style: .regular)
|
||||
//glassEffectValue.tintColor = UIColor(white: 1.0, alpha: 0.1)
|
||||
}
|
||||
self.glassView.effect = glassEffectValue
|
||||
}
|
||||
}
|
||||
|
||||
func updateSize(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
|
||||
transition.animateView {
|
||||
self.glassView.bounds.size = size
|
||||
self.glassView.center = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
|
||||
if #available(iOS 26.0, *) {
|
||||
self.glassView.cornerConfiguration = .corners(radius: UICornerRadius(floatLiteral: cornerRadius))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateSize(size: CGSize, transition: ComponentTransition) {
|
||||
transition.setBounds(view: self, bounds: CGRect(origin: .zero, size: size))
|
||||
transition.setBounds(view: self.glassView, bounds: CGRect(origin: .zero, size: size))
|
||||
transition.setPosition(view: self.glassView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
|
||||
}
|
||||
|
||||
func updateSize(duration: Double, keyframes: [CGSize]) {
|
||||
guard keyframes.count >= 2 else {
|
||||
if let last = keyframes.last {
|
||||
self.bounds.size = last
|
||||
self.glassView.bounds.size = last
|
||||
self.glassView.center = CGPoint(x: last.width * 0.5, y: last.height * 0.5)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Start value
|
||||
self.bounds.size = keyframes[0]
|
||||
self.glassView.bounds.size = keyframes[0]
|
||||
self.glassView.center = CGPoint(x: keyframes[0].width * 0.5, y: keyframes[0].height * 0.5)
|
||||
|
||||
let segmentCount = keyframes.count - 1
|
||||
let relativeStep = 1.0 / Double(segmentCount)
|
||||
|
||||
var options: UIView.KeyframeAnimationOptions = [.calculationModeLinear]
|
||||
options.insert(UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue))
|
||||
UIView.animateKeyframes(
|
||||
withDuration: duration,
|
||||
delay: 0.0,
|
||||
options: options,
|
||||
animations: {
|
||||
for i in 0..<segmentCount {
|
||||
let nextSize = keyframes[i + 1]
|
||||
let relativeStartTime = Double(i) * relativeStep
|
||||
let relativeDuration = (i == segmentCount - 1) ? (1.0 - relativeStartTime) : relativeStep
|
||||
UIView.addKeyframe(
|
||||
withRelativeStartTime: relativeStartTime,
|
||||
relativeDuration: relativeDuration
|
||||
) {
|
||||
self.bounds.size = nextSize
|
||||
self.glassView.bounds.size = nextSize
|
||||
self.glassView.center = CGPoint(x: nextSize.width * 0.5, y: nextSize.height * 0.5)
|
||||
}
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
func updatePosition(duration: Double, keyframes: [CGPoint]) {
|
||||
guard keyframes.count >= 2 else {
|
||||
if let last = keyframes.last {
|
||||
self.center = last
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.center = keyframes[0]
|
||||
|
||||
let segmentCount = keyframes.count - 1
|
||||
let relativeStep = 1.0 / Double(segmentCount)
|
||||
|
||||
var options: UIView.KeyframeAnimationOptions = [.calculationModeLinear]
|
||||
options.insert(UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue))
|
||||
UIView.animateKeyframes(
|
||||
withDuration: duration,
|
||||
delay: 0.0,
|
||||
options: options,
|
||||
animations: {
|
||||
for i in 0 ..< segmentCount {
|
||||
let nextPosition = keyframes[i + 1]
|
||||
let relativeStartTime = Double(i) * relativeStep
|
||||
let relativeDuration = (i == segmentCount - 1) ? (1.0 - relativeStartTime) : relativeStep
|
||||
UIView.addKeyframe(
|
||||
withRelativeStartTime: relativeStartTime,
|
||||
relativeDuration: relativeDuration
|
||||
) {
|
||||
self.center = nextPosition
|
||||
}
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
func updatePosition(position: CGPoint, transition: ComponentTransition) {
|
||||
transition.setPosition(view: self, position: position)
|
||||
}
|
||||
|
||||
func updateCornerRadius(duration: Double, keyframes: [CGFloat]) {
|
||||
guard #available(iOS 26.0, *) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard keyframes.count >= 2 else {
|
||||
if let last = keyframes.last {
|
||||
self.glassView.cornerConfiguration = .corners(radius: UICornerRadius(floatLiteral: last))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Start value
|
||||
self.glassView.cornerConfiguration = .corners(radius: UICornerRadius(floatLiteral: keyframes[0]))
|
||||
|
||||
let segmentCount = keyframes.count - 1
|
||||
let relativeStep = 1.0 / Double(segmentCount)
|
||||
|
||||
var options: UIView.KeyframeAnimationOptions = [.calculationModeLinear]
|
||||
options.insert(UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue))
|
||||
UIView.animateKeyframes(
|
||||
withDuration: duration,
|
||||
delay: 0.0,
|
||||
options: options,
|
||||
animations: {
|
||||
for i in 0 ..< segmentCount {
|
||||
let nextValue = keyframes[i + 1]
|
||||
let relativeStartTime = Double(i) * relativeStep
|
||||
let relativeDuration = (i == segmentCount - 1) ? (1.0 - relativeStartTime) : relativeStep
|
||||
UIView.addKeyframe(
|
||||
withRelativeStartTime: relativeStartTime,
|
||||
relativeDuration: relativeDuration
|
||||
) {
|
||||
self.glassView.cornerConfiguration = .corners(radius: UICornerRadius(floatLiteral: nextValue))
|
||||
}
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
func setTransitionFraction(value: CGFloat, duration: Double) {
|
||||
let fraction = max(0.0, min(1.0, value))
|
||||
let transition: ComponentTransition = duration == 0.0 ? .immediate : .easeInOut(duration: duration)
|
||||
transition.setBlur(layer: self.glassView.contentView.layer, radius: (1.0 - fraction) * 4.0)
|
||||
transition.setAlpha(view: self.glassView.contentView, alpha: fraction)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, ContextControllerActionsStackNode {
|
||||
final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||
let backgroundContainer: GlassBackgroundContainerView
|
||||
let backgroundContainerInset: CGFloat
|
||||
let backgroundView: GlassBackgroundView
|
||||
var sourceExtractableContainer: ContextExtractableContainer?
|
||||
let contentContainer: LensTransitionContainer
|
||||
|
||||
|
|
@ -1427,19 +1611,15 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
|
|||
}
|
||||
|
||||
override init() {
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
self.backgroundContainer.contentView.addSubview(self.backgroundView)
|
||||
|
||||
self.contentContainer = LensTransitionContainer()
|
||||
self.contentContainer.clipsToBounds = true
|
||||
self.backgroundView.contentView.addSubview(self.contentContainer)
|
||||
self.backgroundContainer = GlassBackgroundContainerView(spacing: 28.0)
|
||||
self.contentContainer = LensTransitionContainer(effectView: LensTransitionContainerEffectViewImpl(contentView: nil))
|
||||
|
||||
self.backgroundContainerInset = 32.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.backgroundContainer)
|
||||
self.backgroundContainer.contentView.addSubview(self.contentContainer)
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -1493,69 +1673,40 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
|
|||
}
|
||||
}
|
||||
|
||||
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer, transition: ComponentTransition) {
|
||||
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer, fromRect: CGRect, presentationData: PresentationData, transition: ComponentTransition) {
|
||||
let normalState = extractableContainer.normalState
|
||||
let sourceSize = normalState.size
|
||||
//let sourceSize = normalState.size
|
||||
let normalCornerRadius: CGFloat = normalState.cornerRadius
|
||||
|
||||
let currentSize = self.contentContainer.bounds.size
|
||||
|
||||
self.sourceExtractableContainer = extractableContainer
|
||||
self.backgroundView.isHidden = true
|
||||
self.contentContainer.frame = CGRect(origin: CGPoint(), size: currentSize)
|
||||
|
||||
self.backgroundContainer.contentView.addSubview(extractableContainer.extractableContentView)
|
||||
for subview in extractableContainer.extractableContentView.subviews {
|
||||
if let subview = subview as? GlassBackgroundView {
|
||||
//TODO:release
|
||||
subview.contentView.addSubview(self.contentContainer)
|
||||
break
|
||||
}
|
||||
}
|
||||
let sourceEffectView = LensTransitionContainerEffectViewImpl(contentView: extractableContainer.extractableContentView)
|
||||
sourceEffectView.update(theme: presentationData.theme)
|
||||
|
||||
self.contentContainer.frame = CGRect(origin: CGPoint(), size: sourceSize)
|
||||
self.contentContainer.update(size: sourceSize, cornerRadius: min(sourceSize.width, sourceSize.height) * 0.5, state: .animatedOut, transition: .immediate)
|
||||
|
||||
extractableContainer.extractableContentView.frame = CGRect(origin: CGPoint(x: (currentSize.width - sourceSize.width) * 0.5, y: (currentSize.height - sourceSize.height) * 0.5), size: sourceSize).offsetBy(dx: self.backgroundContainerInset, dy: self.backgroundContainerInset)
|
||||
transition.setFrame(view: extractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(x: self.backgroundContainerInset, y: self.backgroundContainerInset), size: currentSize))
|
||||
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: currentSize))
|
||||
self.contentContainer.update(size: currentSize, cornerRadius: 30.0, state: .animatedIn, transition: transition)
|
||||
self.contentContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
|
||||
extractableContainer.updateState(state: .extracted(size: sourceSize, cornerRadius: normalCornerRadius, state: .animatedOut), transition: .transition(.immediate), completion: nil)
|
||||
let mappedTransition: ContextExtractableContainer.Transition
|
||||
if case let .curve(duration, curve) = transition.animation, case let .bounce(stiffness, damping) = curve {
|
||||
mappedTransition = .spring(duration: duration, stiffness: stiffness, damping: damping)
|
||||
} else {
|
||||
mappedTransition = .transition(transition.containedViewLayoutTransition)
|
||||
}
|
||||
extractableContainer.updateState(state: .extracted(size: currentSize, cornerRadius: 30.0, state: .animatedIn), transition: mappedTransition, completion: nil)
|
||||
self.contentContainer.animateIn(fromRect: fromRect, toRect: CGRect(origin: CGPoint(), size: currentSize), fromCornerRadius: normalCornerRadius, toCornerRadius: 30.0, isDark: presentationData.theme.overallDarkAppearance, sourceEffectView: sourceEffectView)
|
||||
}
|
||||
|
||||
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, transition: ComponentTransition) {
|
||||
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, toRect: CGRect, presentationData: PresentationData, transition: ComponentTransition) {
|
||||
let normalState = extractableContainer.normalState
|
||||
let normalSize = normalState.size
|
||||
let normalCornerRadius: CGFloat = normalState.cornerRadius
|
||||
|
||||
|
||||
let currentSize = self.contentContainer.bounds.size
|
||||
self.contentContainer.frame = CGRect(origin: CGPoint(), size: currentSize)
|
||||
|
||||
transition.setFrame(view: extractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(x: self.backgroundContainerInset, y: self.backgroundContainerInset), size: normalSize).offsetBy(dx: (currentSize.width - normalSize.width) * 0.5, dy: (currentSize.height - normalSize.height) * 0.5))
|
||||
|
||||
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: normalSize))
|
||||
self.contentContainer.update(size: normalSize, cornerRadius: normalCornerRadius, state: .animatedOut, transition: transition)
|
||||
|
||||
transition.setCornerRadius(layer: self.contentContainer.layer, cornerRadius: normalCornerRadius)
|
||||
transition.setAlpha(view: self.contentContainer, alpha: 0.0)
|
||||
|
||||
let mappedTransition: ContextExtractableContainer.Transition
|
||||
if case let .curve(duration, curve) = transition.animation, case let .bounce(stiffness, damping) = curve {
|
||||
mappedTransition = .spring(duration: duration, stiffness: stiffness, damping: damping)
|
||||
} else {
|
||||
mappedTransition = .transition(transition.containedViewLayoutTransition)
|
||||
}
|
||||
extractableContainer.updateState(state: .extracted(size: normalSize, cornerRadius: normalCornerRadius, state: .animatedOut), transition: mappedTransition, completion: nil)
|
||||
let sourceEffectView = LensTransitionContainerEffectViewImpl(contentView: extractableContainer.extractableContentView)
|
||||
sourceEffectView.update(theme: presentationData.theme)
|
||||
|
||||
self.contentContainer.animateOut(fromRect: CGRect(origin: CGPoint(), size: currentSize), toRect: toRect, fromCornerRadius: 30.0, toCornerRadius: normalCornerRadius, isDark: presentationData.theme.overallDarkAppearance, sourceEffectView: sourceEffectView)
|
||||
}
|
||||
|
||||
func didAnimateOut(toExtractableContainer extractableContainer: ContextExtractableContainer) {
|
||||
let normalState = extractableContainer.normalState
|
||||
extractableContainer.extractableContentView.frame = CGRect(origin: CGPoint(), size: normalState.size)
|
||||
extractableContainer.isHidden = false
|
||||
extractableContainer.extractableContentView.alpha = 1.0
|
||||
extractableContainer.addSubview(extractableContainer.extractableContentView)
|
||||
extractableContainer.updateState(state: .normal, transition: .transition(.immediate), completion: nil)
|
||||
}
|
||||
|
|
@ -1563,23 +1714,30 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
|
|||
func update(presentationData: PresentationData, presentation: Presentation, size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setCornerRadius(layer: self.contentContainer.layer, cornerRadius: min(30.0, size.height * 0.5))
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundContainer.update(size: size, isDark: presentationData.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
let backgroundContainerFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -self.backgroundContainerInset, dy: -self.backgroundContainerInset)
|
||||
if let effectView = self.contentContainer.effectView as? LensTransitionContainerEffectViewImpl {
|
||||
effectView.update(theme: presentationData.theme)
|
||||
}
|
||||
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), isDark: presentationData.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
if self.backgroundContainer.bounds.size != backgroundContainerFrame.size {
|
||||
//let backgroundContainerFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -self.backgroundContainerInset, dy: -self.backgroundContainerInset)
|
||||
|
||||
/*if self.backgroundContainer.bounds.size != backgroundContainerFrame.size {
|
||||
self.backgroundContainer.update(size: backgroundContainerFrame.size, isDark: presentationData.theme.overallDarkAppearance, transition: transition)
|
||||
transition.setFrame(view: self.backgroundContainer, frame: backgroundContainerFrame)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: self.backgroundContainerInset, y: self.backgroundContainerInset), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: min(30.0, size.height * 0.5), isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
self.backgroundView.update(size: size, cornerRadius: min(30.0, size.height * 0.5), isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)*/
|
||||
|
||||
if let sourceExtractableContainer = self.sourceExtractableContainer {
|
||||
/*if let sourceExtractableContainer = self.sourceExtractableContainer {
|
||||
transition.setFrame(view: sourceExtractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(x: self.backgroundContainerInset, y: self.backgroundContainerInset), size: size))
|
||||
sourceExtractableContainer.updateState(state: .extracted(size: size, cornerRadius: min(30.0, size.height * 0.5), state: .animatedIn), transition: .transition(transition.containedViewLayoutTransition), completion: nil)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2223,12 +2381,12 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
|
|||
}
|
||||
}
|
||||
|
||||
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer, transition: ComponentTransition) {
|
||||
self.navigationContainer.animateIn(fromExtractableContainer: extractableContainer, transition: transition)
|
||||
func animateIn(fromExtractableContainer extractableContainer: ContextExtractableContainer, fromRect: CGRect, presentationData: PresentationData, transition: ComponentTransition) {
|
||||
self.navigationContainer.animateIn(fromExtractableContainer: extractableContainer, fromRect: fromRect, presentationData: presentationData, transition: transition)
|
||||
}
|
||||
|
||||
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, transition: ComponentTransition) {
|
||||
self.navigationContainer.animateOut(toExtractableContainer: extractableContainer, transition: transition)
|
||||
func animateOut(toExtractableContainer extractableContainer: ContextExtractableContainer, toRect: CGRect, presentationData: PresentationData, transition: ComponentTransition) {
|
||||
self.navigationContainer.animateOut(toExtractableContainer: extractableContainer, toRect: toRect, presentationData: presentationData, transition: transition)
|
||||
}
|
||||
|
||||
func didAnimateOut(toExtractableContainer extractableContainer: ContextExtractableContainer) {
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
self.contentRectDebugNode.backgroundColor = UIColor.red.withAlphaComponent(0.2)
|
||||
|
||||
self.actionsContainerNode = ASDisplayNode()
|
||||
self.actionsContainerNode.alpha = 0.0
|
||||
self.actionsStackNode = ContextControllerActionsStackNodeImpl(
|
||||
context: self.context,
|
||||
getController: getController,
|
||||
|
|
@ -816,7 +817,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
case let .reference(reference):
|
||||
if let transitionInfo = reference.transitionInfo() {
|
||||
if let referenceView = transitionInfo.referenceView as? ContextExtractableContainer {
|
||||
contextExtractableContainer = (referenceView, convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view))
|
||||
if #available(iOS 26.0, *) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0)
|
||||
|
|
@ -887,6 +892,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
}
|
||||
}
|
||||
|
||||
if contextExtractableContainer != nil {
|
||||
if stateTransition != nil {
|
||||
self.actionsContainerNode.alpha = 1.0
|
||||
}
|
||||
} else {
|
||||
self.actionsContainerNode.alpha = 1.0
|
||||
}
|
||||
|
||||
var contentParentGlobalFrameOffsetX: CGFloat = 0.0
|
||||
if case let .extracted(extracted) = self.source, extracted.adjustContentForSideInset {
|
||||
let contentSideInset: CGFloat = actionsSideInset + 6.0
|
||||
|
|
@ -1351,21 +1364,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
}
|
||||
|
||||
if let contextExtractableContainer {
|
||||
let positionTransition = ComponentTransition(animation: .curve(duration: 0.35, curve: .bounce(stiffness: 900.0, damping: 95.0)))
|
||||
let transition = ComponentTransition(animation: .curve(duration: 0.5, curve: .spring))
|
||||
|
||||
positionTransition.animatePosition(layer: self.actionsContainerNode.layer, from: CGPoint(
|
||||
x: contextExtractableContainer.sourceRect.midX - self.actionsContainerNode.frame.midX,
|
||||
y: contextExtractableContainer.sourceRect.midY - self.actionsContainerNode.frame.midY
|
||||
), to: CGPoint(), additive: true)
|
||||
/*self.actionsContainerNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.actionsContainerNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
})*/
|
||||
|
||||
self.actionsStackNode.animateIn(fromExtractableContainer: contextExtractableContainer.container, transition: transition)
|
||||
self.actionsStackNode.animateIn(fromExtractableContainer: contextExtractableContainer.container, fromRect: contextExtractableContainer.sourceRect.offsetBy(dx: -self.actionsContainerNode.frame.minX, dy: -self.actionsContainerNode.frame.minY), presentationData: presentationData, transition: transition)
|
||||
} else {
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: self.actionsContainerNode.alpha, duration: 0.05)
|
||||
self.actionsContainerNode.layer.animateSpring(
|
||||
|
|
@ -1719,20 +1720,24 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
|
||||
let contextExtractableContainerView = contextExtractableContainer.container
|
||||
|
||||
positionTransition.setPosition(view: self.actionsContainerNode.view, position: CGPoint(x: contextExtractableContainer.sourceRect.midX, y: contextExtractableContainer.sourceRect.midY), completion: { _ in
|
||||
/*positionTransition.setPosition(view: self.actionsContainerNode.view, position: CGPoint(x: contextExtractableContainer.sourceRect.midX, y: contextExtractableContainer.sourceRect.midY), completion: { _ in
|
||||
if completeWithActionStack {
|
||||
restoreOverlayViews.forEach({ $0() })
|
||||
completion()
|
||||
}
|
||||
})
|
||||
})*/
|
||||
|
||||
positionTransition.attachAnimation(view: self.actionsContainerNode.view, id: "animateOut", completion: { [weak self, weak contextExtractableContainerView] _ in
|
||||
if completeWithActionStack {
|
||||
restoreOverlayViews.forEach({ $0() })
|
||||
completion()
|
||||
}
|
||||
if let self, let contextExtractableContainerView {
|
||||
self.actionsStackNode.didAnimateOut(toExtractableContainer: contextExtractableContainerView)
|
||||
}
|
||||
})
|
||||
|
||||
self.actionsStackNode.animateOut(toExtractableContainer: contextExtractableContainer.container, transition: transition)
|
||||
self.actionsStackNode.animateOut(toExtractableContainer: contextExtractableContainer.container, toRect: contextExtractableContainer.sourceRect.offsetBy(dx: -self.actionsContainerNode.frame.minX, dy: -self.actionsContainerNode.frame.minY), presentationData: presentationData, transition: transition)
|
||||
} else {
|
||||
self.actionsContainerNode.layer.animateAlpha(from: self.actionsContainerNode.alpha, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||
self.actionsContainerNode.layer.animate(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -666,10 +701,10 @@ public final class GlassBackgroundContainerView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
public init(spacing: CGFloat = 7.0) {
|
||||
if #available(iOS 26.0, *), !GlassBackgroundView.useCustomGlassImpl {
|
||||
let effect = UIGlassContainerEffect()
|
||||
effect.spacing = 7.0
|
||||
effect.spacing = spacing
|
||||
let nativeView = UIVisualEffectView(effect: effect)
|
||||
self.nativeView = nativeView
|
||||
|
||||
|
|
@ -684,7 +719,7 @@ public final class GlassBackgroundContainerView: UIView {
|
|||
self.legacyView = ContentView()
|
||||
}
|
||||
|
||||
super.init(frame: frame)
|
||||
super.init(frame: CGRect())
|
||||
|
||||
if let nativeParamsView = self.nativeParamsView {
|
||||
self.addSubview(nativeParamsView)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 #available(iOS 17.0, *), 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 #available(iOS 17.0, *), 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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ swift_library(
|
|||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"app": "12.5.1",
|
||||
"app": "12.6",
|
||||
"xcode": "26.2",
|
||||
"bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa",
|
||||
"macos": "26"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue