version 1.0.0
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import AppKit
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
final class FolderAccessService {
|
||||
private let bookmarkKey = "velody.selected-folder.bookmark"
|
||||
|
||||
func chooseFolder() -> URL? {
|
||||
let panel = NSOpenPanel()
|
||||
panel.canChooseDirectories = true
|
||||
panel.canChooseFiles = false
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.prompt = "Choose Folder"
|
||||
|
||||
guard panel.runModal() == .OK, let url = panel.url else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let bookmark = try url.bookmarkData(
|
||||
options: [.withSecurityScope],
|
||||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil
|
||||
)
|
||||
UserDefaults.standard.set(bookmark, forKey: bookmarkKey)
|
||||
return url
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
func storedFolderURL() -> URL? {
|
||||
guard
|
||||
let bookmark = UserDefaults.standard.data(forKey: bookmarkKey)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var isStale = false
|
||||
return try? URL(
|
||||
resolvingBookmarkData: bookmark,
|
||||
options: [.withSecurityScope],
|
||||
relativeTo: nil,
|
||||
bookmarkDataIsStale: &isStale
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import SwiftUI
|
||||
import VelodyDomain
|
||||
|
||||
struct MacLibraryView: View {
|
||||
@State private var viewModel = MacLibraryViewModel()
|
||||
|
||||
var body: some View {
|
||||
NavigationSplitView {
|
||||
List(viewModel.tracks) { track in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(track.title)
|
||||
.font(.headline)
|
||||
Text(track.artist)
|
||||
.foregroundStyle(.secondary)
|
||||
if let album = track.album {
|
||||
Text(album)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
.navigationTitle("Velody")
|
||||
} detail: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Private Library Foundation")
|
||||
.font(.largeTitle)
|
||||
Text("Selected folder")
|
||||
.font(.headline)
|
||||
Text(viewModel.selectedFolderPath)
|
||||
.textSelection(.enabled)
|
||||
Text("Sync status")
|
||||
.font(.headline)
|
||||
Text(viewModel.syncStatus)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
HStack {
|
||||
Button("Choose Watched Folder") {
|
||||
viewModel.chooseFolder()
|
||||
}
|
||||
Button("Refresh Placeholder Sync") {
|
||||
Task {
|
||||
await viewModel.refreshSyncStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
.task {
|
||||
await viewModel.loadIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
import VelodyDomain
|
||||
import VelodyNetworking
|
||||
import VelodyPersistence
|
||||
import VelodySync
|
||||
import VelodyUtilities
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class MacLibraryViewModel {
|
||||
var tracks: [LibraryTrack] = []
|
||||
var selectedFolderPath = "No folder selected"
|
||||
var syncStatus = "Sync not started"
|
||||
|
||||
private let folderAccessService = FolderAccessService()
|
||||
private let keychainService = MemoryKeychainService()
|
||||
private let store = InMemoryLocalLibraryStore()
|
||||
private let syncCoordinator: PlaceholderSyncCoordinator
|
||||
private var hasLoaded = false
|
||||
|
||||
init() {
|
||||
let environment = ServerEnvironment(
|
||||
baseURL: URL(string: "http://localhost:3000")!,
|
||||
appVersion: "0.1.0"
|
||||
)
|
||||
let apiClient = StubVelodyAPIClient(environment: environment)
|
||||
self.syncCoordinator = PlaceholderSyncCoordinator(
|
||||
apiClient: apiClient,
|
||||
store: store
|
||||
)
|
||||
if let url = folderAccessService.storedFolderURL() {
|
||||
selectedFolderPath = url.path
|
||||
}
|
||||
}
|
||||
|
||||
func loadIfNeeded() async {
|
||||
guard !hasLoaded else { return }
|
||||
hasLoaded = true
|
||||
await refreshSyncStatus()
|
||||
await keychainService.save("placeholder-bootstrap-token", forKey: "bootstrap-token")
|
||||
}
|
||||
|
||||
func refreshSyncStatus() async {
|
||||
do {
|
||||
let result = try await syncCoordinator.performInitialSync()
|
||||
tracks = result.tracks
|
||||
syncStatus = result.statusMessage
|
||||
} catch {
|
||||
syncStatus = "Sync placeholder failed: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
func chooseFolder() {
|
||||
if let url = folderAccessService.chooseFolder() {
|
||||
selectedFolderPath = url.path
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct VelodyMacApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MacLibraryView()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user