Фикс: пуш-аватарки (Communication Notification entitlement) + in-app баннер 1:1 Telegram parity
This commit is contained in:
@@ -13,7 +13,7 @@ enum ReleaseNotes {
|
||||
body: """
|
||||
|
||||
**Пуш-уведомления**
|
||||
In-app баннеры (Telegram parity) — при получении сообщения внутри приложения. Исправлен спам вибраций при входе. Аватарки в пушах совпадают с приложением. Группы без фото — иконка людей. Desktop-suppression 30 сек.
|
||||
Аватарки отправителей в системных пушах (Communication Notification). In-app баннер переделан 1-в-1 как в Telegram (glass-фон, жесты, анимации). Исправлен спам вибраций при входе. Desktop-suppression 30 сек.
|
||||
"""
|
||||
)
|
||||
]
|
||||
|
||||
@@ -5,13 +5,13 @@ import SwiftUI
|
||||
/// Telegram-style in-app notification banner shown when a message arrives
|
||||
/// while the app is in foreground and the user is NOT in that chat.
|
||||
///
|
||||
/// Specs (from Telegram iOS `ChatMessageNotificationItem.swift`):
|
||||
/// - Panel: 74pt height, 24pt corner radius, 8pt horizontal margin
|
||||
/// - Avatar: 54pt circle, 12pt left inset
|
||||
/// Specs (from Telegram iOS `ChatMessageNotificationItem.swift` + `NotificationItemContainerNode.swift`):
|
||||
/// - Panel: 74pt min height, 24pt corner radius, 8pt horizontal margin
|
||||
/// - Avatar: 54pt circle, 12pt left inset, 23pt avatar-to-text spacing
|
||||
/// - Title: semibold 16pt, white, 1 line
|
||||
/// - Message: regular 16pt, white 70% opacity, max 2 lines
|
||||
/// - Background: ultraThinMaterial (dark)
|
||||
/// - Slide from top: 0.4s, auto-dismiss: 5s, swipe-up dismiss
|
||||
/// - Message: regular 16pt, white (full), max 2 lines
|
||||
/// - Background: TelegramGlass (CABackdropLayer / UIGlassEffect)
|
||||
/// - Slide from top: 0.4s, auto-dismiss: 5s, swipe dismiss with 0.55 damping
|
||||
struct InAppBannerView: View {
|
||||
let senderName: String
|
||||
let messagePreview: String
|
||||
@@ -28,7 +28,7 @@ struct InAppBannerView: View {
|
||||
private let horizontalMargin: CGFloat = 8
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 23) {
|
||||
avatarView
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
Text(senderName)
|
||||
@@ -37,36 +37,44 @@ struct InAppBannerView: View {
|
||||
.lineLimit(1)
|
||||
Text(messagePreview)
|
||||
.font(.system(size: 16))
|
||||
.foregroundStyle(.white.opacity(0.7))
|
||||
.foregroundStyle(.white)
|
||||
.lineLimit(2)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.frame(height: panelHeight)
|
||||
.padding(.leading, 12)
|
||||
.padding(.trailing, 10)
|
||||
.frame(minHeight: panelHeight)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.glass(cornerRadius: cornerRadius)
|
||||
.padding(.horizontal, horizontalMargin)
|
||||
.offset(y: dragOffset)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 5)
|
||||
.onChanged { value in
|
||||
// Only allow upward drag (negative Y).
|
||||
if value.translation.height < 0 {
|
||||
dragOffset = value.translation.height
|
||||
let translation = value.translation.height
|
||||
if translation < 0 {
|
||||
// Dragging up — full 1:1 tracking.
|
||||
dragOffset = translation
|
||||
} else {
|
||||
// Dragging down — logarithmic rubber-band (Telegram: 0.55/50 cap).
|
||||
// Formula: -((1 - 1/((delta*0.55/50)+1)) * 50)
|
||||
let delta = translation
|
||||
dragOffset = (1.0 - (1.0 / ((delta * 0.55 / 50.0) + 1.0))) * 50.0
|
||||
}
|
||||
}
|
||||
.onEnded { value in
|
||||
// Dismiss if dragged up > 20pt or velocity > 300.
|
||||
if value.translation.height < -20
|
||||
|| value.predictedEndTranslation.height < -100 {
|
||||
withAnimation(.easeOut(duration: 0.3)) {
|
||||
let velocity = value.predictedEndTranslation.height - value.translation.height
|
||||
// Dismiss: swiped up > 20pt or fast upward velocity (Telegram: -20pt / 300pt/s).
|
||||
if value.translation.height < -20 || velocity < -300 {
|
||||
withAnimation(.easeOut(duration: 0.4)) {
|
||||
dragOffset = -200
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
|
||||
onDismiss()
|
||||
}
|
||||
} else {
|
||||
// Spring back (Telegram: 0.3s easeInOut).
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
dragOffset = 0
|
||||
}
|
||||
|
||||
@@ -850,7 +850,8 @@ struct RosettaApp: App {
|
||||
|
||||
@ViewBuilder
|
||||
private var inAppBannerOverlay: some View {
|
||||
VStack {
|
||||
// Telegram parity: 8pt inset below safe area top (NotificationItemContainerNode.swift:98).
|
||||
VStack(spacing: 0) {
|
||||
if let banner = bannerManager.currentBanner {
|
||||
InAppBannerView(
|
||||
senderName: banner.senderName,
|
||||
|
||||
Reference in New Issue
Block a user