diff --git a/Signal/Symbols.xcassets/check/check-circle-light.imageset/Contents.json b/Signal/Symbols.xcassets/check/check-circle-light.imageset/Contents.json index 134f8a500f..17b6176199 100644 --- a/Signal/Symbols.xcassets/check/check-circle-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/check/check-circle-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/copy/copy-light.imageset/Contents.json b/Signal/Symbols.xcassets/copy/copy-light.imageset/Contents.json index 2732fc3345..91f8973591 100644 --- a/Signal/Symbols.xcassets/copy/copy-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/copy/copy-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/edit/edit-light.imageset/Contents.json b/Signal/Symbols.xcassets/edit/edit-light.imageset/Contents.json index b0b0cabd8f..65032b7007 100644 --- a/Signal/Symbols.xcassets/edit/edit-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/edit/edit-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/forward/forward-light.imageset/Contents.json b/Signal/Symbols.xcassets/forward/forward-light.imageset/Contents.json index 44dd2b8c91..332b76241a 100644 --- a/Signal/Symbols.xcassets/forward/forward-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/forward/forward-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/info/info-light.imageset/Contents.json b/Signal/Symbols.xcassets/info/info-light.imageset/Contents.json index e62fd67378..f6e43469ce 100644 --- a/Signal/Symbols.xcassets/info/info-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/info/info-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/media/pause/pause-circle-light.imageset/Contents.json b/Signal/Symbols.xcassets/media/pause/pause-circle-light.imageset/Contents.json index 1394c90e10..d92fcfe7f9 100644 --- a/Signal/Symbols.xcassets/media/pause/pause-circle-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/media/pause/pause-circle-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/payment/payment.imageset/Contents.json b/Signal/Symbols.xcassets/payment/payment.imageset/Contents.json index 87c5561cfb..4e20a808f7 100644 --- a/Signal/Symbols.xcassets/payment/payment.imageset/Contents.json +++ b/Signal/Symbols.xcassets/payment/payment.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/polls/pollstop.imageset/Contents.json b/Signal/Symbols.xcassets/polls/pollstop.imageset/Contents.json index d26fd97227..a580d8a314 100644 --- a/Signal/Symbols.xcassets/polls/pollstop.imageset/Contents.json +++ b/Signal/Symbols.xcassets/polls/pollstop.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/save/save-light.imageset/Contents.json b/Signal/Symbols.xcassets/save/save-light.imageset/Contents.json index 01730d4d36..9312286b08 100644 --- a/Signal/Symbols.xcassets/save/save-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/save/save-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/share/share-light.imageset/Contents.json b/Signal/Symbols.xcassets/share/share-light.imageset/Contents.json index 4220f73bdf..62502c6526 100644 --- a/Signal/Symbols.xcassets/share/share-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/share/share-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/speaker/speaker-light.imageset/Contents.json b/Signal/Symbols.xcassets/speaker/speaker-light.imageset/Contents.json index a4e6a17091..9c13540861 100644 --- a/Signal/Symbols.xcassets/speaker/speaker-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/speaker/speaker-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/Symbols.xcassets/trash/trash-light.imageset/Contents.json b/Signal/Symbols.xcassets/trash/trash-light.imageset/Contents.json index 6874d9f7ad..aabfdbf8f7 100644 --- a/Signal/Symbols.xcassets/trash/trash-light.imageset/Contents.json +++ b/Signal/Symbols.xcassets/trash/trash-light.imageset/Contents.json @@ -10,6 +10,7 @@ "version" : 1 }, "properties" : { + "preserves-vector-representation" : true, "template-rendering-intent" : "template" } } diff --git a/Signal/src/ViewControllers/ContextMenus/CustomContextMenus/ContextMenuActionsAccessory.swift b/Signal/src/ViewControllers/ContextMenus/CustomContextMenus/ContextMenuActionsAccessory.swift index 9f6f78b56d..babcd36d9f 100644 --- a/Signal/src/ViewControllers/ContextMenus/CustomContextMenus/ContextMenuActionsAccessory.swift +++ b/Signal/src/ViewControllers/ContextMenus/CustomContextMenus/ContextMenuActionsAccessory.swift @@ -150,12 +150,10 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr private class ContextMenuActionRow: UIView { let attributes: ContextMenuAction.Attributes - let hostEffect: UIBlurEffect let forceDarkTheme: Bool - let visualEffectView: UIVisualEffectView let titleLabel: UILabel let iconView: UIImageView - let separatorView: UIView + var separatorView: UIView? var highlightedView: UIView? var isHighlighted: Bool { didSet { @@ -163,7 +161,16 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr if isHighlighted { if highlightedView == nil { let view = UIView() - view.frame = bounds + + if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { +#if compiler(>=6.2) + view.frame = bounds.insetBy(dx: 12, dy: 4) + view.cornerConfiguration = .capsule() +#endif + } else { + view.frame = bounds + } + view.backgroundColor = forceDarkTheme || Theme.isDarkThemeEnabled ? UIColor(rgbHex: 0x787880).withAlphaComponent(0.32) : UIColor(rgbHex: 0x787880).withAlphaComponent(0.16) @@ -182,16 +189,25 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr } var maxWidth: CGFloat = 250 - let margin: CGFloat = 16 + let margin: CGFloat = if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + 24 + } else { + 16 + } + let iconSpacing: CGFloat = 12 let verticalPadding: CGFloat = 23 - let iconSize: CGFloat = 20 + let iconSize: CGFloat = if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + 24 + } else { + 20 + } let titleMaxLines = 2 public init( title: String, icon: UIImage?, attributes: ContextMenuAction.Attributes, - hostBlurEffect: UIBlurEffect, + hostBlurEffect: UIBlurEffect?, forceDarkTheme: Bool ) { titleLabel = UILabel(frame: CGRect.zero) @@ -200,7 +216,6 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr titleLabel.numberOfLines = titleMaxLines self.attributes = attributes - hostEffect = hostBlurEffect self.forceDarkTheme = forceDarkTheme /// when made a child of a UIVisualEffectView, UILabel text color is overridden, but a vibrancy effect is added. @@ -222,26 +237,34 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr iconView.contentMode = .scaleAspectFit iconView.tintColor = titleLabel.textColor - separatorView = UIView() - separatorView.backgroundColor = forceDarkTheme || Theme.isDarkThemeEnabled - ? UIColor(rgbHex: 0x545458).withAlphaComponent(0.6) - : UIColor(rgbHex: 0x3c3c43).withAlphaComponent(0.3) - separatorView.autoresizingMask = [.flexibleWidth, .flexibleHeight] isHighlighted = false - visualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: hostBlurEffect, style: .label)) super.init(frame: .zero) - addSubview(visualEffectView) - if makeLabelSubviewOfVisualEffectsView { - visualEffectView.contentView.addSubview(titleLabel) + if let hostBlurEffect { + let visualEffectView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: hostBlurEffect, style: .label)) + addSubview(visualEffectView) + if makeLabelSubviewOfVisualEffectsView { + visualEffectView.contentView.addSubview(titleLabel) + } else { + addSubview(titleLabel) + } + + let separatorView = UIView() + separatorView.backgroundColor = forceDarkTheme || Theme.isDarkThemeEnabled + ? UIColor(rgbHex: 0x545458).withAlphaComponent(0.6) + : UIColor(rgbHex: 0x3c3c43).withAlphaComponent(0.3) + separatorView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.separatorView = separatorView + + visualEffectView.contentView.addSubview(iconView) + visualEffectView.contentView.addSubview(separatorView) + + visualEffectView.autoPinEdgesToSuperviewEdges() } else { addSubview(titleLabel) + addSubview(iconView) } - visualEffectView.contentView.addSubview(iconView) - visualEffectView.contentView.addSubview(separatorView) - - visualEffectView.autoPinEdgesToSuperviewEdges() isAccessibilityElement = true accessibilityLabel = titleLabel.text @@ -263,7 +286,7 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr titleFrame.y = ceil((bounds.height - titleFrame.height) / 2) iconViewFrame.y = max(0, (bounds.height - iconViewFrame.height) / 2) - let titleWidth = bounds.width - iconViewFrame.width - 3 * margin + let titleWidth = bounds.width - iconViewFrame.width - (2 * margin) - iconSpacing if titleWidth < titleFrame.width { // Give it more height for a second line. let originalHeight = titleLabel.textRect(forBounds: CGRect.infinite, limitedToNumberOfLines: 1).height @@ -280,27 +303,35 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr } titleFrame.width = titleWidth - if !isRTL { + let iconIsToTheRightOfText = if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + isRTL + } else { + !isRTL + } + + if iconIsToTheRightOfText { titleFrame.x = margin - iconViewFrame.x = titleFrame.maxX + margin + iconViewFrame.x = titleFrame.maxX + iconSpacing } else { iconViewFrame.x = margin - titleFrame.x = iconViewFrame.maxX + margin + titleFrame.x = iconViewFrame.maxX + iconSpacing } titleLabel.frame = titleFrame iconView.frame = iconViewFrame - var separatorFrame = bounds - separatorFrame.height = 1.0 / UIScreen.main.scale - separatorFrame.y = bounds.maxY - separatorFrame.height - separatorView.frame = separatorFrame + if let separatorView { + var separatorFrame = bounds + separatorFrame.height = 1.0 / UIScreen.main.scale + separatorFrame.y = bounds.maxY - separatorFrame.height + separatorView.frame = separatorFrame + } } public override func sizeThatFits( _ size: CGSize ) -> CGSize { - let height = ceil(titleLabel.sizeThatFits(CGSize(width: maxWidth - 3 * margin - iconSize, height: 0)).height) + verticalPadding + let height = ceil(titleLabel.sizeThatFits(CGSize(width: maxWidth - (2 * margin) - iconSpacing - iconSize, height: 0)).height) + verticalPadding return CGSize(width: maxWidth, height: height) } } @@ -326,7 +357,17 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr } } - let cornerRadius: CGFloat = 12 + let cornerRadius: CGFloat = if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + 33 + } else { + 12 + } + + let vMargin: CGFloat = if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + 10 + } else { + 0 + } public init( menu: ContextMenu, @@ -336,15 +377,36 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr self.forceDarkTheme = forceDarkTheme scrollView = UIScrollView(frame: CGRect.zero) - let effect: UIBlurEffect = .init(style: .systemThinMaterial) - backdropView = UIVisualEffectView(effect: effect) - backdropView.backgroundColor = Theme.isDarkThemeEnabled || forceDarkTheme - ? .ows_blackAlpha80 - : .ows_whiteAlpha40 + scrollView.verticalScrollIndicatorInsets = .init(hMargin: 0, vMargin: cornerRadius) + + let blurEffect: UIBlurEffect? + if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { +#if compiler(>=6.2) + let effect = UIGlassEffect(style: .clear) + effect.isInteractive = true + backdropView = UIVisualEffectView(effect: effect) +#else + // will never execute because of the FeatureFlags.iOS26SDKIsAvailable check above, but needed for the Xcode 16 compiler to not get mad. Remove when CI is all Xcode 26 + backdropView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterial)) +#endif + blurEffect = nil + } else { + blurEffect = UIBlurEffect(style: .systemThinMaterial) + backdropView = UIVisualEffectView(effect: blurEffect) + backdropView.backgroundColor = Theme.isDarkThemeEnabled || forceDarkTheme + ? .ows_blackAlpha80 + : .ows_whiteAlpha40 + } var actionViews: [ContextMenuActionRow] = [] for action in menu.children { - let actionView = ContextMenuActionRow(title: action.title, icon: action.image, attributes: action.attributes, hostBlurEffect: effect, forceDarkTheme: forceDarkTheme) + let actionView = ContextMenuActionRow( + title: action.title, + icon: action.image, + attributes: action.attributes, + hostBlurEffect: blurEffect, + forceDarkTheme: forceDarkTheme + ) actionViews.append(actionView) } @@ -365,11 +427,14 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr addGestureRecognizer(highlightHoverGestureRecognizer) self.highlightHoverGestureRecognizer = highlightHoverGestureRecognizer - layer.cornerRadius = cornerRadius - layer.shadowRadius = 64 - layer.shadowOffset = CGSize(width: 0, height: 32) - layer.shadowColor = UIColor.ows_black.cgColor - layer.shadowOpacity = 0.2 + if #available(iOS 26, *), FeatureFlags.iOS26SDKIsAvailable { + } else { + layer.cornerRadius = cornerRadius + layer.shadowRadius = 64 + layer.shadowOffset = CGSize(width: 0, height: 32) + layer.shadowColor = UIColor.ows_black.cgColor + layer.shadowOpacity = 0.2 + } backdropView.layer.cornerRadius = cornerRadius backdropView.layer.masksToBounds = true @@ -382,7 +447,7 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr scrollView.delegate = self - actionViews.last?.separatorView.isHidden = true + actionViews.last?.separatorView?.isHidden = true } required init?(coder: NSCoder) { @@ -396,7 +461,7 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr backdropView.frame = bounds scrollView.frame = bounds - var yOffset: CGFloat = 0 + var yOffset: CGFloat = vMargin var maxY: CGFloat = 0 var width: CGFloat = 0.0 for actionView in actionViews { @@ -406,6 +471,7 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr yOffset += size.height maxY = max(maxY, actionView.frame.maxY) } + maxY += vMargin scrollView.contentSize = CGSize(width: width, height: maxY) } @@ -414,7 +480,7 @@ private class ContextMenuActionsView: UIView, UIGestureRecognizerDelegate, UIScr _ size: CGSize ) -> CGSize { // every entry may have a different height - var height = 0.0 + var height = vMargin * 2 var width = 0.0 for actionView in actionViews { let size = actionView.sizeThatFits(size)