Telegram-стиль затемнение сверху в ChatDetailView (iOS < 26)

This commit is contained in:
2026-03-09 18:24:52 +05:00
parent b1f71c43f0
commit fd948991f3
14 changed files with 596 additions and 35 deletions

View File

@@ -89,21 +89,22 @@ struct ChatDetailView: View {
private var content: some View {
GeometryReader { geometry in
ZStack {
RosettaColors.Adaptive.background.ignoresSafeArea()
tiledChatBackground
.ignoresSafeArea()
messagesList(maxBubbleWidth: max(min(geometry.size.width * 0.72, 380), 140))
}
.overlay { chatEdgeGradients }
.safeAreaInset(edge: .bottom, spacing: 0) { composer }
.background {
ZStack {
RosettaColors.Adaptive.background
tiledChatBackground
}
.ignoresSafeArea()
}
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true) // скрываем стандартный back, но НЕ навбар
.navigationBarBackButtonHidden(true)
.enableSwipeBack()
.toolbarBackground(.visible, for: .navigationBar)
.applyGlassNavBar()
.toolbar { chatDetailToolbar } // твой header тут
.modifier(ChatDetailNavBarStyleModifier())
.toolbar { chatDetailToolbar }
.toolbar(.hidden, for: .tabBar)
.task {
isViewActive = true
@@ -158,7 +159,7 @@ private extension ChatDetailView {
}
Text(subtitleText)
.font(.system(size: 11, weight: .regular))
.font(.system(size: 12, weight: .medium))
.foregroundStyle(
isTyping || (dialog?.isOnline == true)
? RosettaColors.online
@@ -216,6 +217,39 @@ private extension ChatDetailView {
RosettaColors.adaptive(light: Color(hex: 0x2C2C2E), dark: Color(hex: 0x2C2C2E))
}
// MARK: - Edge Gradients (Telegram-style)
/// Top: native SwiftUI Material blur with gradient mask blurs content behind it.
@ViewBuilder
var chatEdgeGradients: some View {
if #available(iOS 26, *) {
EmptyView()
} else {
VStack(spacing: 0) {
// Telegram-style: dark gradient that smoothly fades content into
// the dark background behind the nav bar pills.
// NOT a material blur Telegram uses dark overlay, not light material.
LinearGradient(
stops: [
.init(color: Color.black.opacity(0.85), location: 0.0),
.init(color: Color.black.opacity(0.75), location: 0.2),
.init(color: Color.black.opacity(0.55), location: 0.4),
.init(color: Color.black.opacity(0.3), location: 0.6),
.init(color: Color.black.opacity(0.12), location: 0.78),
.init(color: Color.black.opacity(0.0), location: 1.0),
],
startPoint: .top,
endPoint: .bottom
)
.frame(height: 90)
Spacer()
}
.ignoresSafeArea(edges: .top)
.allowsHitTesting(false)
}
}
/// Tiled chat background with properly scaled tiles (200pt wide)
private var tiledChatBackground: some View {
Group {
@@ -538,7 +572,26 @@ private extension ChatDetailView {
.animation(composerAnimation, value: shouldShowSendButton)
.animation(composerAnimation, value: isInputFocused)
}
.background(Color.clear)
.background {
if #available(iOS 26, *) {
Color.clear
} else {
// Telegram-style: dark gradient below composer home indicator
VStack(spacing: 0) {
Spacer()
LinearGradient(
colors: [
Color.black.opacity(0.0),
Color.black.opacity(0.55)
],
startPoint: .top,
endPoint: .bottom
)
.frame(height: 34)
}
.ignoresSafeArea(edges: .bottom)
}
}
}
// MARK: - Bubbles / Glass
@@ -575,7 +628,7 @@ private extension ChatDetailView {
func glass(
shape: ChatGlassShape,
strokeOpacity: Double = 0.18,
strokeColor: Color = RosettaColors.Adaptive.border
strokeColor: Color = .white
) -> some View {
if #available(iOS 26.0, *) {
switch shape {
@@ -589,14 +642,21 @@ private extension ChatDetailView {
.glassEffect(.regular, in: RoundedRectangle(cornerRadius: radius, style: .continuous))
}
} else {
// iOS < 26: frosted glass with stroke + shadow (Figma spec)
switch shape {
case .capsule:
Capsule().fill(.ultraThinMaterial)
Capsule().fill(.thinMaterial)
.overlay { Capsule().strokeBorder(strokeColor.opacity(strokeOpacity), lineWidth: 0.5) }
.shadow(color: .black.opacity(0.12), radius: 20, y: 8)
case .circle:
Circle().fill(.ultraThinMaterial)
Circle().fill(.thinMaterial)
.overlay { Circle().strokeBorder(strokeColor.opacity(strokeOpacity), lineWidth: 0.5) }
.shadow(color: .black.opacity(0.12), radius: 20, y: 8)
case let .rounded(radius):
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(.ultraThinMaterial)
let r = RoundedRectangle(cornerRadius: radius, style: .continuous)
r.fill(.thinMaterial)
.overlay { r.strokeBorder(strokeColor.opacity(strokeOpacity), lineWidth: 0.5) }
.shadow(color: .black.opacity(0.12), radius: 20, y: 8)
}
}
}
@@ -725,6 +785,22 @@ private extension ChatDetailView {
}()
}
// MARK: - Nav Bar Style (ChatDetail-specific)
/// ChatDetail uses a transparent nav bar so glass/blur pills float independently.
/// ChatListView & SettingsView keep `.applyGlassNavBar()` with `.regularMaterial`.
private struct ChatDetailNavBarStyleModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 26, *) {
content
.toolbarBackground(.hidden, for: .navigationBar)
} else {
content
.toolbarBackground(.hidden, for: .navigationBar)
}
}
}
// MARK: - Button Styles
private struct ChatDetailGlassPressButtonStyle: ButtonStyle {