first commit
This commit is contained in:
128
ContentView.swift
Normal file
128
ContentView.swift
Normal file
@@ -0,0 +1,128 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject private var vm = DownloadViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
header
|
||||
|
||||
GroupBox("Download") {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
TextField("YouTube video or playlist URL", text: $vm.urlString)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
||||
Button("Paste") {
|
||||
if let s = NSPasteboard.general.string(forType: .string) {
|
||||
vm.urlString = s.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 16) {
|
||||
Toggle("Audio only (MP3)", isOn: $vm.audioOnly)
|
||||
|
||||
Picker("Quality", selection: $vm.quality) {
|
||||
ForEach(VideoQuality.allCases, id: \.self) { q in
|
||||
Text(q.title).tag(q)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Save to:")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text(vm.destinationPathDisplay)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Choose Folder…") { vm.pickFolder() }
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button(vm.isDownloading ? "Downloading…" : "Start") {
|
||||
vm.start()
|
||||
}
|
||||
.disabled(!vm.canStart)
|
||||
|
||||
Button("Cancel") {
|
||||
vm.cancel()
|
||||
}
|
||||
.disabled(!vm.isDownloading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
|
||||
GroupBox("Progress") {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
ProgressView(value: vm.progress)
|
||||
.progressViewStyle(.linear)
|
||||
|
||||
HStack {
|
||||
Text(vm.statusLine)
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(2)
|
||||
|
||||
Spacer()
|
||||
|
||||
if vm.isDownloading {
|
||||
Text(vm.percentText)
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
|
||||
GroupBox("Log") {
|
||||
ScrollView {
|
||||
Text(vm.log)
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.textSelection(.enabled)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.frame(height: 140)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
.padding(18)
|
||||
.alert("Error", isPresented: $vm.showError) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text(vm.errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("YoutubeDumper")
|
||||
.font(.title2.bold())
|
||||
Text("Uses Homebrew yt-dlp. Supports videos, playlists, and audio-only.")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Button("Check yt-dlp") {
|
||||
vm.checkYtDlp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user