feat: refine photo dock design for compact layout

This commit is contained in:
dscyrescotti
2024-07-21 15:47:13 +07:00
parent a8ea08f63d
commit 798beaab85
7 changed files with 106 additions and 42 deletions

View File

@@ -320,7 +320,7 @@ extension GraphicContext {
tree.insert(photo.element, in: photo.photoBox)
let photoFileID = photoFile.objectID
withPersistence(\.backgroundContext) { [weak _photo = photo, weak graphicContext = object] context in
guard let _photo, let photoFile = context.object(with: photoFileID) as? PhotoFileObject else {
guard let _photo, let photoFile = try context.existingObject(with: photoFileID) as? PhotoFileObject else {
return
}
let photo = PhotoObject(\.backgroundContext)

View File

@@ -128,13 +128,13 @@ final class Tool: NSObject, ObservableObject {
}
}
func createFile(_ image: Platform.Image, with canvas: CanvasObject) {
func createFile(_ image: Platform.Image, with canvas: CanvasObject?) {
guard let (resizedImage, dimension) = resizePhoto(of: image) else { return }
guard let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvas.objectID) else { return }
guard let objectID = canvas?.objectID, let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: objectID) else { return }
let _dimension = photoItem.dimension
let graphicContext = canvas.graphicContext
withPersistence(\.viewContext) { [weak graphicContext = graphicContext] context in
let file = PhotoFileObject(\.viewContext)
let graphicContext = canvas?.graphicContext
withPersistenceSync(\.backgroundContext) { [weak graphicContext = graphicContext] context in
let file = PhotoFileObject(\.backgroundContext)
file.imageURL = photoItem.id
file.bookmark = photoItem.bookmark
file.dimension = [_dimension.width, _dimension.height]
@@ -142,7 +142,6 @@ final class Tool: NSObject, ObservableObject {
file.photos = []
file.graphicContext = graphicContext
graphicContext?.files.add(file)
try context.saveIfNeeded()
}
}

View File

@@ -33,9 +33,6 @@ struct ElementToolbar: View {
ZStack(alignment: .bottom) {
if tool.selection == .photo {
PhotoDock(tool: tool, canvas: canvas)
.padding(.bottom, 10)
.frame(maxWidth: .infinity)
.transition(.move(edge: .bottom).combined(with: .blurReplace))
} else {
compactToolbar
}
@@ -164,7 +161,7 @@ struct ElementToolbar: View {
.fill(.regularMaterial)
}
.padding(10)
.frame(maxWidth: .infinity)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.transition(.move(edge: .bottom).combined(with: .blurReplace))
}
}

View File

@@ -75,7 +75,7 @@ struct MemoView: View {
case .pen:
PenDock(tool: tool, canvas: canvas)
case .photo:
PhotoDock(memo: memo, tool: tool, canvas: canvas)
PhotoDock(tool: tool, canvas: canvas)
default:
EmptyView()
}

View File

@@ -13,7 +13,6 @@ struct PhotoDock: View {
@FetchRequest private var fileObjects: FetchedResults<PhotoFileObject>
private let memo: MemoObject
private let size: CGFloat = 40
@ObservedObject private var tool: Tool
@@ -23,12 +22,14 @@ struct PhotoDock: View {
@State private var isCameraAccessDenied: Bool = false
@State private var photosPickerItems: [PhotosPickerItem] = []
init(memo: MemoObject, tool: Tool, canvas: Canvas) {
self.memo = memo
init(tool: Tool, canvas: Canvas) {
self.tool = tool
self.canvas = canvas
let predicate: NSPredicate = NSPredicate(format: "graphicContext = %@", memo.canvas.graphicContext)
var predicate: NSPredicate?
if let canvasObject = canvas.object {
predicate = NSPredicate(format: "graphicContext = %@", canvasObject.graphicContext)
}
let descriptors: [SortDescriptor<PhotoFileObject>] = [SortDescriptor(\.createdAt)]
self._fileObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate)
}
@@ -36,22 +37,12 @@ struct PhotoDock: View {
var body: some View {
Group {
#if os(macOS)
GeometryReader { proxy in
VStack(alignment: .trailing, spacing: 5) {
photoOption
photoItemGrid
.frame(minHeight: proxy.size.height * 0.2, maxHeight: proxy.size.height * 0.4)
}
.fixedSize()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
}
.padding(10)
.transition(.move(edge: .trailing).combined(with: .blurReplace))
photoDock
#else
if horizontalSizeClass == .regular {
photoOption
photoDock
} else {
compactPhotoOption
compactPhotoDock
}
#endif
}
@@ -59,10 +50,13 @@ struct PhotoDock: View {
#if os(iOS)
.fullScreenCover(isPresented: $opensCamera) {
let image: Binding<UIImage?> = Binding {
tool.selectedPhotoFile?.image
.none
} set: { image in
guard let image else { return }
tool.selectPhoto(image, for: canvas.canvasID)
tool.isLoadingPhoto = true
tool.createFile(image, with: canvas.object)
saveFile()
tool.isLoadingPhoto = false
}
CameraView(image: image, canvas: canvas)
.ignoresSafeArea()
@@ -87,6 +81,7 @@ struct PhotoDock: View {
for photoItem in newValue {
await createFile(for: photoItem)
}
saveFile()
photosPickerItems = []
tool.isLoadingPhoto = false
}
@@ -94,6 +89,38 @@ struct PhotoDock: View {
}
}
private var photoDock: some View {
GeometryReader { proxy in
VStack(alignment: .trailing, spacing: 5) {
photoOption
photoItemGrid
.frame(minHeight: proxy.size.height * 0.2, maxHeight: proxy.size.height * 0.4)
}
.fixedSize()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
}
.padding(10)
.transition(.move(edge: .trailing).combined(with: .blurReplace))
}
private var compactPhotoDock: some View {
GeometryReader { proxy in
HStack(spacing: 0) {
compactPhotoItemList
compactPhotoOption
}
.fixedSize(horizontal: false, vertical: true)
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.regularMaterial)
}
.frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom)
.frame(maxWidth: .infinity)
}
.padding(10)
.transition(.move(edge: .bottom).combined(with: .blurReplace))
}
private var photoOption: some View {
HStack(spacing: 0) {
#if os(iOS)
@@ -196,13 +223,6 @@ struct PhotoDock: View {
#endif
}
}
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.regularMaterial)
}
.padding(.bottom, 10)
.frame(maxWidth: .infinity)
.transition(.move(edge: .bottom).combined(with: .blurReplace))
}
@ViewBuilder
@@ -210,7 +230,7 @@ struct PhotoDock: View {
let padding: CGFloat = 5
let size = (self.size * 2 - (5 + padding * 2)) / 2
let columns: [GridItem] = .init(repeating: GridItem(.flexible(), spacing: 5), count: 2)
ScrollView {
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 5) {
ForEach(fileObjects) { file in
Group {
@@ -248,6 +268,44 @@ struct PhotoDock: View {
}
}
@ViewBuilder
private var compactPhotoItemList: some View {
let padding: CGFloat = 5
let size = self.size - padding * 2
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 5) {
ForEach(fileObjects) { file in
Group {
let previewSize = file.previewSize(size)
if let previewImage = file.previewImage {
Image(image: previewImage)
.resizable()
.frame(width: previewSize.width, height: previewSize.height)
.onTapGesture {
if tool.selectedPhotoFile == file {
tool.unselectPhoto()
} else {
tool.selectPhoto(file)
}
}
} else {
Color.gray.opacity(0.5)
}
}
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: 5))
.overlay {
if tool.selectedPhotoFile == file {
RoundedRectangle(cornerRadius: 5)
.stroke(Color.accentColor, lineWidth: 2.5)
}
}
}
}
.padding(padding)
}
}
private func openCamera() {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
@@ -271,7 +329,16 @@ struct PhotoDock: View {
private func createFile(for photoItem: PhotosPickerItem) async {
let data = try? await photoItem.loadTransferable(type: Data.self)
if let data, let image = Platform.Image(data: data) {
tool.createFile(image, with: memo.canvas)
tool.createFile(image, with: canvas.object)
}
}
private func saveFile() {
withPersistenceSync(\.backgroundContext) { context in
try context.saveIfNeeded()
withPersistenceSync(\.viewContext) { context in
try context.saveIfNeeded()
}
}
}
}

View File

@@ -20,7 +20,8 @@ final class Persistence {
}()
lazy var backgroundContext: NSManagedObjectContext = {
let context = persistentContainer.newBackgroundContext()
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = viewContext
context.undoManager = nil
context.automaticallyMergesChangesFromParent = true
return context

View File

@@ -19,7 +19,7 @@ final class PhotoFileObject: NSManagedObject, Identifiable {
@NSManaged var graphicContext: GraphicContextObject?
var previewImage: Platform.Image? {
guard let imageURL else { return nil }
guard let imageURL = bookmark?.getBookmarkURL() else { return nil }
guard let data = try? Data(contentsOf: imageURL, options: []) else { return nil }
return Platform.Image(data: data)
}