Фикс: группы — пароль вложений hex→plain (Android parity, Desktop decrypt fix)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user