photo view edit
This commit is contained in:
parent
13a1d00934
commit
f3653c5617
@ -76,6 +76,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"1 из 1" : {
|
||||
|
||||
},
|
||||
"2FA включена" : {
|
||||
"comment" : "Заголовок уведомления об успешной активации 2FA"
|
||||
|
||||
@ -408,22 +408,43 @@ struct AvatarViewerView: View {
|
||||
let onDownload: () -> 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 {
|
||||
if #available(iOS 16.0, *) {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
avatarContent
|
||||
|
||||
zoomableContent
|
||||
|
||||
topOverlay
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
}
|
||||
|
||||
private var topOverlay: some View {
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Button(action: onClose) {
|
||||
Image(systemName: "xmark")
|
||||
.imageScale(.large)
|
||||
}
|
||||
.tint(.white)
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("1 из 1")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
|
||||
Spacer()
|
||||
|
||||
Menu {
|
||||
Button(action: onDownload) {
|
||||
Label(NSLocalizedString("Скачать", comment: "Avatar download"), systemImage: "square.and.arrow.down")
|
||||
@ -437,19 +458,18 @@ struct AvatarViewerView: View {
|
||||
}
|
||||
.tint(.white)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 24)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var avatarContent: some View {
|
||||
private var zoomableContent: some View {
|
||||
switch state.source {
|
||||
case .local(let image):
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.padding()
|
||||
zoomableImage(Image(uiImage: image))
|
||||
case .remote(let url, _, _):
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
@ -457,10 +477,7 @@ struct AvatarViewerView: View {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.padding()
|
||||
zoomableImage(image)
|
||||
case .failure:
|
||||
Image(systemName: "person.crop.circle.badge.exclam")
|
||||
.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 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user