Пуш-нотификации, кросс-платформенный аудит + 65 новых тестов (badge, dedup, Desktop-suppression, payload parity)

This commit is contained in:
2026-04-07 18:38:35 +05:00
parent ff8eca710d
commit d84c867bd3
6 changed files with 906 additions and 16 deletions

View File

@@ -255,6 +255,14 @@ struct ChatDetailView: View {
pendingGroupInviteTitle = parsed.title
}
}
cellActions.onGroupInviteOpen = { dialogKey in
let title = DialogRepository.shared.dialogs[dialogKey]?.opponentTitle ?? "Group"
let route = ChatRoute(groupDialogKey: dialogKey, title: title)
NotificationCenter.default.post(
name: .openChatFromNotification,
object: route
)
}
// Capture first unread incoming message BEFORE marking as read.
if firstUnreadMessageId == nil {
firstUnreadMessageId = messages.first(where: {

View File

@@ -450,9 +450,10 @@ struct ImageGalleryViewer: View {
// MARK: - GalleryPageModifier
/// Applies hero transition frame/offset ONLY for the initial page.
/// Non-hero pages have NO explicit frame they fill the TabView page naturally,
/// which fixes the "tiny image" bug caused by explicit frame fighting with TabView sizing.
/// Applies hero transition frame/offset for the initial page.
/// Uses a SINGLE view branch (no if/else) to preserve SwiftUI structural identity
/// across the hero expanded transition. This prevents GeometryReader inside
/// ZoomableImagePage from receiving stale/incorrect proposed sizes during the swap.
private struct GalleryPageModifier: ViewModifier {
let heroActive: Bool
let sourceFrame: CGRect
@@ -460,16 +461,17 @@ private struct GalleryPageModifier: ViewModifier {
let dragOffset: CGSize
func body(content: Content) -> some View {
if heroActive {
content
.frame(width: sourceFrame.width, height: sourceFrame.height)
.clipped()
.offset(x: sourceFrame.minX, y: sourceFrame.minY)
.offset(dragOffset)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
} else {
content
.offset(dragOffset)
}
content
.frame(
width: heroActive ? sourceFrame.width : fullSize.width,
height: heroActive ? sourceFrame.height : fullSize.height
)
.clipped()
.offset(
x: heroActive ? sourceFrame.minX : 0,
y: heroActive ? sourceFrame.minY : 0
)
.offset(dragOffset)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
}

View File

@@ -43,6 +43,7 @@ struct ZoomableImagePage: View {
.position(x: viewSize.width / 2, y: viewSize.height / 2)
}
}
.ignoresSafeArea()
.contentShape(Rectangle())
.onTapGesture(count: 2) {
withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {