diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index e72348ad07..47d54e90d8 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -830,7 +830,7 @@ public extension ContainedViewLayoutTransition { } func updateAlpha(node: ASDisplayNode, alpha: CGFloat, beginWithCurrentState: Bool = false, force: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { - if node.alpha.isEqual(to: alpha) && !force { + if node.layer.opacity == Float(alpha) && !force { if let completion = completion { completion(true) } @@ -844,14 +844,18 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - let previousAlpha: CGFloat + let previousAlpha: Float if beginWithCurrentState, let presentation = node.layer.presentation() { - previousAlpha = CGFloat(presentation.opacity) + previousAlpha = presentation.opacity } else { - previousAlpha = node.alpha + previousAlpha = node.layer.opacity } - node.alpha = alpha - node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in + if alpha == 0.0 { + node.layer.opacity = Float(alpha) + } else { + node.alpha = alpha + } + node.layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } diff --git a/submodules/Display/Source/SwitchNode.swift b/submodules/Display/Source/SwitchNode.swift index 3dc327c0d2..532727cbeb 100644 --- a/submodules/Display/Source/SwitchNode.swift +++ b/submodules/Display/Source/SwitchNode.swift @@ -9,7 +9,11 @@ private final class SwitchNodeViewLayer: CALayer { private final class SwitchNodeView: UISwitch { override class var layerClass: AnyClass { - return SwitchNodeViewLayer.self + if #available(iOS 26.0, *) { + return super.layerClass + } else { + return SwitchNodeViewLayer.self + } } } @@ -82,7 +86,11 @@ open class SwitchNode: ASDisplayNode { } override open func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: 51.0, height: 31.0) + if #available(iOS 26.0, *) { + return CGSize(width: 63.0, height: 28.0) + } else { + return CGSize(width: 51.0, height: 31.0) + } } @objc func switchValueChanged(_ view: UISwitch) { diff --git a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.h b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.h index efdfd1f48b..a191122163 100644 --- a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.h +++ b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.h @@ -11,6 +11,8 @@ typedef enum { + (void)swizzleInstanceMethodOfClass:(Class _Nonnull)targetClass currentSelector:(SEL _Nonnull)currentSelector newSelector:(SEL _Nonnull)newSelector; + (void)swizzleInstanceMethodOfClass:(Class _Nonnull)targetClass currentSelector:(SEL _Nonnull)currentSelector withAnotherClass:(Class _Nonnull)anotherClass newSelector:(SEL _Nonnull)newSelector; + (void)swizzleClassMethodOfClass:(Class _Nonnull)targetClass currentSelector:(SEL _Nonnull)currentSelector newSelector:(SEL _Nonnull)newSelector; ++ (void * _Nullable)getMethodOfClass:(Class _Nonnull)targetClass selector:(SEL _Nonnull)selector; ++ (void)replaceMethodImplementationOfClass:(Class _Nonnull)targetClass selector:(SEL _Nonnull)selector replacement:(IMP _Nonnull)replacement; + (CALayer * _Nonnull)makeLayerHostCopy:(CALayer * _Nonnull)another; @end @@ -30,3 +32,4 @@ typedef enum { @end SEL _Nonnull makeSelectorFromString(NSString * _Nonnull string); + diff --git a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m index 82b42731c5..10bdf8ab83 100644 --- a/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m +++ b/submodules/ObjCRuntimeUtils/Source/ObjCRuntimeUtils/RuntimeUtils.m @@ -59,6 +59,17 @@ } } ++ (void * _Nullable)getMethodOfClass:(Class _Nonnull)targetClass selector:(SEL _Nonnull)selector { + return class_getInstanceMethod(targetClass, selector); +} + ++ (void)replaceMethodImplementationOfClass:(Class _Nonnull)targetClass selector:(SEL _Nonnull)selector replacement:(IMP _Nonnull)replacement { + Method method = class_getInstanceMethod(targetClass, selector); + if (method) { + method_setImplementation(method, replacement); + } +} + @end @implementation NSObject (AssociatedObject) diff --git a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift index 42e195a818..25a2afaf63 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift @@ -33,9 +33,9 @@ open class ChatInputPanelNode: ASDisplayNode { open func defaultHeight(metrics: LayoutMetrics) -> CGFloat { if case .regular = metrics.widthClass, case .regular = metrics.heightClass { - return 49.0 + return 40.0 } else { - return 45.0 + return 40.0 } } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD b/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD index 941836106e..54cc2f8764 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/UIKitRuntimeUtils", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 5c66aac2a6..c8c8593153 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -3,6 +3,7 @@ import UIKit import Display import ComponentFlow import ComponentDisplayAdapters +import UIKitRuntimeUtils private final class ContentContainer: UIView { private let maskContentView: UIView @@ -273,6 +274,7 @@ public class GlassBackgroundView: UIView { private let backgroundNode: NavigationBackgroundNode? private let nativeView: UIVisualEffectView? private let nativeContainerView: UIVisualEffectView? + private let nativeParamsView: EffectSettingsContainerView? private let foregroundView: UIImageView? private let shadowView: UIImageView? @@ -305,13 +307,20 @@ public class GlassBackgroundView: UIView { self.nativeContainerView = nativeContainerView nativeContainerView.contentView.addSubview(nativeView) + let nativeParamsView = EffectSettingsContainerView(frame: CGRect()) + self.nativeParamsView = nativeParamsView + + nativeParamsView.addSubview(nativeContainerView) + self.foregroundView = nil self.shadowView = nil } else { self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0) self.nativeView = nil self.nativeContainerView = nil + self.nativeParamsView = nil self.foregroundView = UIImageView() + self.shadowView = UIImageView() } @@ -331,8 +340,8 @@ public class GlassBackgroundView: UIView { if let shadowView = self.shadowView { self.addSubview(shadowView) } - if let nativeContainerView = self.nativeContainerView { - self.addSubview(nativeContainerView) + if let nativeParamsView = self.nativeParamsView { + self.addSubview(nativeParamsView) } if let backgroundNode = self.backgroundNode { self.addSubview(backgroundNode.view) @@ -372,7 +381,7 @@ public class GlassBackgroundView: UIView { nativeView.layer.animateFrame(from: previousFrame, to: CGRect(origin: CGPoint(), size: size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) } - transition.setFrame(view: nativeContainerView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: nativeContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: max(size.height, 400.0)))) } if let backgroundNode = self.backgroundNode { backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.color.alpha != 1.0, transition: transition.containedViewLayoutTransition) @@ -404,7 +413,7 @@ public class GlassBackgroundView: UIView { if let foregroundView = self.foregroundView { foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: tintColor.color) } else { - if let nativeContainerView = self.nativeContainerView, let nativeView { + if let nativeParamsView = self.nativeParamsView, let nativeContainerView = self.nativeContainerView, let nativeView { if #available(iOS 26.0, *) { let glassEffect = UIGlassEffect(style: .regular) switch tintColor.kind { @@ -416,9 +425,16 @@ public class GlassBackgroundView: UIView { glassEffect.isInteractive = params.isInteractive nativeView.effect = glassEffect - let _ = nativeContainerView - //nativeContainerView.overrideUserInterfaceStyle = .light// isDark ? .dark : .light - self.overrideUserInterfaceStyle = isDark ? .dark : .light + + if isDark { + nativeParamsView.lumaMin = 0.0 + nativeParamsView.lumaMax = 0.15 + } else { + nativeParamsView.lumaMin = 0.25 + nativeParamsView.lumaMax = 1.0 + } + + nativeContainerView.overrideUserInterfaceStyle = isDark ? .dark : .light } } } @@ -665,7 +681,6 @@ public extension GlassBackgroundView { addShadow(false, CGPoint(x: 3.0, y: -3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25), false) addShadow(false, CGPoint(x: -3.0, y: 3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25), false) } else { - addShadow(true, CGPoint(), 32.0, 0.0, UIColor(white: 0.0, alpha: 0.08), false) addShadow(true, CGPoint(), 16.0, 0.0, UIColor(white: 0.0, alpha: 0.08), false) context.setFillColor(fillColor.cgColor) @@ -673,17 +688,18 @@ public extension GlassBackgroundView { let highlightColor: UIColor if fillColor.hsb.s > 0.5 { - highlightColor = fillColor.withMultiplied(hue: 1.0, saturation: 2.0, brightness: 1.0).adjustedPerceivedBrightness(2.0) + var (h, s, v) = fillColor.hsb + s = max(0.0, min(1.0, s * 0.25)) + v = max(v, 0.95) + h = max(0.0, min(1.0, h - 0.1)) - let shadowColor = fillColor.withMultiplied(hue: 1.0, saturation: 2.0, brightness: 1.0).adjustedPerceivedBrightness(0.5).withMultipliedAlpha(0.2) - addShadow(false, CGPoint(x: -2.0, y: 2.0), 0.5, 0.0, shadowColor, false) + highlightColor = UIColor(hue: h, saturation: s, brightness: v, alpha: fillColor.alpha) } else { - highlightColor = UIColor(white: 1.0, alpha: 0.4) - addShadow(false, CGPoint(x: -2.0, y: 2.0), 0.5, 0.0, UIColor.black.withMultipliedAlpha(0.15), true) - addShadow(false, CGPoint(x: -2.0, y: 2.0), 0.6, 0.0, UIColor(white: 0.0, alpha: 0.1), false) + highlightColor = UIColor(white: 1.0, alpha: min(1.0, fillColor.alpha * 1.2)) } - addShadow(false, CGPoint(x: 2.0, y: -2.0), 0.5, 0.0, highlightColor, false) + addShadow(false, CGPoint(x: 2.0, y: -2.0), 1.0, 0.0, highlightColor, false) + addShadow(false, CGPoint(x: -2.0, y: 2.0), 1.0, 0.0, highlightColor, false) } })!.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5)) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 628150a818..190d7b524b 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -192,7 +192,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { transition.updateAlpha(node: self.downButton, alpha: 1.0) transition.updateTransformScale(node: self.downButton, scale: 1.0) } else { - transition.updateAlpha(node: self.downButton, alpha: 0.1, completion: { [weak self] completed in + transition.updateAlpha(node: self.downButton, alpha: 0.0, completion: { [weak self] completed in guard let strongSelf = self, completed else { return } diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index e8f743f52c..eaf18a9d40 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -279,9 +279,9 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { let height: CGFloat if case .regular = params.metrics.widthClass { - height = 49.0 + height = 40.0 } else { - height = 45.0 + height = 40.0 } var modeButtonTitle: [AnimatedTextComponent.Item] = [] diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h index 5afbb21710..ca13788e4d 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.h @@ -57,3 +57,10 @@ void applyKeyboardAutocorrection(UITextView * _Nonnull textView); @end void snapshotViewByDrawingInContext(UIView * _Nonnull view); + +@interface EffectSettingsContainerView : UIView + +@property (nonatomic) double lumaMin; +@property (nonatomic) double lumaMax; + +@end diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m index 8d74325e8f..46a531594a 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIViewController+Navigation.m @@ -173,6 +173,59 @@ static bool notyfyingShiftState = false; @end +static EffectSettingsContainerView *findTopmostEffectSuperview(UIView *view, int depth) { + if (depth > 5) { + return nil; + } + if ([view isKindOfClass:[EffectSettingsContainerView class]]) { + return (EffectSettingsContainerView* )view; + } + if (view.superview != nil) { + return findTopmostEffectSuperview(view.superview, depth + 1); + } else { + return nil; + } +} + +static id (*original_backdropLayerDidChangeLuma)(UIView *, SEL, CALayer *, double) = NULL; +static void replacement_backdropLayerDidChangeLuma(UIView *self, SEL selector, CALayer *layer, double luma) { + EffectSettingsContainerView *topmostSuperview = findTopmostEffectSuperview(self, 0); + if (topmostSuperview) { + luma = MIN(MAX(luma, topmostSuperview.lumaMin), topmostSuperview.lumaMax); + } + original_backdropLayerDidChangeLuma(self, selector, layer, luma); +} + +static void registerEffectViewOverrides(void) { + int classCount = objc_getClassList(NULL, 0); + if (classCount > 0) { + __unsafe_unretained Class *classList = (Class *)malloc(classCount * sizeof(Class)); + objc_getClassList(classList, classCount); + + NSString *searchString = [@"UISD" stringByAppendingString:@"FBackdropView"]; + NSString *selectorString = [@"backdropLayer" stringByAppendingString:@":didChangeLuma:"]; + + for (int i = 0; i < classCount; i++) + { + const char *className = class_getName(classList[i]); + NSString *name = [[NSString alloc] initWithCString:className encoding:NSASCIIStringEncoding]; + if ([name hasSuffix:searchString]) { + Method method = (Method)[RuntimeUtils getMethodOfClass:classList[i] selector:NSSelectorFromString(selectorString)]; + if (method) { + const char *typeEncoding = method_getTypeEncoding(method); + if (strcmp(typeEncoding, "v32@0:8@16d24") == 0) { + original_backdropLayerDidChangeLuma = (id (*)(id, SEL, CALayer *, double))method_getImplementation(method); + [RuntimeUtils replaceMethodImplementationOfClass:classList[i] selector:NSSelectorFromString(selectorString) replacement:(IMP)&replacement_backdropLayerDidChangeLuma]; + } + } + break; + } + } + + free(classList); + } +} + @implementation UIViewController (Navigation) + (void)load @@ -197,6 +250,10 @@ static bool notyfyingShiftState = false; } [RuntimeUtils swizzleInstanceMethodOfClass:[UIFocusSystem class] currentSelector:@selector(updateFocusIfNeeded) newSelector:@selector(_65087dc8_updateFocusIfNeeded)]; + + if (@available(iOS 26.0, *)) { + registerEffectViewOverrides(); + } }); } @@ -501,3 +558,16 @@ void applyKeyboardAutocorrection(UITextView * _Nonnull textView) { void snapshotViewByDrawingInContext(UIView * _Nonnull view) { [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:false]; } + +@implementation EffectSettingsContainerView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self != nil) { + _lumaMin = 0.0; + _lumaMax = 0.0; + } + return self; +} + +@end