mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-01-11 20:00:24 +01:00
feat: refine photo dock design for compact layout
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user