mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Restored files from old Mac, force pushing
This commit is contained in:
parent
5dd95228b8
commit
5040381840
126 changed files with 10431 additions and 67 deletions
40
DynamicIsland/components/Shelf/AirDrop.swift
Normal file
40
DynamicIsland/components/Shelf/AirDrop.swift
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// AirDrop.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/7.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AirDrop: NSObject, NSSharingServiceDelegate {
|
||||
let files: [URL]
|
||||
|
||||
init(files: [URL]) {
|
||||
self.files = files
|
||||
super.init()
|
||||
}
|
||||
|
||||
func begin() {
|
||||
do {
|
||||
try sendEx(files)
|
||||
} catch {
|
||||
NSAlert.popError(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendEx(_ files: [URL]) throws {
|
||||
guard let service = NSSharingService(named: .sendViaAirDrop) else {
|
||||
throw NSError(domain: "AirDrop", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: NSLocalizedString("AirDrop service not available", comment: ""),
|
||||
])
|
||||
}
|
||||
guard service.canPerform(withItems: files) else {
|
||||
throw NSError(domain: "AirDrop", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: NSLocalizedString("AirDrop service not available", comment: ""),
|
||||
])
|
||||
}
|
||||
service.delegate = self
|
||||
service.perform(withItems: files)
|
||||
}
|
||||
}
|
||||
70
DynamicIsland/components/Shelf/AirDropView.swift
Normal file
70
DynamicIsland/components/Shelf/AirDropView.swift
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// AirDrop+View.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct AirDropView: View {
|
||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
|
||||
@State var trigger: UUID = .init()
|
||||
@State var targeting = false
|
||||
|
||||
var body: some View {
|
||||
dropArea
|
||||
.onDrop(of: [.data], isTargeted: $vm.dropZoneTargeting) { providers in
|
||||
trigger = .init()
|
||||
vm.dropEvent = true
|
||||
DispatchQueue.global().async { beginDrop(providers) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var dropArea: some View {
|
||||
Rectangle()
|
||||
.fill(.white.opacity(0.1))
|
||||
.opacity(0.5)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.overlay { dropLabel }
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
var dropLabel: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(systemName: "airplayaudio")
|
||||
Text("AirDrop")
|
||||
}
|
||||
.foregroundStyle(.gray)
|
||||
.font(.system(.headline, design: .rounded))
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
trigger = .init()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let picker = NSOpenPanel()
|
||||
picker.allowsMultipleSelection = true
|
||||
picker.canChooseDirectories = true
|
||||
picker.canChooseFiles = true
|
||||
picker.begin { response in
|
||||
if response == .OK {
|
||||
let drop = AirDrop(files: picker.urls)
|
||||
drop.begin()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func beginDrop(_ providers: [NSItemProvider]) {
|
||||
assert(!Thread.isMainThread)
|
||||
guard let urls = providers.interfaceConvert() else { return }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
let drop = AirDrop(files: urls)
|
||||
drop.begin()
|
||||
}
|
||||
}
|
||||
}
|
||||
56
DynamicIsland/components/Shelf/DragDropView.swift
Normal file
56
DynamicIsland/components/Shelf/DragDropView.swift
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// DragDropView.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Richard Kunkli on 2024. 10. 19..
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
class DragDropView: NSView {
|
||||
var onDragEntered: () -> Void = {}
|
||||
var onDragExited: () -> Void = {}
|
||||
var onDrop: () -> Void = {}
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
registerForDraggedTypes([.fileURL])
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
|
||||
onDragEntered()
|
||||
return .copy
|
||||
}
|
||||
|
||||
override func draggingExited(_ sender: NSDraggingInfo?) {
|
||||
onDragExited()
|
||||
}
|
||||
|
||||
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
|
||||
onDrop()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
struct DragDropViewRepresentable: NSViewRepresentable {
|
||||
@Binding var isTargeted: Bool
|
||||
var onDrop: () -> Void
|
||||
|
||||
func makeNSView(context: Context) -> DragDropView {
|
||||
let view = DragDropView()
|
||||
view.onDragEntered = { isTargeted = true }
|
||||
view.onDragExited = { isTargeted = false }
|
||||
view.onDrop = onDrop
|
||||
|
||||
view.autoresizingMask = [.width, .height]
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: DragDropView, context: Context) {}
|
||||
}
|
||||
58
DynamicIsland/components/Shelf/DropItem.swift
Normal file
58
DynamicIsland/components/Shelf/DropItem.swift
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
//
|
||||
// TrayDrop+DropItem.swift
|
||||
// TrayDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import QuickLook
|
||||
|
||||
extension TrayDrop {
|
||||
struct DropItem: Identifiable, Codable, Equatable, Hashable {
|
||||
let id: UUID
|
||||
|
||||
let fileName: String
|
||||
let size: Int
|
||||
|
||||
let copiedDate: Date
|
||||
let workspacePreviewImageData: Data
|
||||
|
||||
init(url: URL) throws {
|
||||
assert(!Thread.isMainThread)
|
||||
|
||||
id = UUID()
|
||||
fileName = url.lastPathComponent
|
||||
|
||||
size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0
|
||||
copiedDate = Date()
|
||||
workspacePreviewImageData = url.snapshotPreview().pngRepresentation
|
||||
|
||||
try FileManager.default.createDirectory(
|
||||
at: storageURL.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.copyItem(at: url, to: storageURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TrayDrop.DropItem {
|
||||
static let mainDir = "CopiedItems"
|
||||
|
||||
var storageURL: URL {
|
||||
documentsDirectory
|
||||
.appendingPathComponent(Self.mainDir)
|
||||
.appendingPathComponent(id.uuidString)
|
||||
.appendingPathComponent(fileName)
|
||||
}
|
||||
|
||||
var workspacePreviewImage: NSImage {
|
||||
.init(data: workspacePreviewImageData) ?? .init()
|
||||
}
|
||||
|
||||
var shouldClean: Bool { // TODO: In the future clean if old
|
||||
return false
|
||||
}
|
||||
}
|
||||
79
DynamicIsland/components/Shelf/DropItemView.swift
Normal file
79
DynamicIsland/components/Shelf/DropItemView.swift
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// TrayDrop+DropItemView.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct DropItemView: View {
|
||||
let item: TrayDrop.DropItem
|
||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||
@StateObject var tvm = TrayDrop.shared
|
||||
|
||||
@State var hover = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack {
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(.clear)
|
||||
.background {
|
||||
Image(nsImage: item.workspacePreviewImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
}
|
||||
|
||||
Text(item.fileName)
|
||||
.multilineTextAlignment(.center)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(hover ? .white : .gray)
|
||||
.lineLimit(2)
|
||||
.allowsTightening(true)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onDrag {
|
||||
handleOnDrag(for: item)
|
||||
}
|
||||
.frame(width: 64, height: 64)
|
||||
|
||||
if hover {
|
||||
Circle()
|
||||
.fill(.white)
|
||||
.overlay(Image(systemName: "xmark").foregroundStyle(.black).font(.system(size: 7)).fontWeight(.semibold))
|
||||
.frame(width: spacing, height: spacing)
|
||||
.opacity(hover ? 1 : 0) // TODO: Use option key pressed to show delete
|
||||
.scaleEffect(coordinator.optionKeyPressed ? 1 : 0.5)
|
||||
.transition(.blurReplace.combined(with: .scale))
|
||||
.offset(x: spacing / 2, y: -spacing / 2)
|
||||
.onTapGesture { tvm.delete(item.id) }
|
||||
.shadow(color: .black, radius: 3)
|
||||
}
|
||||
}
|
||||
.onHover { hovering in
|
||||
withAnimation(.smooth) {
|
||||
if hovering {
|
||||
hover.toggle()
|
||||
} else {
|
||||
hover.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleOnDrag(for item: TrayDrop.DropItem) -> NSItemProvider {
|
||||
guard let itemProvider = NSItemProvider(contentsOf: item.storageURL) else {
|
||||
return NSItemProvider()
|
||||
}
|
||||
|
||||
let nameWithoutExtension = (item.fileName as NSString).deletingPathExtension
|
||||
itemProvider.suggestedName = nameWithoutExtension
|
||||
|
||||
return itemProvider
|
||||
}
|
||||
}
|
||||
61
DynamicIsland/components/Shelf/Ext+FileProvider.swift
Normal file
61
DynamicIsland/components/Shelf/Ext+FileProvider.swift
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Ext+FileProvider.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension NSItemProvider {
|
||||
private func duplicateToOurStorage(_ url: URL?) throws -> URL? {
|
||||
guard let url else { return nil }
|
||||
let temp = temporaryDirectory
|
||||
.appendingPathComponent("TemporaryDrop")
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathComponent(url.lastPathComponent)
|
||||
try? FileManager.default.createDirectory(
|
||||
at: temp.deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true
|
||||
)
|
||||
try FileManager.default.copyItem(at: url, to: temp)
|
||||
return temp
|
||||
}
|
||||
|
||||
func convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop() -> URL? {
|
||||
var url: URL?
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
_ = loadObject(ofClass: URL.self) { item, _ in
|
||||
url = try? self.duplicateToOurStorage(item)
|
||||
sem.signal()
|
||||
}
|
||||
sem.wait()
|
||||
if url == nil {
|
||||
loadInPlaceFileRepresentation(
|
||||
forTypeIdentifier: UTType.data.identifier
|
||||
) { input, _, _ in
|
||||
defer { sem.signal() }
|
||||
url = try? self.duplicateToOurStorage(input)
|
||||
}
|
||||
sem.wait()
|
||||
}
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
extension [NSItemProvider] {
|
||||
func interfaceConvert() -> [URL]? {
|
||||
let urls = compactMap { provider -> URL? in
|
||||
provider.convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop()
|
||||
}
|
||||
guard urls.count == count else {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
NSAlert.popError(NSLocalizedString("One or more files failed to load", comment: ""))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return urls
|
||||
}
|
||||
}
|
||||
36
DynamicIsland/components/Shelf/Ext+NSAlert.swift
Normal file
36
DynamicIsland/components/Shelf/Ext+NSAlert.swift
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// Ext+NSAlert.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/9.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSAlert {
|
||||
static func popError(_ error: String) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("Error", comment: "")
|
||||
alert.alertStyle = .critical
|
||||
alert.informativeText = error
|
||||
alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
|
||||
alert.runModal()
|
||||
}
|
||||
|
||||
static func popRestart(_ error: String, completion: @escaping () -> Void) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = NSLocalizedString("Need Restart", comment: "")
|
||||
alert.alertStyle = .critical
|
||||
alert.informativeText = error
|
||||
alert.addButton(withTitle: NSLocalizedString("Exit", comment: ""))
|
||||
alert.addButton(withTitle: NSLocalizedString("Later", comment: ""))
|
||||
let response = alert.runModal()
|
||||
if response == .alertFirstButtonReturn {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
static func popError(_ error: Error) {
|
||||
popError(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
19
DynamicIsland/components/Shelf/Ext+NSImage.swift
Normal file
19
DynamicIsland/components/Shelf/Ext+NSImage.swift
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Ext+NSImage.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSImage {
|
||||
var pngRepresentation: Data {
|
||||
guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
||||
return .init()
|
||||
}
|
||||
let imageRep = NSBitmapImageRep(cgImage: cgImage)
|
||||
imageRep.size = size
|
||||
return imageRep.representation(using: .png, properties: [:]) ?? .init()
|
||||
}
|
||||
}
|
||||
24
DynamicIsland/components/Shelf/Ext+URL.swift
Normal file
24
DynamicIsland/components/Shelf/Ext+URL.swift
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Ext+URL.swift
|
||||
// NotchDrop
|
||||
//
|
||||
// Created by 秋星桥 on 2024/7/8.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import QuickLook
|
||||
|
||||
extension URL {
|
||||
func snapshotPreview() -> NSImage {
|
||||
if let preview = QLThumbnailImageCreate(
|
||||
kCFAllocatorDefault,
|
||||
self as CFURL,
|
||||
CGSize(width: 128, height: 128),
|
||||
nil
|
||||
)?.takeRetainedValue() {
|
||||
return NSImage(cgImage: preview, size: .zero)
|
||||
}
|
||||
return NSWorkspace.shared.icon(forFile: path)
|
||||
}
|
||||
}
|
||||
77
DynamicIsland/components/Shelf/TrayDrop.swift
Normal file
77
DynamicIsland/components/Shelf/TrayDrop.swift
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import Cocoa
|
||||
import Combine
|
||||
import Foundation
|
||||
import OrderedCollections
|
||||
|
||||
class TrayDrop: ObservableObject {
|
||||
static let shared = TrayDrop()
|
||||
|
||||
@Published var items: OrderedSet<DropItem>
|
||||
var isEmpty: Bool { items.isEmpty }
|
||||
@Published var isLoading: Int = 0
|
||||
|
||||
init(items: OrderedSet<DropItem> = .init(), isLoading: Int = 0) {
|
||||
self.items = items
|
||||
self.isLoading = isLoading
|
||||
}
|
||||
|
||||
func load(_ providers: [NSItemProvider]) {
|
||||
assert(!Thread.isMainThread)
|
||||
DispatchQueue.main.asyncAndWait { isLoading += 1 }
|
||||
|
||||
guard let urls = providers.interfaceConvert() else {
|
||||
DispatchQueue.main.asyncAndWait { isLoading -= 1 }
|
||||
print("Faield to load items")
|
||||
return
|
||||
}
|
||||
let dropItems = urls.map { url in
|
||||
try? DropItem(url: url)
|
||||
}.compactMap { $0 }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
dropItems.forEach { self.items.updateOrInsert($0, at: 0) }
|
||||
self.isLoading -= 1
|
||||
}
|
||||
print("DONE")
|
||||
}
|
||||
|
||||
func cleanExpiredFiles() {
|
||||
var inEdit = items
|
||||
let shouldCleanItems = items.filter(\.shouldClean)
|
||||
for item in shouldCleanItems {
|
||||
inEdit.remove(item)
|
||||
}
|
||||
items = inEdit
|
||||
}
|
||||
|
||||
func delete(_ item: DropItem.ID) {
|
||||
guard let item = items.first(where: { $0.id == item }) else { return }
|
||||
delete(item: item)
|
||||
}
|
||||
|
||||
private func delete(item: DropItem) {
|
||||
var inEdit = items
|
||||
|
||||
var url = item.storageURL
|
||||
try? FileManager.default.removeItem(at: url)
|
||||
|
||||
do {
|
||||
// loops up to the main directory
|
||||
url = url.deletingLastPathComponent()
|
||||
while url.lastPathComponent != DropItem.mainDir, url != documentsDirectory {
|
||||
let contents = try FileManager.default.contentsOfDirectory(atPath: url.path)
|
||||
guard contents.isEmpty else { break }
|
||||
try FileManager.default.removeItem(at: url)
|
||||
url = url.deletingLastPathComponent()
|
||||
}
|
||||
} catch {}
|
||||
|
||||
inEdit.remove(item)
|
||||
items = inEdit
|
||||
}
|
||||
|
||||
func removeAll() {
|
||||
items.forEach { delete(item: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue