diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift index e8ed5a0b2f..02802ddc40 100644 --- a/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift @@ -1,17 +1,20 @@ import SwiftUI import UIKit -/// A 36pt circular chat avatar. Renders (priority): Saved-Messages bookmark glyph; +/// A circular chat avatar (default 36pt, customizable via `size`). Renders (priority): /// crisp downloaded photo; blurred minithumbnail placeholder; initials on a -/// per-chat color. Drives viewport-based download of the small photo via -/// `.onScrollVisibilityChange` (same as `PhotoBubbleView`) — only while the photo -/// exists and hasn't downloaded yet. +/// per-chat color. When embedded in a `ScrollView`, drives viewport-based download +/// of the small photo via `.onScrollVisibilityChange` (same as `PhotoBubbleView`) — +/// only while the photo exists and hasn't downloaded yet. Outside a scroll +/// container (e.g. as a `ToolbarItem`), the visibility callback fires once on +/// appear and once on disappear, so the download path is "request on chat open, +/// cancel on chat close." struct AvatarView: View { let avatar: AvatarVisual var onRequestDownload: (Int) -> Void = { _ in } var onCancelDownload: (Int) -> Void = { _ in } - private let size: CGFloat = 36 + var size: CGFloat = 36 var body: some View { content diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift index 73f4a3c02a..f6dd7e2c78 100644 --- a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift @@ -13,39 +13,42 @@ struct ChatListView: View { @FocusState private var listFocused: Bool var body: some View { - ZStack(alignment: .top) { - NavigationStack { - ScrollViewReader { proxy in - VStack(spacing: 0) { - if case .failed(let message) = store.loadState(for: store.currentFolder) { - banner(text: message, kind: .retry) - } - if let err = client.lastError, dismissedLastError != err { - banner(text: err, kind: .dismiss) - } - content(proxy: proxy) + NavigationStack { + ScrollViewReader { proxy in + VStack(spacing: 0) { + if case .failed(let message) = store.loadState(for: store.currentFolder) { + banner(text: message, kind: .retry) } - .navigationDestination(for: ChatRow.self) { row in - MessageListView(row: row) + if let err = client.lastError, dismissedLastError != err { + banner(text: err, kind: .dismiss) } - .navigationTitle("") - .navigationBarTitleDisplayMode(.inline) - // Must live INSIDE the NavigationStack so the modifier's - // .navigationDestination(isPresented:) attaches to it. - .accountSwitcherSheet(presentation: .push, logoutAffordance: .allowed) + content(proxy: proxy) } + // Gradient is .overlay on the chat-list VStack — NOT a sibling + // of NavigationStack. Earlier this lived in an outer ZStack and + // rendered on top of every pushed destination (e.g. MessageListView's + // back chevron + title), since ZStack draws siblings front-to-back + // regardless of which destination is current. Scoping it to the + // root view's overlay restricts it to the chat list itself. + .overlay(alignment: .top) { + LinearGradient( + colors: [.black.opacity(0.8), .black.opacity(0)], + startPoint: .top, + endPoint: .bottom + ) + .frame(height: 48) + .allowsHitTesting(false) + .ignoresSafeArea(edges: .top) + } + .navigationDestination(for: ChatRow.self) { row in + MessageListView(row: row) + } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) + // Must live INSIDE the NavigationStack so the modifier's + // .navigationDestination(isPresented:) attaches to it. + .accountSwitcherSheet(presentation: .push, logoutAffordance: .allowed) } - // Gradient that fades chat content behind the system clock area, - // so rows scrolling up under the clock don't visually collide - // with the time readout. - LinearGradient( - colors: [.black.opacity(0.8), .black.opacity(0)], - startPoint: .top, - endPoint: .bottom - ) - .frame(height: 48) - .allowsHitTesting(false) - .ignoresSafeArea(edges: .top) } .accessibilityIdentifier("chatListView") } diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift index 9f7e45b1a8..d653925466 100644 --- a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift @@ -56,6 +56,17 @@ struct MessageListView: View { } .navigationTitle(row.title) .accessibilityIdentifier("messageListView") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + AvatarView( + avatar: row.avatar, + onRequestDownload: { fileId in store?.requestFileDownload(fileId: fileId) }, + onCancelDownload: { fileId in store?.cancelFileDownload(fileId: fileId) }, + size: 36 + ) + .glassEffect(in: Circle()) + } + } .sheet(item: $presentedPhoto) { photo in PhotoViewerView(photo: photo) }