Initial Commit
This commit is contained in:
79
Swipe That Pic/Views/BigPicture.swift
Normal file
79
Swipe That Pic/Views/BigPicture.swift
Normal file
@ -0,0 +1,79 @@
|
||||
//
|
||||
// PhotoTumbnailView.swift
|
||||
// Swipe That Pic
|
||||
//
|
||||
// Created by Amine Bou on 27/07/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Photos
|
||||
import SwiftUI
|
||||
|
||||
struct BigPicture: View {
|
||||
private var image: Image
|
||||
|
||||
@State var scale = 1.0
|
||||
@State var lastScale = 0.0
|
||||
@State var offset: CGSize = .zero
|
||||
@State var lastOffset: CGSize = .zero
|
||||
|
||||
init(image: Image) {
|
||||
self.image = image
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.scaleEffect(scale)
|
||||
.offset(offset)
|
||||
.frame(width: proxy.size.width, height: proxy.size.height)
|
||||
.gesture(
|
||||
MagnificationGesture(minimumScaleDelta: 0)
|
||||
.onChanged({ value in
|
||||
withAnimation(.interactiveSpring()) {
|
||||
scale = handleScaleChange(value)
|
||||
}
|
||||
})
|
||||
.onEnded({ _ in
|
||||
lastScale = scale
|
||||
}).simultaneously(
|
||||
with: DragGesture(minimumDistance: 0)
|
||||
.onChanged({ value in
|
||||
withAnimation(.interactiveSpring()) {
|
||||
offset = handleOffsetChange(value.translation)
|
||||
}
|
||||
})
|
||||
.onEnded({ _ in
|
||||
lastOffset = offset
|
||||
})
|
||||
|
||||
).simultaneously(with: TapGesture(count: 2).onEnded({ Void in
|
||||
scale = 1.0
|
||||
lastScale = 0.0
|
||||
offset = .zero
|
||||
lastOffset = .zero
|
||||
}))
|
||||
)
|
||||
|
||||
}
|
||||
// We'll also make sure that the photo will
|
||||
// be square
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
|
||||
}
|
||||
|
||||
private func handleScaleChange(_ zoom: CGFloat) -> CGFloat {
|
||||
lastScale + zoom - (lastScale == 0 ? 0 : 1)
|
||||
}
|
||||
|
||||
private func handleOffsetChange(_ offset: CGSize) -> CGSize {
|
||||
var newOffset: CGSize = .zero
|
||||
|
||||
newOffset.width = offset.width + lastOffset.width
|
||||
newOffset.height = offset.height + lastOffset.height
|
||||
|
||||
return newOffset
|
||||
}
|
||||
}
|
70
Swipe That Pic/Views/ContentView.swift
Normal file
70
Swipe That Pic/Views/ContentView.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Swipe That Pic
|
||||
//
|
||||
// Created by Amine Bou on 26/07/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import Photos
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@EnvironmentObject var photoLibraryService: PhotosService
|
||||
@Query private var items: [Item]
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
VStack {
|
||||
PhotoThumbnailView(asset: $photoLibraryService.one)
|
||||
Spacer()
|
||||
HStack {
|
||||
Button(role: .destructive, action: {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
PHAssetChangeRequest.deleteAssets([photoLibraryService.one as Any] as NSArray)
|
||||
}, completionHandler: { success, error in
|
||||
if success {
|
||||
photoLibraryService.fetchNotKeptPhotos()
|
||||
|
||||
} else {
|
||||
}})
|
||||
}) {
|
||||
Label("Supprimer", systemImage: "minus.circle.fill")
|
||||
}.buttonStyle(.borderedProminent)
|
||||
|
||||
Spacer()
|
||||
Button(action: {
|
||||
let newItem = Item(localIdentfier: photoLibraryService.one!.localIdentifier)
|
||||
modelContext.insert(newItem)
|
||||
photoLibraryService.fetchNotKeptPhotos()
|
||||
}) {
|
||||
Label("Garder", systemImage: "plus.circle.fill")
|
||||
}.buttonStyle(.borderedProminent)
|
||||
}.disabled(photoLibraryService.one == nil)
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
if (items.count > 0) {
|
||||
ToolbarItem {
|
||||
NavigationLink(destination: IgnoredPictures()) {
|
||||
Label("Handle kept", systemImage: "tray.and.arrow.up.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} detail: {
|
||||
Text("Select an item")
|
||||
}.onAppear(perform: {
|
||||
photoLibraryService.fetchNotKeptPhotos()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
.modelContainer(for: Item.self, inMemory: true)
|
||||
}
|
||||
|
53
Swipe That Pic/Views/IgnoredPictures.swift
Normal file
53
Swipe That Pic/Views/IgnoredPictures.swift
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// Swipe That Pic
|
||||
//
|
||||
// Created by Amine Bou on 26/07/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import Photos
|
||||
|
||||
struct IgnoredPictures: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Query private var items: [Item]
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 200)), GridItem(.adaptive(minimum: 200))], alignment: .leading) {
|
||||
ForEach(items) { item in
|
||||
PhotoThumbnailFromStringView(localIdentifier: item.localIdentfier)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: clearItems) {
|
||||
Label("Clear", systemImage: "bubbles.and.sparkles.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} detail : {
|
||||
Text("Ignored pictures")
|
||||
}
|
||||
}
|
||||
|
||||
private func clearItems() {
|
||||
withAnimation {
|
||||
do {
|
||||
try modelContext.delete(model: Item.self)
|
||||
} catch {
|
||||
print("did not work")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
.modelContainer(for: Item.self, inMemory: true)
|
||||
}
|
||||
|
73
Swipe That Pic/Views/PhotoTumbnailFromStringView.swift
Normal file
73
Swipe That Pic/Views/PhotoTumbnailFromStringView.swift
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// PhotoTumbnailView.swift
|
||||
// Swipe That Pic
|
||||
//
|
||||
// Created by Amine Bou on 27/07/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Photos
|
||||
import SwiftUI
|
||||
|
||||
struct PhotoThumbnailFromStringView: View {
|
||||
@EnvironmentObject var photoLibraryService: PhotosService
|
||||
@State private var image: Image?
|
||||
@State var localIdentifier: String?
|
||||
|
||||
func loadImageAsset(
|
||||
targetSize: CGSize = PHImageManagerMaximumSize
|
||||
) async {
|
||||
if (localIdentifier != nil) {
|
||||
guard let uiImage = try? await photoLibraryService
|
||||
.fetchImage(
|
||||
byLocalIdentifier: localIdentifier!,
|
||||
targetSize: targetSize
|
||||
) else {
|
||||
image = nil
|
||||
return
|
||||
}
|
||||
image = Image(uiImage: uiImage)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Show the image if it's available
|
||||
if let image = image {
|
||||
GeometryReader { proxy in
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(
|
||||
width: proxy.size.width,
|
||||
height: proxy.size.width
|
||||
)
|
||||
.clipped()
|
||||
}
|
||||
// We'll also make sure that the photo will
|
||||
// be square
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
} else {
|
||||
// Otherwise, show a gray rectangle with a
|
||||
// spinning progress view
|
||||
Rectangle()
|
||||
.foregroundColor(.gray)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
// We need to use the task to work on a concurrent request to
|
||||
// load the image from the photo library service, which
|
||||
// is asynchronous work.
|
||||
.task(id: localIdentifier) {
|
||||
await loadImageAsset()
|
||||
}
|
||||
// Finally, when the view disappears, we need to free it
|
||||
// up from the memory
|
||||
.onDisappear {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
}
|
73
Swipe That Pic/Views/PhotoTumbnailView.swift
Normal file
73
Swipe That Pic/Views/PhotoTumbnailView.swift
Normal file
@ -0,0 +1,73 @@
|
||||
//
|
||||
// PhotoTumbnailView.swift
|
||||
// Swipe That Pic
|
||||
//
|
||||
// Created by Amine Bou on 27/07/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Photos
|
||||
import SwiftUI
|
||||
|
||||
struct PhotoThumbnailView: View {
|
||||
@EnvironmentObject var photoLibraryService: PhotosService
|
||||
@State private var image: Image?
|
||||
@Binding var asset: PHAsset?
|
||||
|
||||
func loadImageAsset(
|
||||
targetSize: CGSize = PHImageManagerMaximumSize
|
||||
) async {
|
||||
if (asset != nil) {
|
||||
guard let uiImage = try? await photoLibraryService
|
||||
.fetchImage(
|
||||
byLocalIdentifier: asset!.localIdentifier,
|
||||
targetSize: targetSize
|
||||
) else {
|
||||
image = nil
|
||||
return
|
||||
}
|
||||
image = Image(uiImage: uiImage)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Show the image if it's available
|
||||
if let image = image {
|
||||
GeometryReader { proxy in
|
||||
NavigationLink(destination: BigPicture(image: image)) {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(
|
||||
width: proxy.size.width,
|
||||
height: proxy.size.width
|
||||
)
|
||||
.clipped()
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// Otherwise, show a gray rectangle with a
|
||||
// spinning progress view
|
||||
Rectangle()
|
||||
.foregroundColor(.gray)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
// We need to use the task to work on a concurrent request to
|
||||
// load the image from the photo library service, which
|
||||
// is asynchronous work.
|
||||
.task(id: asset?.localIdentifier) {
|
||||
await loadImageAsset()
|
||||
}
|
||||
// Finally, when the view disappears, we need to free it
|
||||
// up from the memory
|
||||
.onDisappear {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user