photo view edit
This commit is contained in:
parent
13a1d00934
commit
f3653c5617
@ -76,6 +76,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"1 из 1" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"2FA включена" : {
|
"2FA включена" : {
|
||||||
"comment" : "Заголовок уведомления об успешной активации 2FA"
|
"comment" : "Заголовок уведомления об успешной активации 2FA"
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user