238 lines
7.8 KiB
Swift
238 lines
7.8 KiB
Swift
//
|
|
// ImageCarousel.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2022-10-16.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Kingfisher
|
|
|
|
// TODO: all this ShareSheet complexity can be replaced with ShareLink once we update to iOS 16
|
|
struct ShareSheet: UIViewControllerRepresentable {
|
|
typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
|
|
|
|
let activityItems: [URL?]
|
|
let callback: Callback? = nil
|
|
let applicationActivities: [UIActivity]? = nil
|
|
let excludedActivityTypes: [UIActivity.ActivityType]? = nil
|
|
|
|
func makeUIViewController(context: Context) -> UIActivityViewController {
|
|
let controller = UIActivityViewController(
|
|
activityItems: activityItems as [Any],
|
|
applicationActivities: applicationActivities)
|
|
controller.excludedActivityTypes = excludedActivityTypes
|
|
controller.completionWithItemsHandler = callback
|
|
return controller
|
|
}
|
|
|
|
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
|
|
// nothing to do here
|
|
}
|
|
}
|
|
|
|
struct ImageContextMenuModifier: ViewModifier {
|
|
let url: URL?
|
|
let image: UIImage?
|
|
@Binding var showShareSheet: Bool
|
|
|
|
func body(content: Content) -> some View {
|
|
return content.contextMenu {
|
|
Button {
|
|
UIPasteboard.general.url = url
|
|
} label: {
|
|
Label(NSLocalizedString("Copy Image URL", comment: "Context menu option to copy the URL of an image into clipboard."), systemImage: "doc.on.doc")
|
|
}
|
|
if let someImage = image {
|
|
Button {
|
|
UIPasteboard.general.image = someImage
|
|
} label: {
|
|
Label(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image into clipboard."), systemImage: "photo.on.rectangle")
|
|
}
|
|
Button {
|
|
UIImageWriteToSavedPhotosAlbum(someImage, nil, nil, nil)
|
|
} label: {
|
|
Label(NSLocalizedString("Save Image", comment: "Context menu option to save an image."), systemImage: "square.and.arrow.down")
|
|
}
|
|
}
|
|
Button {
|
|
showShareSheet = true
|
|
} label: {
|
|
Label(NSLocalizedString("Share", comment: "Button to share an image."), systemImage: "square.and.arrow.up")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct ImageContainerView: View {
|
|
|
|
let url: URL?
|
|
|
|
@State private var image: UIImage?
|
|
@State private var showShareSheet = false
|
|
|
|
private struct ImageHandler: ImageModifier {
|
|
@Binding var handler: UIImage?
|
|
|
|
func modify(_ image: UIImage) -> UIImage {
|
|
handler = image
|
|
return image
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
|
|
KFAnimatedImage(url)
|
|
.imageContext(.note)
|
|
.configure { view in
|
|
view.framePreloadCount = 3
|
|
}
|
|
.imageModifier(ImageHandler(handler: $image))
|
|
.clipped()
|
|
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
|
|
.sheet(isPresented: $showShareSheet) {
|
|
ShareSheet(activityItems: [url])
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ImageView: View {
|
|
|
|
let urls: [URL?]
|
|
|
|
@Environment(\.presentationMode) var presentationMode
|
|
|
|
@State private var selectedIndex = 0
|
|
@State var showMenu = true
|
|
|
|
var navBarView: some View {
|
|
VStack {
|
|
HStack {
|
|
Text(urls[selectedIndex]?.lastPathComponent ?? "")
|
|
.bold()
|
|
|
|
Spacer()
|
|
|
|
Button(action: {
|
|
presentationMode.wrappedValue.dismiss()
|
|
}, label: {
|
|
Image(systemName: "xmark")
|
|
})
|
|
}
|
|
.padding()
|
|
|
|
Divider()
|
|
.ignoresSafeArea()
|
|
}
|
|
.background(.regularMaterial)
|
|
}
|
|
|
|
var tabViewIndicator: some View {
|
|
HStack(spacing: 10) {
|
|
ForEach(urls.indices, id: \.self) { index in
|
|
Capsule()
|
|
.fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary)
|
|
.frame(width: 7, height: 7)
|
|
}
|
|
}
|
|
.padding()
|
|
.background(.regularMaterial)
|
|
.clipShape(Capsule())
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Color(.systemBackground)
|
|
.ignoresSafeArea()
|
|
|
|
TabView(selection: $selectedIndex) {
|
|
ForEach(urls.indices, id: \.self) { index in
|
|
ZoomableScrollView {
|
|
ImageContainerView(url: urls[index])
|
|
.aspectRatio(contentMode: .fit)
|
|
.padding(.top, Theme.safeAreaInsets?.top)
|
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
|
}
|
|
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
|
presentationMode.wrappedValue.dismiss()
|
|
}))
|
|
.ignoresSafeArea()
|
|
.tag(index)
|
|
}
|
|
}
|
|
.ignoresSafeArea()
|
|
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
|
.gesture(TapGesture(count: 2).onEnded {
|
|
// Prevents menu from hiding on double tap
|
|
})
|
|
.gesture(TapGesture(count: 1).onEnded {
|
|
showMenu.toggle()
|
|
})
|
|
.overlay(
|
|
VStack {
|
|
if showMenu {
|
|
navBarView
|
|
Spacer()
|
|
|
|
if (urls.count > 1) {
|
|
tabViewIndicator
|
|
}
|
|
}
|
|
}
|
|
.animation(.easeInOut, value: showMenu)
|
|
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ImageCarousel: View {
|
|
var urls: [URL]
|
|
|
|
@State var open_sheet: Bool = false
|
|
@State var current_url: URL? = nil
|
|
|
|
var body: some View {
|
|
TabView {
|
|
ForEach(urls, id: \.absoluteString) { url in
|
|
Rectangle()
|
|
.foregroundColor(Color.clear)
|
|
.overlay {
|
|
KFAnimatedImage(url)
|
|
.imageContext(.note)
|
|
.cancelOnDisappear(true)
|
|
.configure { view in
|
|
view.framePreloadCount = 3
|
|
}
|
|
.aspectRatio(contentMode: .fit)
|
|
.cornerRadius(10)
|
|
.tabItem {
|
|
Text(url.absoluteString)
|
|
}
|
|
.id(url.absoluteString)
|
|
// .contextMenu {
|
|
// Button(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image to clipboard.")) {
|
|
// UIPasteboard.general.string = url.absoluteString
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
.fullScreenCover(isPresented: $open_sheet) {
|
|
ImageView(urls: urls)
|
|
}
|
|
.frame(height: 200)
|
|
.clipped()
|
|
.onTapGesture {
|
|
open_sheet = true
|
|
}
|
|
.tabViewStyle(PageTabViewStyle())
|
|
}
|
|
}
|
|
|
|
struct ImageCarousel_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
ImageCarousel(urls: [URL(string: "https://jb55.com/red-me.jpg")!,URL(string: "https://jb55.com/red-me.jpg")!])
|
|
}
|
|
}
|