Фикс: группы — пароль вложений hex→plain (Android parity, Desktop decrypt fix)

This commit is contained in:
2026-04-06 00:48:07 +05:00
parent 55cb120db3
commit cdb6c7e51e
5 changed files with 109 additions and 92 deletions

View File

@@ -550,7 +550,7 @@ final class NativeMessageCell: UICollectionViewCell {
let isOutgoing = currentLayout?.isOutgoing ?? false
let isMediaStatus: Bool = {
guard let type = currentLayout?.messageType else { return false }
return type == .photo || type == .photoWithCaption || type == .emojiOnly
return type == .photo || type == .emojiOnly
}()
// Text use cached CoreTextTextLayout from measurement phase.

View File

@@ -21,80 +21,83 @@ struct ZoomableImagePage: View {
@GestureState private var pinchScale: CGFloat = 1.0
var body: some View {
Group {
if let image {
let effectiveScale = zoomScale * pinchScale
let effectiveScale = zoomScale * pinchScale
Image(uiImage: image)
.resizable()
.scaledToFit()
.scaleEffect(effectiveScale)
.offset(x: effectiveScale > 1.05 ? zoomOffset.width : 0,
y: effectiveScale > 1.05 ? zoomOffset.height : 0)
// Expand hit-test area to full screen scaleEffect is visual-only
// and doesn't grow the Image's gesture frame. Without this,
// double-tap to zoom out doesn't work on zoomed-in edges.
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
// Double tap: zoom to 2.5x or reset (MUST be before single tap)
.onTapGesture(count: 2) {
// Color.clear always fills ALL proposed space from the parent TabView page,
// hero frame, etc. The Image in .overlay sizes relative to Color.clear's actual
// rendered frame. Previous approach (.scaledToFit + .frame(maxWidth: .infinity))
// sometimes got a stale/zero proposed size from TabView lazy page creation,
// causing the image to render at thumbnail size.
Color.clear
.overlay {
if let image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.scaleEffect(effectiveScale)
.offset(
x: effectiveScale > 1.05 ? zoomOffset.width : 0,
y: effectiveScale > 1.05 ? zoomOffset.height : 0
)
} else {
placeholder
}
}
.contentShape(Rectangle())
// Double tap: zoom to 2.5x or reset (MUST be before single tap)
.onTapGesture(count: 2) {
withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {
if zoomScale > 1.1 {
zoomScale = 1.0
zoomOffset = .zero
} else {
zoomScale = 2.5
}
currentScale = zoomScale
}
}
// Single tap: toggle controls / edge navigation
.onTapGesture { location in
let width = UIScreen.main.bounds.width
let edgeZone = width * 0.20
if location.x < edgeZone {
onEdgeTap?(-1)
} else if location.x > width - edgeZone {
onEdgeTap?(1)
} else {
showControls.toggle()
}
}
// Pinch zoom
.simultaneousGesture(
MagnifyGesture()
.updating($pinchScale) { value, state, _ in
state = value.magnification
}
.onEnded { value in
let newScale = zoomScale * value.magnification
withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {
if zoomScale > 1.1 {
zoomScale = min(max(newScale, 1.0), 5.0)
if zoomScale <= 1.05 {
zoomScale = 1.0
zoomOffset = .zero
} else {
zoomScale = 2.5
}
currentScale = zoomScale
}
}
// Single tap: toggle controls / edge navigation
.onTapGesture { location in
let width = UIScreen.main.bounds.width
let edgeZone = width * 0.20
if location.x < edgeZone {
onEdgeTap?(-1)
} else if location.x > width - edgeZone {
onEdgeTap?(1)
} else {
showControls.toggle()
}
)
// Pan when zoomed
.simultaneousGesture(
zoomScale > 1.05 ?
DragGesture()
.onChanged { value in
zoomOffset = value.translation
}
// Pinch zoom
.simultaneousGesture(
MagnifyGesture()
.updating($pinchScale) { value, state, _ in
state = value.magnification
}
.onEnded { value in
let newScale = zoomScale * value.magnification
withAnimation(.spring(response: 0.35, dampingFraction: 0.9)) {
zoomScale = min(max(newScale, 1.0), 5.0)
if zoomScale <= 1.05 {
zoomScale = 1.0
zoomOffset = .zero
}
currentScale = zoomScale
}
}
)
// Pan when zoomed
.simultaneousGesture(
zoomScale > 1.05 ?
DragGesture()
.onChanged { value in
zoomOffset = value.translation
}
.onEnded { _ in
// Clamp offset
}
: nil
)
// Dismiss drag handled by HeroPanGesture on ImageGalleryViewer level.
} else {
placeholder
}
}
.onEnded { _ in
// Clamp offset
}
: nil
)
.task {
if let cached = AttachmentCache.shared.cachedImage(forAttachmentId: attachmentId) {
image = cached