Кастомный header чат-листа в стиле Telegram — glass кнопки, анимация search bar, snap при скролле
This commit is contained in:
@@ -41,6 +41,8 @@ final class ChatListCollectionController: UIViewController {
|
|||||||
private var lastReportedExpansion: CGFloat = 1.0
|
private var lastReportedExpansion: CGFloat = 1.0
|
||||||
private var lastReportedPinnedHeaderFraction: CGFloat = -1.0
|
private var lastReportedPinnedHeaderFraction: CGFloat = -1.0
|
||||||
private let searchCollapseDistance: CGFloat = 54
|
private let searchCollapseDistance: CGFloat = 54
|
||||||
|
/// Extra top offset for custom header bar (nav bar is hidden)
|
||||||
|
var customHeaderBarHeight: CGFloat = 44
|
||||||
private var searchHeaderExpansion: CGFloat = 1.0
|
private var searchHeaderExpansion: CGFloat = 1.0
|
||||||
private var hasInitializedTopOffset = false
|
private var hasInitializedTopOffset = false
|
||||||
private var isPinnedFractionReportScheduled = false
|
private var isPinnedFractionReportScheduled = false
|
||||||
@@ -121,7 +123,7 @@ final class ChatListCollectionController: UIViewController {
|
|||||||
private func applyInsets() {
|
private func applyInsets() {
|
||||||
guard collectionView != nil else { return }
|
guard collectionView != nil else { return }
|
||||||
let oldTopInset = collectionView.contentInset.top
|
let oldTopInset = collectionView.contentInset.top
|
||||||
let topInset = view.safeAreaInsets.top + (searchCollapseDistance * searchHeaderExpansion)
|
let topInset = view.safeAreaInsets.top + customHeaderBarHeight + (searchCollapseDistance * searchHeaderExpansion)
|
||||||
let bottomInset = chatListBottomInset
|
let bottomInset = chatListBottomInset
|
||||||
collectionView.contentInset.top = topInset
|
collectionView.contentInset.top = topInset
|
||||||
collectionView.contentInset.bottom = bottomInset
|
collectionView.contentInset.bottom = bottomInset
|
||||||
@@ -465,7 +467,7 @@ extension ChatListCollectionController: UICollectionViewDelegate {
|
|||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
// Only react to user-driven scroll, not programmatic/layout changes
|
// Only react to user-driven scroll, not programmatic/layout changes
|
||||||
guard scrollView.isDragging || scrollView.isDecelerating else { return }
|
guard scrollView.isDragging || scrollView.isDecelerating else { return }
|
||||||
let offset = scrollView.contentOffset.y + view.safeAreaInsets.top + searchCollapseDistance
|
let offset = scrollView.contentOffset.y + view.safeAreaInsets.top + customHeaderBarHeight + searchCollapseDistance
|
||||||
let expansion = max(0.0, min(1.0, 1.0 - offset / searchCollapseDistance))
|
let expansion = max(0.0, min(1.0, 1.0 - offset / searchCollapseDistance))
|
||||||
if abs(expansion - lastReportedExpansion) > 0.005 {
|
if abs(expansion - lastReportedExpansion) > 0.005 {
|
||||||
lastReportedExpansion = expansion
|
lastReportedExpansion = expansion
|
||||||
@@ -482,11 +484,11 @@ extension ChatListCollectionController: UICollectionViewDelegate {
|
|||||||
// Telegram snap-to-edge: if search bar is partially visible, snap to
|
// Telegram snap-to-edge: if search bar is partially visible, snap to
|
||||||
// fully visible (>50%) or fully hidden (<50%).
|
// fully visible (>50%) or fully hidden (<50%).
|
||||||
guard lastReportedExpansion > 0.0 && lastReportedExpansion < 1.0 else { return }
|
guard lastReportedExpansion > 0.0 && lastReportedExpansion < 1.0 else { return }
|
||||||
let safeTop = view.safeAreaInsets.top
|
let headerTop = view.safeAreaInsets.top + customHeaderBarHeight
|
||||||
if lastReportedExpansion < 0.5 {
|
if lastReportedExpansion < 0.5 {
|
||||||
targetContentOffset.pointee.y = -safeTop
|
targetContentOffset.pointee.y = -headerTop
|
||||||
} else {
|
} else {
|
||||||
targetContentOffset.pointee.y = -(safeTop + searchCollapseDistance)
|
targetContentOffset.pointee.y = -(headerTop + searchCollapseDistance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ final class ChatListRootViewController: UIViewController, UINavigationController
|
|||||||
private var typingDialogs: [String: Set<String>] = [:]
|
private var typingDialogs: [String: Set<String>] = [:]
|
||||||
private var currentSearchQuery = ""
|
private var currentSearchQuery = ""
|
||||||
private var searchResultUsersByKey: [String: SearchUser] = [:]
|
private var searchResultUsersByKey: [String: SearchUser] = [:]
|
||||||
private let searchTopSpacing: CGFloat = 5
|
private let searchTopSpacing: CGFloat = 10
|
||||||
private let searchBottomSpacing: CGFloat = 5
|
private let searchBottomSpacing: CGFloat = 5
|
||||||
private let searchHeaderHeight: CGFloat = 44
|
private let searchHeaderHeight: CGFloat = 44
|
||||||
private var searchChromeHeight: CGFloat {
|
private var searchChromeHeight: CGFloat {
|
||||||
@@ -275,8 +275,8 @@ final class ChatListRootViewController: UIViewController, UINavigationController
|
|||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
top,
|
top,
|
||||||
searchHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
|
searchHeaderView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
||||||
searchHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
|
searchHeaderView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
||||||
height,
|
height,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -445,20 +445,20 @@ final class ChatListRootViewController: UIViewController, UINavigationController
|
|||||||
guard abs(clamped - lastSearchExpansion) > 0.003 else { return }
|
guard abs(clamped - lastSearchExpansion) > 0.003 else { return }
|
||||||
lastSearchExpansion = clamped
|
lastSearchExpansion = clamped
|
||||||
|
|
||||||
// Telegram non-linear: search bar stays fully visible for first 27% of scroll
|
// Structural: top stays fixed, height collapses (Telegram: 60*progress, we use 44)
|
||||||
let visibleProgress = max(0.0, min(1.0, (clamped - 0.267) / 0.733))
|
searchHeaderTopConstraint?.constant = headerBarHeight + searchTopSpacing
|
||||||
|
|
||||||
// Structural: use raw clamped
|
|
||||||
searchHeaderTopConstraint?.constant = searchTopSpacing * clamped
|
|
||||||
searchHeaderHeightConstraint?.constant = searchHeaderHeight * clamped
|
searchHeaderHeightConstraint?.constant = searchHeaderHeight * clamped
|
||||||
listController.setSearchHeaderExpansion(clamped)
|
listController.setSearchHeaderExpansion(clamped)
|
||||||
|
|
||||||
// Visual: use non-linear visibleProgress
|
// Telegram animation: NO scale transform, NO whole-view alpha.
|
||||||
searchHeaderView.alpha = visibleProgress
|
// Height reduction + clipping handles the collapse.
|
||||||
searchHeaderView.isUserInteractionEnabled = visibleProgress > 0.2
|
// Only content (text/icon) fades separately.
|
||||||
let yShift = -8.0 * (1.0 - clamped)
|
searchHeaderView.transform = .identity
|
||||||
searchHeaderView.transform = CGAffineTransform(translationX: 0, y: yShift)
|
searchHeaderView.alpha = 1.0
|
||||||
.scaledBy(x: 1.0, y: 0.92 + 0.08 * visibleProgress)
|
searchHeaderView.isUserInteractionEnabled = clamped > 0.2
|
||||||
|
|
||||||
|
// Update internal content alpha + corner radius (Telegram behavior)
|
||||||
|
searchHeaderView.updateExpansionProgress(clamped)
|
||||||
|
|
||||||
updateNavigationBlurHeight()
|
updateNavigationBlurHeight()
|
||||||
updateNavigationBarBlur(progress: 1.0 - clamped)
|
updateNavigationBarBlur(progress: 1.0 - clamped)
|
||||||
@@ -838,12 +838,30 @@ private final class ChatListSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
setSearchActive(false, animated: animated, clearText: clearText)
|
setSearchActive(false, animated: animated, clearText: clearText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Telegram-style expansion animation:
|
||||||
|
/// - Corner radius scales with height (pill shape maintained)
|
||||||
|
/// - Text/icon alpha: invisible until 77% expanded, then ramps to 1.0
|
||||||
|
/// - Background: always visible (controlled by height clipping)
|
||||||
|
func updateExpansionProgress(_ progress: CGFloat) {
|
||||||
|
let currentHeight = searchBarHeight * progress
|
||||||
|
// Dynamic corner radius — Telegram: height * 0.5
|
||||||
|
capsuleView.layer.cornerRadius = max(0, currentHeight * 0.5)
|
||||||
|
|
||||||
|
// Telegram inner content alpha: 0 until 77%, then ramps to 1
|
||||||
|
let innerAlpha = max(0.0, min(1.0, (progress - 0.77) / 0.23))
|
||||||
|
placeholderStack.alpha = isSearchActive ? 0 : innerAlpha
|
||||||
|
placeholderIcon.alpha = innerAlpha
|
||||||
|
placeholderLabel.alpha = innerAlpha
|
||||||
|
}
|
||||||
|
|
||||||
|
private let searchBarHeight: CGFloat = 44
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
translatesAutoresizingMaskIntoConstraints = false
|
translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
capsuleView.translatesAutoresizingMaskIntoConstraints = false
|
capsuleView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
capsuleView.layer.cornerRadius = 22
|
capsuleView.layer.cornerRadius = 22
|
||||||
capsuleView.layer.borderWidth = 0.5
|
capsuleView.layer.borderWidth = 0
|
||||||
capsuleView.clipsToBounds = true
|
capsuleView.clipsToBounds = true
|
||||||
addSubview(capsuleView)
|
addSubview(capsuleView)
|
||||||
|
|
||||||
@@ -901,7 +919,7 @@ private final class ChatListSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
capsuleView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
capsuleView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
capsuleView.topAnchor.constraint(equalTo: topAnchor),
|
capsuleView.topAnchor.constraint(equalTo: topAnchor),
|
||||||
capsuleView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
capsuleView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
capsuleView.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -8),
|
capsuleView.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor),
|
||||||
|
|
||||||
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
cancelButton.centerYAnchor.constraint(equalTo: centerYAnchor),
|
cancelButton.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||||
@@ -921,13 +939,22 @@ private final class ChatListSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func applyColors() {
|
private func applyColors() {
|
||||||
capsuleView.backgroundColor = UIColor(RosettaColors.Adaptive.searchBarFill)
|
// Telegram exact colors from DefaultDarkPresentationTheme / DefaultDayPresentationTheme
|
||||||
capsuleView.layer.borderColor = UIColor(RosettaColors.Adaptive.searchBarBorder).cgColor
|
let isDark = traitCollection.userInterfaceStyle == .dark
|
||||||
placeholderLabel.textColor = .gray
|
// regularSearchBarColor: dark #272728, light #e9e9e9
|
||||||
placeholderIcon.tintColor = .gray
|
capsuleView.backgroundColor = isDark
|
||||||
activeIcon.tintColor = .gray
|
? UIColor(red: 0x27/255.0, green: 0x27/255.0, blue: 0x28/255.0, alpha: 1.0)
|
||||||
textField.textColor = UIColor(RosettaColors.Adaptive.text)
|
: UIColor(red: 0xe9/255.0, green: 0xe9/255.0, blue: 0xe9/255.0, alpha: 1.0)
|
||||||
inlineClearButton.tintColor = .gray
|
// inputPlaceholderTextColor: dark #8f8f8f, light #8e8e93
|
||||||
|
let placeholderColor = isDark
|
||||||
|
? UIColor(red: 0x8f/255.0, green: 0x8f/255.0, blue: 0x8f/255.0, alpha: 1.0)
|
||||||
|
: UIColor(red: 0x8e/255.0, green: 0x8e/255.0, blue: 0x93/255.0, alpha: 1.0)
|
||||||
|
placeholderLabel.textColor = placeholderColor
|
||||||
|
placeholderIcon.tintColor = placeholderColor
|
||||||
|
activeIcon.tintColor = placeholderColor
|
||||||
|
// inputTextColor: dark #ffffff, light #000000
|
||||||
|
textField.textColor = isDark ? .white : .black
|
||||||
|
inlineClearButton.tintColor = placeholderColor
|
||||||
cancelButton.setTitleColor(UIColor(RosettaColors.primaryBlue), for: .normal)
|
cancelButton.setTitleColor(UIColor(RosettaColors.primaryBlue), for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,8 +1039,11 @@ private final class ChatListSearchHeaderView: UIView, UITextFieldDelegate {
|
|||||||
|
|
||||||
private final class ChatListHeaderBlurView: UIView {
|
private final class ChatListHeaderBlurView: UIView {
|
||||||
|
|
||||||
private let edgeEffectView = UIView()
|
// Tint overlay — shows pinned section background color via gradient mask
|
||||||
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
private let tintView = UIView()
|
||||||
|
private let tintMaskView = UIImageView()
|
||||||
|
// CABackdropLayer — captures content behind and applies subtle blur
|
||||||
|
private var backdropLayer: CALayer?
|
||||||
private let fadeMaskLayer = CAGradientLayer()
|
private let fadeMaskLayer = CAGradientLayer()
|
||||||
private var plainBackgroundColor: UIColor = .black
|
private var plainBackgroundColor: UIColor = .black
|
||||||
private var pinnedBackgroundColor: UIColor = .black
|
private var pinnedBackgroundColor: UIColor = .black
|
||||||
@@ -1024,27 +1054,27 @@ private final class ChatListHeaderBlurView: UIView {
|
|||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
isUserInteractionEnabled = false
|
isUserInteractionEnabled = false
|
||||||
clipsToBounds = true
|
|
||||||
|
|
||||||
edgeEffectView.translatesAutoresizingMaskIntoConstraints = false
|
// Backdrop blur layer — very subtle (radius 1.0), no colorMatrix
|
||||||
blurView.translatesAutoresizingMaskIntoConstraints = false
|
if let backdrop = BackdropLayerHelper.createBackdropLayer() {
|
||||||
|
backdrop.delegate = BackdropLayerDelegate.shared
|
||||||
|
BackdropLayerHelper.setScale(backdrop, scale: 0.5)
|
||||||
|
if let blur = CALayer.blurFilter() {
|
||||||
|
blur.setValue(1.0 as NSNumber, forKey: "inputRadius")
|
||||||
|
backdrop.filters = [blur]
|
||||||
|
}
|
||||||
|
layer.addSublayer(backdrop)
|
||||||
|
self.backdropLayer = backdrop
|
||||||
|
}
|
||||||
|
|
||||||
addSubview(edgeEffectView)
|
// Tint view with gradient mask (for pinned section color)
|
||||||
addSubview(blurView)
|
tintView.mask = tintMaskView
|
||||||
|
tintView.alpha = 0.85
|
||||||
NSLayoutConstraint.activate([
|
addSubview(tintView)
|
||||||
edgeEffectView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
edgeEffectView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
edgeEffectView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
edgeEffectView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
|
|
||||||
blurView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
// Gradient fade mask on the whole view
|
||||||
layer.mask = fadeMaskLayer
|
layer.mask = fadeMaskLayer
|
||||||
|
|
||||||
applyAdaptiveColors()
|
applyAdaptiveColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,14 +1090,16 @@ private final class ChatListHeaderBlurView: UIView {
|
|||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
backdropLayer?.frame = bounds
|
||||||
|
tintView.frame = bounds
|
||||||
|
tintMaskView.frame = bounds
|
||||||
updateFadeMask()
|
updateFadeMask()
|
||||||
|
updateTintMask()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyAdaptiveColors() {
|
private func applyAdaptiveColors() {
|
||||||
plainBackgroundColor = UIColor(RosettaColors.Adaptive.background)
|
plainBackgroundColor = UIColor(RosettaColors.Adaptive.background)
|
||||||
pinnedBackgroundColor = UIColor(RosettaColors.Adaptive.pinnedSectionBackground)
|
pinnedBackgroundColor = UIColor(RosettaColors.Adaptive.pinnedSectionBackground)
|
||||||
blurView.effect = UIBlurEffect(style: .light)
|
|
||||||
configureTelegramBlurFilters()
|
|
||||||
updateEdgeEffectColor()
|
updateEdgeEffectColor()
|
||||||
updateChromeOpacity()
|
updateChromeOpacity()
|
||||||
}
|
}
|
||||||
@@ -1075,25 +1107,20 @@ private final class ChatListHeaderBlurView: UIView {
|
|||||||
private func updateEdgeEffectColor() {
|
private func updateEdgeEffectColor() {
|
||||||
let effectivePinnedFraction = isSearchCurrentlyActive ? 0.0 : currentPinnedFraction
|
let effectivePinnedFraction = isSearchCurrentlyActive ? 0.0 : currentPinnedFraction
|
||||||
let resolved = plainBackgroundColor.mixedWith(pinnedBackgroundColor, alpha: effectivePinnedFraction)
|
let resolved = plainBackgroundColor.mixedWith(pinnedBackgroundColor, alpha: effectivePinnedFraction)
|
||||||
edgeEffectView.backgroundColor = resolved
|
tintView.backgroundColor = resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateChromeOpacity() {
|
private func updateChromeOpacity() {
|
||||||
let clamped = max(0.0, min(1.0, currentProgress))
|
let clamped = max(0.0, min(1.0, currentProgress))
|
||||||
edgeEffectView.alpha = clamped
|
// Backdrop blur is always present — its visibility depends on content behind.
|
||||||
blurView.alpha = 0.85 * clamped
|
// Tint overlay fades in with scroll progress.
|
||||||
|
tintView.alpha = 0.85 * clamped
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureTelegramBlurFilters() {
|
private func updateTintMask() {
|
||||||
guard let sublayer = blurView.layer.sublayers?.first,
|
let height = max(1, bounds.height)
|
||||||
let filters = sublayer.filters else { return }
|
let edgeSize = min(54.0, height)
|
||||||
sublayer.backgroundColor = nil
|
tintMaskView.image = VariableBlurEdgeView.generateEdgeGradient(baseHeight: edgeSize)
|
||||||
sublayer.isOpaque = false
|
|
||||||
let allowedKeys: Set<String> = ["gaussianBlur", "colorSaturate"]
|
|
||||||
sublayer.filters = filters.filter { filter in
|
|
||||||
guard let obj = filter as? NSObject else { return true }
|
|
||||||
return allowedKeys.contains(String(describing: obj))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateFadeMask() {
|
private func updateFadeMask() {
|
||||||
@@ -1118,34 +1145,13 @@ private final class ChatListHeaderBlurView: UIView {
|
|||||||
|
|
||||||
private final class ChatListToolbarGlassCapsuleView: UIView {
|
private final class ChatListToolbarGlassCapsuleView: UIView {
|
||||||
|
|
||||||
private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemChromeMaterialDark))
|
private let glassView = TelegramGlassUIView(frame: .zero)
|
||||||
private let tintView = UIView()
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
isUserInteractionEnabled = false
|
isUserInteractionEnabled = false
|
||||||
clipsToBounds = true
|
clipsToBounds = false
|
||||||
|
addSubview(glassView)
|
||||||
blurView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
tintView.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
addSubview(blurView)
|
|
||||||
addSubview(tintView)
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
blurView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
blurView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
blurView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
blurView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
|
|
||||||
tintView.topAnchor.constraint(equalTo: topAnchor),
|
|
||||||
tintView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
||||||
tintView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
tintView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
layer.borderWidth = 1.0 / UIScreen.main.scale
|
|
||||||
applyColors()
|
|
||||||
configureTelegramBlurFilters()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@@ -1154,34 +1160,9 @@ private final class ChatListToolbarGlassCapsuleView: UIView {
|
|||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
applyColors()
|
glassView.frame = bounds
|
||||||
layer.cornerRadius = bounds.height * 0.5
|
glassView.fixedCornerRadius = bounds.height * 0.5
|
||||||
configureTelegramBlurFilters()
|
glassView.updateGlass()
|
||||||
}
|
|
||||||
|
|
||||||
private func applyColors() {
|
|
||||||
let isDark = traitCollection.userInterfaceStyle == .dark
|
|
||||||
blurView.effect = UIBlurEffect(style: isDark ? .systemChromeMaterialDark : .systemChromeMaterialLight)
|
|
||||||
blurView.alpha = isDark ? 0.88 : 0.82
|
|
||||||
|
|
||||||
tintView.backgroundColor = isDark
|
|
||||||
? UIColor(white: 0.0, alpha: 0.34)
|
|
||||||
: UIColor(white: 1.0, alpha: 0.28)
|
|
||||||
layer.borderColor = isDark
|
|
||||||
? UIColor.white.withAlphaComponent(0.12).cgColor
|
|
||||||
: UIColor.black.withAlphaComponent(0.10).cgColor
|
|
||||||
}
|
|
||||||
|
|
||||||
private func configureTelegramBlurFilters() {
|
|
||||||
guard let sublayer = blurView.layer.sublayers?.first,
|
|
||||||
let filters = sublayer.filters else { return }
|
|
||||||
sublayer.backgroundColor = nil
|
|
||||||
sublayer.isOpaque = false
|
|
||||||
let allowedKeys: Set<String> = ["gaussianBlur", "colorSaturate"]
|
|
||||||
sublayer.filters = filters.filter { filter in
|
|
||||||
guard let obj = filter as? NSObject else { return true }
|
|
||||||
return allowedKeys.contains(String(describing: obj))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1271,7 +1252,7 @@ private final class ChatListToolbarDualActionButton: UIView {
|
|||||||
addButton.accessibilityLabel = "Add"
|
addButton.accessibilityLabel = "Add"
|
||||||
composeButton.accessibilityLabel = "Compose"
|
composeButton.accessibilityLabel = "Compose"
|
||||||
|
|
||||||
let iconConfig = UIImage.SymbolConfiguration(pointSize: 16, weight: .medium)
|
let iconConfig = UIImage.SymbolConfiguration(pointSize: 18, weight: .medium)
|
||||||
let addIcon = UIImage(named: "toolbar-add-chat")?.withRenderingMode(.alwaysTemplate)
|
let addIcon = UIImage(named: "toolbar-add-chat")?.withRenderingMode(.alwaysTemplate)
|
||||||
?? UIImage(systemName: "plus", withConfiguration: iconConfig)
|
?? UIImage(systemName: "plus", withConfiguration: iconConfig)
|
||||||
let composeIcon = UIImage(named: "toolbar-compose")?.withRenderingMode(.alwaysTemplate)
|
let composeIcon = UIImage(named: "toolbar-compose")?.withRenderingMode(.alwaysTemplate)
|
||||||
@@ -1301,12 +1282,12 @@ private final class ChatListToolbarDualActionButton: UIView {
|
|||||||
addButton.leadingAnchor.constraint(equalTo: leadingAnchor),
|
addButton.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
addButton.topAnchor.constraint(equalTo: topAnchor),
|
addButton.topAnchor.constraint(equalTo: topAnchor),
|
||||||
addButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
addButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
addButton.widthAnchor.constraint(equalToConstant: 38),
|
addButton.widthAnchor.constraint(equalToConstant: 44),
|
||||||
|
|
||||||
composeButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
composeButton.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
composeButton.topAnchor.constraint(equalTo: topAnchor),
|
composeButton.topAnchor.constraint(equalTo: topAnchor),
|
||||||
composeButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
composeButton.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
composeButton.widthAnchor.constraint(equalToConstant: 38),
|
composeButton.widthAnchor.constraint(equalToConstant: 44),
|
||||||
|
|
||||||
dividerView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
dividerView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
dividerView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
dividerView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||||
@@ -1314,7 +1295,7 @@ private final class ChatListToolbarDualActionButton: UIView {
|
|||||||
dividerView.heightAnchor.constraint(equalToConstant: 20),
|
dividerView.heightAnchor.constraint(equalToConstant: 20),
|
||||||
|
|
||||||
heightAnchor.constraint(equalToConstant: 44),
|
heightAnchor.constraint(equalToConstant: 44),
|
||||||
widthAnchor.constraint(equalToConstant: 76),
|
widthAnchor.constraint(equalToConstant: 88),
|
||||||
])
|
])
|
||||||
|
|
||||||
self.frame = CGRect(origin: .zero, size: intrinsicContentSize)
|
self.frame = CGRect(origin: .zero, size: intrinsicContentSize)
|
||||||
@@ -1325,7 +1306,7 @@ private final class ChatListToolbarDualActionButton: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
CGSize(width: 76, height: 44)
|
CGSize(width: 88, height: 44)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleAddTapped() {
|
@objc private func handleAddTapped() {
|
||||||
|
|||||||
Reference in New Issue
Block a user