photo view edit

This commit is contained in:
cheykrym 2025-12-10 02:59:17 +03:00
parent 13a1d00934
commit f3653c5617
2 changed files with 120 additions and 38 deletions

View File

@ -76,6 +76,9 @@
} }
} }
} }
},
"1 из 1" : {
}, },
"2FA включена" : { "2FA включена" : {
"comment" : "Заголовок уведомления об успешной активации 2FA" "comment" : "Заголовок уведомления об успешной активации 2FA"

View File

@ -408,22 +408,43 @@ struct AvatarViewerView: View {
let onDownload: () -> Void let onDownload: () -> Void
let onDelete: () -> Void let onDelete: () -> Void
@State private var scale: CGFloat = 1.0
@State private var baseScale: CGFloat = 1.0
@State private var panOffset: CGSize = .zero
@State private var storedPanOffset: CGSize = .zero
@State private var dismissOffset: CGSize = .zero
private var currentOffset: CGSize {
scale > 1.05 ? panOffset : dismissOffset
}
var body: some View { var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
ZStack { ZStack {
Color.black.ignoresSafeArea() Color.black.ignoresSafeArea()
avatarContent
zoomableContent
topOverlay
} }
.navigationBarTitleDisplayMode(.inline) }
.toolbar {
ToolbarItem(placement: .navigationBarLeading) { private var topOverlay: some View {
VStack(spacing: 8) {
HStack {
Button(action: onClose) { Button(action: onClose) {
Image(systemName: "xmark") Image(systemName: "xmark")
.imageScale(.large)
} }
.tint(.white) .tint(.white)
}
ToolbarItem(placement: .navigationBarTrailing) { Spacer()
Text("1 из 1")
.font(.subheadline.weight(.semibold))
.foregroundColor(.white.opacity(0.8))
Spacer()
Menu { Menu {
Button(action: onDownload) { Button(action: onDownload) {
Label(NSLocalizedString("Скачать", comment: "Avatar download"), systemImage: "square.and.arrow.down") Label(NSLocalizedString("Скачать", comment: "Avatar download"), systemImage: "square.and.arrow.down")
@ -437,19 +458,18 @@ struct AvatarViewerView: View {
} }
.tint(.white) .tint(.white)
} }
} .padding(.horizontal)
} .padding(.top, 24)
Spacer()
} }
} }
@ViewBuilder @ViewBuilder
private var avatarContent: some View { private var zoomableContent: some View {
switch state.source { switch state.source {
case .local(let image): case .local(let image):
Image(uiImage: image) zoomableImage(Image(uiImage: image))
.resizable()
.scaledToFit()
.padding()
case .remote(let url, _, _): case .remote(let url, _, _):
AsyncImage(url: url) { phase in AsyncImage(url: url) { phase in
switch phase { switch phase {
@ -457,10 +477,7 @@ struct AvatarViewerView: View {
ProgressView() ProgressView()
.progressViewStyle(.circular) .progressViewStyle(.circular)
case .success(let image): case .success(let image):
image zoomableImage(image)
.resizable()
.scaledToFit()
.padding()
case .failure: case .failure:
Image(systemName: "person.crop.circle.badge.exclam") Image(systemName: "person.crop.circle.badge.exclam")
.resizable() .resizable()
@ -473,6 +490,68 @@ struct AvatarViewerView: View {
} }
} }
} }
private func zoomableImage(_ image: Image) -> some View {
image
.resizable()
.scaledToFit()
.offset(currentOffset)
.scaleEffect(scale, anchor: .center)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.gesture(dragGesture)
.simultaneousGesture(magnificationGesture)
}
private var dragGesture: some Gesture {
DragGesture()
.onChanged { value in
if scale > 1.05 {
dismissOffset = .zero
panOffset = CGSize(
width: storedPanOffset.width + value.translation.width,
height: storedPanOffset.height + value.translation.height
)
} else {
dismissOffset = value.translation
}
}
.onEnded { value in
if scale > 1.05 {
storedPanOffset = CGSize(
width: storedPanOffset.width + value.translation.width,
height: storedPanOffset.height + value.translation.height
)
} else {
if abs(value.translation.height) > 120 {
onClose()
} else {
withAnimation(.spring()) {
dismissOffset = .zero
}
}
}
}
}
private var magnificationGesture: some Gesture {
MagnificationGesture()
.onChanged { value in
let newScale = baseScale * value
scale = min(max(newScale, 1), 4)
}
.onEnded { _ in
baseScale = scale
if baseScale <= 1.02 {
baseScale = 1
withAnimation(.spring()) {
scale = 1
storedPanOffset = .zero
panOffset = .zero
dismissOffset = .zero
}
}
}
}
} }
struct ActivityView: UIViewControllerRepresentable { struct ActivityView: UIViewControllerRepresentable {