From 942b3399680f5c335b909565ad7ab1e0fd6dfdf5 Mon Sep 17 00:00:00 2001 From: Samuel Zielke Date: Mon, 14 Sep 2026 01:31:36 +0200 Subject: [PATCH] =?UTF-8?q?SwiftUI-Oberfl=C3=A4che:=20MVP=20mit=20Drag=20&?= =?UTF-8?q?=20Drop,=20Einstellungen,=20Dateiliste?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schritt 4 der Umsetzungsreihenfolge. - AppSettings: Quell-/Zielordner über Security-Scoped Bookmarks, überlebt einen Neustart. - DropZoneView: Drag & Drop plus Dateiauswahl-Dialog, öffnet im konfigurierten Quellordner. - ConversionCoordinator treibt JobQueue an, sortiert neue Dateien nach zuletzt geändert, hält Fortschritt/Status pro Datei nach. - FileListView zeigt Fortschritt und hebt Problemfälle (übersprungen/ abgelehnt/fehlgeschlagen) sichtbar hervor — keine stillen Abbrüche. - CoreErrors+LocalizedError: deutsche Klartexte für die Core-Fehler- Enums leben bewusst im App-Target, nicht im UI-freien Core. - AppIcon aus assets/icon.svg gebaut (alle 10 macOS-Icongrößen via rsvg-convert, ImageMagicks eingebauter SVG-Renderer hat den Rahmen-Stroke verschluckt). Bewusst ausgeklammert (spätere Schritte): Review-Ansicht, Menu-Bar- Betrieb, Signierung/Notarisierung/Sparkle, Overwrite-Rückfrage-Dialog. --- .../AppIcon.appiconset/Contents.json | 68 ++++++++++++ .../AppIcon.appiconset/icon_128x128.png | Bin 0 -> 2342 bytes .../AppIcon.appiconset/icon_128x128@2x.png | Bin 0 -> 4881 bytes .../AppIcon.appiconset/icon_16x16.png | Bin 0 -> 372 bytes .../AppIcon.appiconset/icon_16x16@2x.png | Bin 0 -> 602 bytes .../AppIcon.appiconset/icon_256x256.png | Bin 0 -> 4881 bytes .../AppIcon.appiconset/icon_256x256@2x.png | Bin 0 -> 10351 bytes .../AppIcon.appiconset/icon_32x32.png | Bin 0 -> 602 bytes .../AppIcon.appiconset/icon_32x32@2x.png | Bin 0 -> 1204 bytes .../AppIcon.appiconset/icon_512x512.png | Bin 0 -> 10351 bytes .../AppIcon.appiconset/icon_512x512@2x.png | Bin 0 -> 23349 bytes Sources/DropTwo/Assets.xcassets/Contents.json | 6 ++ Sources/DropTwo/DropTwoApp.swift | 13 +++ Sources/DropTwo/Info.plist | 30 ++++++ .../DropTwo/Model/ConversionCoordinator.swift | 102 ++++++++++++++++++ Sources/DropTwo/Model/DropItem.swift | 39 +++++++ Sources/DropTwo/Settings/AppSettings.swift | 50 +++++++++ .../Support/CoreErrors+LocalizedError.swift | 53 +++++++++ Sources/DropTwo/Views/ContentView.swift | 48 +++++++++ Sources/DropTwo/Views/DropZoneView.swift | 56 ++++++++++ Sources/DropTwo/Views/FileListView.swift | 62 +++++++++++ Sources/DropTwo/Views/SettingsView.swift | 59 ++++++++++ 22 files changed, 586 insertions(+) create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_16x16.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_32x32.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_512x512.png create mode 100644 Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png create mode 100644 Sources/DropTwo/Assets.xcassets/Contents.json create mode 100644 Sources/DropTwo/DropTwoApp.swift create mode 100644 Sources/DropTwo/Info.plist create mode 100644 Sources/DropTwo/Model/ConversionCoordinator.swift create mode 100644 Sources/DropTwo/Model/DropItem.swift create mode 100644 Sources/DropTwo/Settings/AppSettings.swift create mode 100644 Sources/DropTwo/Support/CoreErrors+LocalizedError.swift create mode 100644 Sources/DropTwo/Views/ContentView.swift create mode 100644 Sources/DropTwo/Views/DropZoneView.swift create mode 100644 Sources/DropTwo/Views/FileListView.swift create mode 100644 Sources/DropTwo/Views/SettingsView.swift diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..64dc11e --- /dev/null +++ b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "icon_16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "icon_16x16@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "icon_32x32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "icon_32x32@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "icon_128x128@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "icon_256x256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "icon_256x256@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "icon_512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "icon_512x512@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..00031a38fcabac8a3e2513b2b674d28a695a80b2 GIT binary patch literal 2342 zcmd5;`#;l-7yrz4F7p@?u~rjPJTAFqxvUy;eOxvr_vDr|Ql=-%imk?)w36kLEON~y zv7(F1jO;O&uS-bC)CHz6a<=u+#!NTgi@TFp`&gZ_= zaQgry9M-0;RpIneaTM4-{JYYF$~5v#zF`CWYHXU)$4bT#I?)gQWs&9uqLQ`ETKY>lJsAO8Jcm z_W2@zEiV&=3#V81cz`6n%q<{D*IKF`M>#8vAl12RL2r+)E`I93d-dU&(Eu73_%TW5 z5NJ>ZjKwHWSG|F_YS`jc$wFCj>9Atp*y>NIT6sZXd+Y zs!!@yT~e@DCUc>^TSD>T{>G~0hSNy7fVn@H3MDFUIVCl-I4X zfuDwK(&cABdNA>pYPn8|iFA9F!Idd21_71d6}hXfFhjNfx$g-A!Aza&1`9pCv9Z)z z=G|qxuM8*%&(Y&*eijN*78^^cm1jB9SS+@X&UhGSK~GOtw%Gc8th%PAh|VB`BWoi2 zL5bojY^LaBZ_9`AU+a~G`Mnk^1FT{etzFQhANXr7G|fp1XV(^>vNRE9{CAfma05Mp z1c<&@3NQ053BXV--UAl&Nm<{Ic5Oa8*=(3+{s*i3PMpPN$4D(Om}}}-;GAh1*q%OL zM0YdJG{f7TIN`NHZJr$4MmdW=f zkP6fQE`2`ed$*?Q*hBpU+C=;Gdy6UQGH;cUJzFE6>w<$8Ms2*1TjOoDmBe*cFaZX0 ziHR|ytFOU4%l(L<7g}iR6;XoY)wQ)c?IvQ-E;9%qgG1V%^7hU&sR`x#@w4KAlUj^{ z7oq-ragnp};^7o7PevATsKlH7%!zr=q_4L(e_z|GPko$WgsuWr`{ZGZkVioVV~)j% zPk*je?bh}vx_|S)7(*xKd4GWdG(=5hl|%3BIgse(xoNCydRyK-`?z+Hs><41bTq+e zd_2NMI@kOl>FZPA5kn;G;($Nxe&%bkN2S0!W`V;eH~8e#CP59Y*6>Pq@Pypi8D#O; zI7d-DZlvUwVA9&B2xaS6tP!Z2eJaskU05=ueRZK(Ghq7l%&x{uwG#sIVvU{Z3{Ok9 zD@lf#b7{OBZ4!TynxR^M@1xp5o3@i6W2AUzmJ&L~9r(xo|0}tg2M}u+#Tqo`sl(Uu z02q9^kZus-*ET~HssAxmR}mE5Yq%J~9gP$85^&ts6yzp;moAmpvUfs<6+&?te$n0d$t5l3WXh11M&1 zE0WzSAP5ZZnfwO~1b9>I0S^8fg79(AM)+FfSWfzTc@=Gw0gJ;0-}(HO=zZ*-z;H}7 zk$Ds+X-`<5N~mg?nwnBNp!!%S6qZ){lsju8=8SDI5m|XO8Z!Ir=TF@GJ&AM)a7UX`TM%{ z_A*hHr{R=fFV)nS)z!IA0$F+I%nje0h;8rv6(FPJNGHrOJzktkhmnWpnVb($O$Dh^ z-#WS3+1X;&)1&i8R(>}y(HF6qnmyO%H$>}VH)MIGs1IyIqu9AmxQj?QCQJ-~16WT_ zKhO`2d@3ajZ=KT7;Zo|e1`$g<)*CI|Jv5Ypd>k*0uU|m+x>c!2SVB{|*S@^~^%xHU z!bcxmUpZwMfQD)!Igd;t3?llRoN_F@t~`YBs+ALN7dl8CZO_=d*lRSrN zra>bvAl>%CR@!1ou{J8?{B2I2Gx`h&xclFWcuLWcJ$`d+;H8R>n3KO;NXTHB79&dL z#e!_NC&_3|a=ZE;03KucAW9ti=xk*s*QkYZ5sNd4rtlx0mo#X2d9D%ixWK*5hDaoC zIly_ZH44k;TY?c5bAEaP3512!(zs~iPv8V+Y(eG*+9nCy?f`dYkK~RGyz5v-cZdLJ z1T;ZMzA(csY2ERmO7bC^rC`5vL^Cf`n=p)z38Vjrxtx-{F8oJ4HKDM^B_Lbfeikq2z!aBs~n-) zbDeAzsIBB@*Srqm>Fg-%mr&DKxokoEsRnVCbK6q0Yz4P&qD`>yddXyP*|t)Y-1xr! z$$w!HG1DHp((|%&Y4&
k4z5`>J;a)Xgkd6PM+|bOQ=LVlqTW z_5#WzYJOZtT`L4IB~A0bAx8BOsCM2nb?!kSabio7Gl187g|JiIetZCX8yC#;;{iAS E1$Fj60{{R3 literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..19c79b3f7cd02f71d5dc2590864b381b85139ec1 GIT binary patch literal 4881 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 literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..61095c3bd9dc96137332f2ddaf3e96cbfc1888df GIT binary patch literal 372 zcmV-)0gL{LP)Il@I68i zZd7$A2!bPV9H#_w2*?kGaVY49$+4b+eA@hs4dH)THT}BZ1y-28umxq zJrs*8_`Xl8b#E9H3Nw_;Wy3&~MLRyTh_Jaj%V=uaFnH-&L^3dC#!eb;ZE1>$yt&KA zr;wA|L2W9@;5Ya;fW#}DUOg~ocMb0OH3j4OM1zpNET}36=dB?@nvd6q_WNUeMY&!- z!}C1a?Y5!JX0z04`vxb%^Elf^G#taQp`6xL#;b Si8wg`0000wyoa`9qO6q+a`Cd5m7mG&k%4rzi2CNFs7lxMnd9-ilS z?g#h0ir*B8L?-w5TeqNd1ylx@#2eY5x4?Mn_xm0;n@zj7pn4akY0iRr1f1}w^l$nG zdeL#5%|5_E`7^*b;_{wapywUO*;L)z?tWzSJO5L)K)|2&b)Wg*^6BX_6bc36@zbK@ zO63dre4g#?@*vx(y*>A)GR^QC&=&?d*ViA@Xf)W^crF5%o0}yXjk2(?G{mW2`#@)g z1*TGIip2+%N+l6Mz22Z$yeDv~i~$@KrH)3&#BaU-u)$2?xY#R|7~tCF80QjaMc#Lx zcf|l=)KEaat0Tcf-UEzEV}LQh7+?%A1`q@6{%naucYesd;=kdOS2gagy%2emGv~Q< z)nkBmNAtBN`^_d&S{jQ1*uzX(Ym`#MY<(%E&{_o1^mQi|b#ai(vMiRDZ;(vN4kX!Z z7R#~(plm7A4Cg@K9%PKgV&rlwOijf_0OfLp)zv#xtJPr$E4{(w18`E5?wzWkP%v$D z2SvRI$iJO`mkWBSUa#*M0Np`R&jYppP+mQl3ts~CPmiwaehP;t*4pik4LS^J0tk9k o`dumBg1R3H1+Uw-{l2&G3qmTW-iz^ta{vGU07*qoM6N<$f~Tb!+yDRo literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..19c79b3f7cd02f71d5dc2590864b381b85139ec1 GIT binary patch literal 4881 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 literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..09c83883fa001d43f9ad8f970f01bd9f65f0c7cb GIT binary patch literal 10351 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 literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/Sources/DropTwo/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ba35a7513981eadd6be3fcf11de528d0ee1e0e GIT binary patch literal 1204 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 literal 0 HcmV?d00001 diff --git a/Sources/DropTwo/Assets.xcassets/Contents.json b/Sources/DropTwo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Sources/DropTwo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/DropTwo/DropTwoApp.swift b/Sources/DropTwo/DropTwoApp.swift new file mode 100644 index 0000000..156fe9c --- /dev/null +++ b/Sources/DropTwo/DropTwoApp.swift @@ -0,0 +1,13 @@ +import SwiftUI + +@main +struct DropTwoApp: App { + @StateObject private var settings = AppSettings() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(settings) + } + } +} diff --git a/Sources/DropTwo/Info.plist b/Sources/DropTwo/Info.plist new file mode 100644 index 0000000..5370cd8 --- /dev/null +++ b/Sources/DropTwo/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + drop.two + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + drop.two + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 13.0 + LSUIElement + + NSHumanReadableCopyright + (c) Samuel Zielke + + diff --git a/Sources/DropTwo/Model/ConversionCoordinator.swift b/Sources/DropTwo/Model/ConversionCoordinator.swift new file mode 100644 index 0000000..18668c4 --- /dev/null +++ b/Sources/DropTwo/Model/ConversionCoordinator.swift @@ -0,0 +1,102 @@ +import DropTwoCore +import Foundation + +/// Treibt die Verarbeitung der Dateiliste über `JobQueue` an und hält den +/// UI-Status pro Datei nach. Kennt keine Views — nur `@Published`-Zustand, +/// den SwiftUI beobachtet. +@MainActor +final class ConversionCoordinator: ObservableObject { + @Published private(set) var items: [DropItem] = [] + @Published private(set) var isRunning = false + + private var runningTask: Task? + + var overallProgress: Double { + guard !items.isEmpty else { return 0 } + let sum = items.reduce(0.0) { partial, item in + switch item.status { + case .waiting: + return partial + case .running(let progress): + return partial + progress + case .skipped, .rejected, .succeeded, .failed: + return partial + 1 + } + } + return sum / Double(items.count) + } + + var problemItems: [DropItem] { + items.filter { $0.status.needsAttention } + } + + /// README: "Dateiauswahl vorsortiert nach zuletzt geändert" — neueste zuerst. + func addFiles(_ urls: [URL]) { + let existing = Set(items.map(\.sourceURL)) + let newURLs = urls + .filter { !existing.contains($0) } + .sorted { modificationDate(for: $0) > modificationDate(for: $1) } + items.append(contentsOf: newURLs.map(DropItem.init)) + } + + func removeAll() { + guard !isRunning else { return } + items.removeAll() + } + + func start(targetFolder: URL) { + guard !isRunning, !items.isEmpty else { return } + isRunning = true + + for index in items.indices { + items[index].status = .waiting + } + let jobs = items.map { JobQueue.Job(id: $0.id, sourceURL: $0.sourceURL) } + + runningTask = Task { + let stream = JobQueue.run( + jobs: jobs, + outputRootDirectory: targetFolder, + progress: { [weak self] job, value in + Task { @MainActor in + self?.updateProgress(jobID: job.id, value: value) + } + } + ) + for await result in stream { + apply(result) + } + isRunning = false + } + } + + private func updateProgress(jobID: UUID, value: Double) { + guard let index = items.firstIndex(where: { $0.id == jobID }) else { return } + items[index].status = .running(progress: value) + } + + private func apply(_ result: JobQueue.JobResult) { + guard let index = items.firstIndex(where: { $0.id == result.job.id }) else { return } + switch result.outcome { + case .skipped(_, let reason): + items[index].status = .skipped(reason) + case .rejected(_, let reason): + items[index].status = .rejected(reason) + case .succeeded(let success): + items[index].status = .succeeded(croppedDestination: success.croppedDestination) + case .failed(_, let error): + items[index].status = .failed(message: Self.message(for: error)) + } + } + + private static func message(for error: Error) -> String { + if let localizedError = error as? LocalizedError, let description = localizedError.errorDescription { + return description + } + return error.localizedDescription + } + + private func modificationDate(for url: URL) -> Date { + (try? url.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? .distantPast + } +} diff --git a/Sources/DropTwo/Model/DropItem.swift b/Sources/DropTwo/Model/DropItem.swift new file mode 100644 index 0000000..6411294 --- /dev/null +++ b/Sources/DropTwo/Model/DropItem.swift @@ -0,0 +1,39 @@ +import DropTwoCore +import Foundation + +/// Ein Element in der Dateiliste der Oberfläche, mit seinem aktuellen +/// Verarbeitungsstatus. Rein UI-seitig — die eigentliche Logik steckt in +/// `ConversionPipeline`/`JobQueue` in DropTwoCore. +struct DropItem: Identifiable { + let id: UUID + let sourceURL: URL + var status: Status + + enum Status { + case waiting + case running(progress: Double) + case skipped(SkipReason) + case rejected(RejectionReason) + case succeeded(croppedDestination: URL) + case failed(message: String) + + /// Fehlerliste statt stiller Abbrüche: alles außer "läuft"/"fertig" + /// braucht sichtbare Aufmerksamkeit. + var needsAttention: Bool { + switch self { + case .skipped, .rejected, .failed: + return true + case .waiting, .running, .succeeded: + return false + } + } + } + + init(sourceURL: URL) { + self.id = UUID() + self.sourceURL = sourceURL + self.status = .waiting + } + + var fileName: String { sourceURL.lastPathComponent } +} diff --git a/Sources/DropTwo/Settings/AppSettings.swift b/Sources/DropTwo/Settings/AppSettings.swift new file mode 100644 index 0000000..21ea182 --- /dev/null +++ b/Sources/DropTwo/Settings/AppSettings.swift @@ -0,0 +1,50 @@ +import Foundation + +/// Quell- und Zielordner, persistiert über Security-Scoped Bookmarks statt +/// roher Pfade — überlebt damit auch einen späteren Wechsel in den App +/// Sandbox (README: "@AppStorage + Security-Scoped Bookmarks"). +@MainActor +final class AppSettings: ObservableObject { + @Published private(set) var sourceFolder: URL? + @Published private(set) var targetFolder: URL? + + private enum DefaultsKey { + static let sourceBookmark = "dev.zielke.droptwo.sourceFolderBookmark" + static let targetBookmark = "dev.zielke.droptwo.targetFolderBookmark" + } + + init() { + sourceFolder = Self.resolveBookmark(forKey: DefaultsKey.sourceBookmark) + targetFolder = Self.resolveBookmark(forKey: DefaultsKey.targetBookmark) + } + + func setSourceFolder(_ url: URL) { + sourceFolder = url + Self.storeBookmark(for: url, key: DefaultsKey.sourceBookmark) + } + + func setTargetFolder(_ url: URL) { + targetFolder = url + Self.storeBookmark(for: url, key: DefaultsKey.targetBookmark) + } + + private static func storeBookmark(for url: URL, key: String) { + do { + let data = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) + UserDefaults.standard.set(data, forKey: key) + } catch { + // Best effort: der Ordner bleibt für die laufende Sitzung trotzdem + // gesetzt, nur der Neustart-Fall geht dann verloren. + } + } + + private static func resolveBookmark(forKey key: String) -> URL? { + guard let data = UserDefaults.standard.data(forKey: key) else { return nil } + var isStale = false + guard let url = try? URL(resolvingBookmarkData: data, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) else { + return nil + } + _ = url.startAccessingSecurityScopedResource() + return url + } +} diff --git a/Sources/DropTwo/Support/CoreErrors+LocalizedError.swift b/Sources/DropTwo/Support/CoreErrors+LocalizedError.swift new file mode 100644 index 0000000..a91d26a --- /dev/null +++ b/Sources/DropTwo/Support/CoreErrors+LocalizedError.swift @@ -0,0 +1,53 @@ +import DropTwoCore +import Foundation + +// DropTwoCore hält seine Enums bewusst textfrei (siehe FileNameParser). +// Die deutschen Klartexte für die Oberfläche leben hier — das CLI-Target +// formuliert für dieselben Fälle knapper/technischer in seinen eigenen +// `print`-Aufrufen. + +extension SkipReason: LocalizedError { + public var errorDescription: String? { + switch self { + case .alreadyTargetFormat: + return String(localized: "Ist bereits im 4:5-Format.") + } + } +} + +extension RejectionReason: LocalizedError { + public var errorDescription: String? { + switch self { + case .finderDuplicate: + return String(localized: "Sieht nach einem Finder-Duplikat aus (z. B. „Name (1)“) — bitte die richtige Originaldatei wählen.") + case .formatSegmentMissing: + return String(localized: "Kein 9x16- oder 4x5-Format-Segment im Dateinamen gefunden.") + } + } +} + +extension VideoInspectorError: LocalizedError { + public var errorDescription: String? { + switch self { + case .noVideoTrack: + return String(localized: "Die Datei enthält keine Videospur.") + case .assetUnreadable: + return String(localized: "Die Datei konnte nicht gelesen werden.") + } + } +} + +extension CropEngineError: LocalizedError { + public var errorDescription: String? { + switch self { + case .sourceNotNineBySixteen(let actualSize): + return String(localized: "Kein 9:16-Seitenverhältnis (\(Int(actualSize.width))×\(Int(actualSize.height))).") + case .destinationAlreadyExists: + return String(localized: "Die Zieldatei existiert bereits.") + case .unsupportedContainer: + return String(localized: "Nicht unterstütztes Zielformat — es wird immer MP4 erzeugt.") + case .readerSetupFailed, .writerSetupFailed, .processingFailed: + return String(localized: "Die Datei konnte nicht verarbeitet werden.") + } + } +} diff --git a/Sources/DropTwo/Views/ContentView.swift b/Sources/DropTwo/Views/ContentView.swift new file mode 100644 index 0000000..82e278d --- /dev/null +++ b/Sources/DropTwo/Views/ContentView.swift @@ -0,0 +1,48 @@ +import SwiftUI + +struct ContentView: View { + @EnvironmentObject private var settings: AppSettings + @StateObject private var coordinator = ConversionCoordinator() + @State private var showSettings = false + + var body: some View { + VStack(spacing: 16) { + DropZoneView() + + if !coordinator.items.isEmpty { + FileListView() + + if !coordinator.problemItems.isEmpty { + Text("\(coordinator.problemItems.count) Datei(en) brauchen Aufmerksamkeit.") + .font(.footnote) + .foregroundStyle(.orange) + } + + HStack { + ProgressView(value: coordinator.overallProgress) + Button("Leeren") { coordinator.removeAll() } + .disabled(coordinator.isRunning) + } + } + + Divider() + + HStack { + Button("Einstellungen…") { showSettings = true } + Spacer() + Button("Start") { + guard let target = settings.targetFolder else { return } + coordinator.start(targetFolder: target) + } + .keyboardShortcut(.defaultAction) + .disabled(coordinator.items.isEmpty || coordinator.isRunning || settings.targetFolder == nil) + } + } + .padding(20) + .frame(minWidth: 520, minHeight: 460) + .environmentObject(coordinator) + .sheet(isPresented: $showSettings) { + SettingsView() + } + } +} diff --git a/Sources/DropTwo/Views/DropZoneView.swift b/Sources/DropTwo/Views/DropZoneView.swift new file mode 100644 index 0000000..2f7e122 --- /dev/null +++ b/Sources/DropTwo/Views/DropZoneView.swift @@ -0,0 +1,56 @@ +import SwiftUI +import UniformTypeIdentifiers + +struct DropZoneView: View { + @EnvironmentObject private var settings: AppSettings + @EnvironmentObject private var coordinator: ConversionCoordinator + @State private var isTargeted = false + + var body: some View { + VStack(spacing: 12) { + Image(systemName: "arrow.down.doc") + .font(.system(size: 36)) + .foregroundStyle(.secondary) + Text("9:16-Dateien hierher ziehen") + .font(.headline) + Button("Datei(en) wählen…", action: chooseFiles) + } + .frame(maxWidth: .infinity, minHeight: 140) + .background( + RoundedRectangle(cornerRadius: 12) + .strokeBorder(style: StrokeStyle(lineWidth: 2, dash: [6])) + .foregroundStyle(isTargeted ? Color.accentColor : Color.secondary.opacity(0.35)) + ) + .onDrop(of: [.fileURL], isTargeted: $isTargeted, perform: handleDrop) + } + + private func chooseFiles() { + let panel = NSOpenPanel() + panel.allowsMultipleSelection = true + panel.canChooseDirectories = false + panel.canChooseFiles = true + panel.allowedContentTypes = [.movie, .quickTimeMovie, .mpeg4Movie] + if let sourceFolder = settings.sourceFolder { + panel.directoryURL = sourceFolder + } + guard panel.runModal() == .OK else { return } + coordinator.addFiles(panel.urls) + } + + private func handleDrop(providers: [NSItemProvider]) -> Bool { + guard !providers.isEmpty else { return false } + let group = DispatchGroup() + var urls: [URL] = [] + for provider in providers { + group.enter() + _ = provider.loadObject(ofClass: URL.self) { url, _ in + if let url { urls.append(url) } + group.leave() + } + } + group.notify(queue: .main) { + coordinator.addFiles(urls) + } + return true + } +} diff --git a/Sources/DropTwo/Views/FileListView.swift b/Sources/DropTwo/Views/FileListView.swift new file mode 100644 index 0000000..652ee42 --- /dev/null +++ b/Sources/DropTwo/Views/FileListView.swift @@ -0,0 +1,62 @@ +import SwiftUI + +struct FileListView: View { + @EnvironmentObject private var coordinator: ConversionCoordinator + + var body: some View { + List(coordinator.items) { item in + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(item.fileName) + .lineLimit(1) + .truncationMode(.middle) + statusText(for: item.status) + .font(.caption) + .foregroundStyle(item.status.needsAttention ? Color.orange : .secondary) + } + Spacer() + statusIndicator(for: item.status) + } + .padding(.vertical, 2) + } + } + + @ViewBuilder + private func statusText(for status: DropItem.Status) -> some View { + switch status { + case .waiting: + Text("Wartet …") + case .running(let progress): + Text("\(Int(progress * 100)) %") + case .skipped(let reason): + Text(reason.errorDescription ?? "Übersprungen") + case .rejected(let reason): + Text(reason.errorDescription ?? "Abgelehnt") + case .succeeded(let destination): + Text("Fertig — \(destination.lastPathComponent)") + case .failed(let message): + Text(message) + } + } + + @ViewBuilder + private func statusIndicator(for status: DropItem.Status) -> some View { + switch status { + case .waiting: + Image(systemName: "clock") + .foregroundStyle(.secondary) + case .running(let progress): + ProgressView(value: progress) + .frame(width: 60) + case .skipped: + Image(systemName: "arrow.uturn.forward.circle") + .foregroundStyle(.secondary) + case .rejected, .failed: + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + case .succeeded: + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(.green) + } + } +} diff --git a/Sources/DropTwo/Views/SettingsView.swift b/Sources/DropTwo/Views/SettingsView.swift new file mode 100644 index 0000000..905200d --- /dev/null +++ b/Sources/DropTwo/Views/SettingsView.swift @@ -0,0 +1,59 @@ +import SwiftUI + +struct SettingsView: View { + @EnvironmentObject private var settings: AppSettings + @Environment(\.dismiss) private var dismiss + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("Einstellungen") + .font(.title2.bold()) + + VStack(alignment: .leading, spacing: 6) { + Text("Quellordner").font(.headline) + Text("Der Dateiauswahl-Dialog öffnet sich hier.") + .font(.caption) + .foregroundStyle(.secondary) + folderRow(current: settings.sourceFolder) { settings.setSourceFolder($0) } + } + + VStack(alignment: .leading, spacing: 6) { + Text("Zielordner").font(.headline) + Text("Hier entsteht pro Projekt ein Unterordner mit Original und 4:5-Fassung.") + .font(.caption) + .foregroundStyle(.secondary) + folderRow(current: settings.targetFolder) { settings.setTargetFolder($0) } + } + + Spacer() + + HStack { + Spacer() + Button("Fertig") { dismiss() } + .keyboardShortcut(.defaultAction) + } + } + .padding(20) + .frame(minWidth: 440, minHeight: 280) + } + + @ViewBuilder + private func folderRow(current: URL?, onChoose: @escaping (URL) -> Void) -> some View { + HStack { + Text(current?.path ?? "Nicht gesetzt") + .lineLimit(1) + .truncationMode(.middle) + .foregroundStyle(current == nil ? .secondary : .primary) + Spacer() + Button("Wählen…") { + let panel = NSOpenPanel() + panel.canChooseDirectories = true + panel.canChooseFiles = false + panel.allowsMultipleSelection = false + if let current { panel.directoryURL = current } + guard panel.runModal() == .OK, let url = panel.url else { return } + onChoose(url) + } + } + } +}