Update tgwatch

This commit is contained in:
isaac 2026-05-28 21:10:19 +02:00
parent 55619c54f9
commit 3a62d3417f
3 changed files with 51 additions and 34 deletions

View file

@ -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

View file

@ -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")
}

View file

@ -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)
}