|
|
@@ -19,7 +19,6 @@ struct CloudBrowserView: View {
|
|
|
@EnvironmentObject private var theme: AppTheme
|
|
|
@State private var apiClient = ChadMusicAPIClient.shared
|
|
|
@State private var uploadService = UploadService.shared
|
|
|
- @State private var orchestrator = SoulseekOrchestrator.shared
|
|
|
@State private var navStack: [CloudNavDestination] = []
|
|
|
|
|
|
init(initialDestination: LibraryDestination? = nil) {
|
|
|
@@ -57,9 +56,6 @@ struct CloudBrowserView: View {
|
|
|
} else {
|
|
|
CategoryListView(apiClient: apiClient, uploadService: uploadService, navStack: $navStack)
|
|
|
}
|
|
|
-
|
|
|
- // Soulseek status banner — appears at bottom during active pipeline
|
|
|
- SoulseekStatusBanner(orchestrator: orchestrator)
|
|
|
}
|
|
|
.onAppear {
|
|
|
if let dest = initialDestination {
|
|
|
@@ -1071,63 +1067,3 @@ private struct CloudTrackRow: View {
|
|
|
.padding(.vertical, 2)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-// MARK: - Soulseek Status Banner
|
|
|
-
|
|
|
-/// Persistent overlay at the bottom of CloudBrowserView showing Soulseek pipeline status.
|
|
|
-private struct SoulseekStatusBanner: View {
|
|
|
- let orchestrator: SoulseekOrchestrator
|
|
|
-
|
|
|
- var body: some View {
|
|
|
- let state = orchestrator.state
|
|
|
- if state != .idle {
|
|
|
- HStack(spacing: 10) {
|
|
|
- // Progress indicator
|
|
|
- if state.isActive {
|
|
|
- if case .downloading(let progress) = state {
|
|
|
- ProgressView(value: progress)
|
|
|
- .progressViewStyle(.circular)
|
|
|
- .controlSize(.small)
|
|
|
- } else {
|
|
|
- ProgressView()
|
|
|
- .controlSize(.small)
|
|
|
- }
|
|
|
- } else if case .complete = state {
|
|
|
- Image(systemName: "checkmark.circle.fill")
|
|
|
- .foregroundStyle(.green)
|
|
|
- } else if case .failed = state {
|
|
|
- Image(systemName: "exclamationmark.triangle.fill")
|
|
|
- .foregroundStyle(.red)
|
|
|
- }
|
|
|
-
|
|
|
- // Status text
|
|
|
- Text(state.statusText)
|
|
|
- .font(.system(size: 12))
|
|
|
- .foregroundStyle(state.isActive ? .primary : .secondary)
|
|
|
- .lineLimit(1)
|
|
|
-
|
|
|
- Spacer()
|
|
|
-
|
|
|
- // Action button
|
|
|
- if state.isActive {
|
|
|
- Button("Cancel") {
|
|
|
- orchestrator.cancel()
|
|
|
- }
|
|
|
- .buttonStyle(.bordered)
|
|
|
- .controlSize(.small)
|
|
|
- } else {
|
|
|
- Button("Dismiss") {
|
|
|
- orchestrator.dismiss()
|
|
|
- }
|
|
|
- .buttonStyle(.bordered)
|
|
|
- .controlSize(.small)
|
|
|
- }
|
|
|
- }
|
|
|
- .padding(.horizontal, 12)
|
|
|
- .padding(.vertical, 8)
|
|
|
- .background(.ultraThinMaterial)
|
|
|
- .transition(.move(edge: .bottom).combined(with: .opacity))
|
|
|
- .animation(.easeInOut(duration: 0.3), value: state != .idle)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|