diff --git a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobModels.swift b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobModels.swift index 72f05c487..120126563 100644 --- a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobModels.swift +++ b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobModels.swift @@ -48,15 +48,15 @@ enum JobStatus: String, Codable, CaseIterable { var icon: String { switch self { case .running: - return "⚡️" + return "circle.fill" case .completed: - return "✅" + return "checkmark.circle.fill" case .failed: - return "❌" + return "xmark.circle.fill" case .paused: - return "⏸️" + return "pause.circle.fill" case .queued: - return "⏳" + return "clock.fill" } } } diff --git a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobMonitorView.swift b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobMonitorView.swift index 9e251c297..76ddd330a 100644 --- a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobMonitorView.swift +++ b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobMonitorView.swift @@ -23,18 +23,18 @@ struct JobMonitorView: View { private var headerView: some View { HStack { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: 3) { Text("Spacedrive Jobs") - .font(.title2) - .fontWeight(.semibold) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.primary) HStack(spacing: 6) { Circle() .fill(connectionStatusColor) - .frame(width: 8, height: 8) + .frame(width: 6, height: 6) Text(viewModel.connectionStatus.displayName) - .font(.caption) + .font(.system(size: 11)) .foregroundColor(.secondary) } } @@ -46,14 +46,14 @@ struct JobMonitorView: View { viewModel.reconnect() }) { Image(systemName: "arrow.clockwise") - .font(.system(size: 14, weight: .medium)) + .font(.system(size: 12, weight: .medium)) .foregroundColor(.secondary) } .buttonStyle(PlainButtonStyle()) .help("Reconnect to daemon") } - .padding(.horizontal, 16) - .padding(.vertical, 12) + .padding(.horizontal, 12) + .padding(.vertical, 10) } private var connectionStatusColor: Color { @@ -90,7 +90,7 @@ struct JobMonitorView: View { private var jobListView: some View { ScrollView { - LazyVStack(spacing: 12) { + LazyVStack(spacing: 4) { ForEach(viewModel.jobs) { job in JobRowView(job: job) .transition(.asymmetric( @@ -99,10 +99,10 @@ struct JobMonitorView: View { )) } } - .padding(.horizontal, 16) - .padding(.vertical, 12) + .padding(.horizontal, 12) + .padding(.vertical, 8) } - .animation(.easeInOut(duration: 0.3), value: viewModel.jobs.count) + .animation(.easeInOut(duration: 0.2), value: viewModel.jobs.count) } } diff --git a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobRowView.swift b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobRowView.swift index 6a011944b..d5b840019 100644 --- a/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobRowView.swift +++ b/apps/spacedrive-companion/Sources/SpacedriveCompanion/JobRowView.swift @@ -4,7 +4,7 @@ struct JobRowView: View { let job: JobInfo private var progressPercentage: String { - return String(format: "%.1f%%", job.progress * 100) + return String(format: "%.0f%%", job.progress * 100) } private var timeAgo: String { @@ -18,95 +18,93 @@ struct JobRowView: View { let duration = completedAt.timeIntervalSince(job.startedAt) if duration < 60 { - return String(format: "%.1fs", duration) + return String(format: "%.0fs", duration) } else if duration < 3600 { - return String(format: "%.1fm", duration / 60) + return String(format: "%.0fm", duration / 60) } else { return String(format: "%.1fh", duration / 3600) } } var body: some View { - VStack(alignment: .leading, spacing: 8) { - // Header with job name and status - HStack { - VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 12) { + // Status indicator - simple colored circle + Circle() + .fill(statusColor) + .frame(width: 8, height: 8) + .opacity(job.status == .completed ? 1.0 : 0.8) + + // Main content area + VStack(alignment: .leading, spacing: 3) { + // Top row: Job name and status/progress + HStack { Text(job.name) - .font(.headline) + .font(.system(size: 13, weight: .medium)) .foregroundColor(.primary) + .lineLimit(1) + .truncationMode(.tail) - Text("ID: \(String(job.id.prefix(8)))...") - .font(.caption) - .foregroundColor(.secondary) - } + Spacer() - Spacer() + // Status and progress info + HStack(spacing: 6) { + if job.status == .running { + Text(progressPercentage) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(.secondary) + } else { + Text(job.status.displayName) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(job.status == .failed ? .red : .secondary) + } - HStack(spacing: 4) { - Text(job.status.icon) - .font(.title2) - - Text(job.status.displayName) - .font(.caption) - .fontWeight(.medium) - .foregroundColor(statusColor) - } - } - - // Progress bar (only show for running jobs) - if job.status == .running { - VStack(alignment: .leading, spacing: 4) { - HStack { - Text("Progress") - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - Text(progressPercentage) - .font(.caption) - .fontWeight(.medium) + if let duration = duration { + Text("• \(duration)") + .font(.system(size: 11)) + .foregroundColor(.secondary) + .opacity(0.7) + } else if job.status == .running { + Text("• \(timeAgo)") + .font(.system(size: 11)) + .foregroundColor(.secondary) + .opacity(0.7) + } } - - ProgressView(value: job.progress, total: 1.0) - .progressViewStyle(LinearProgressViewStyle()) - .scaleEffect(x: 1, y: 0.8, anchor: .center) } - } - // Error message (if any) - if let errorMessage = job.errorMessage, !errorMessage.isEmpty { - Text(errorMessage) - .font(.caption) - .foregroundColor(.red) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.red.opacity(0.1)) - .cornerRadius(4) - } + // Progress bar for active jobs + if job.status == .running || job.status == .paused { + ProgressView(value: job.progress, total: 1.0) + .progressViewStyle(LinearProgressViewStyle(tint: statusColor)) + .scaleEffect(x: 1, y: 0.6, anchor: .center) + } else if job.status == .completed { + // Completed indicator line + Rectangle() + .fill(Color.green.opacity(0.3)) + .frame(height: 2) + .cornerRadius(1) + } - // Timestamps - HStack { - Text("Started \(timeAgo)") - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - if let duration = duration { - Text("Duration: \(duration)") - .font(.caption) - .foregroundColor(.secondary) + // Error message (compact display) + if let errorMessage = job.errorMessage, !errorMessage.isEmpty { + Text(errorMessage) + .font(.system(size: 11)) + .foregroundColor(.red) + .lineLimit(1) + .truncationMode(.tail) } } } + .frame(height: 44) // Fixed height for uniform appearance .padding(.horizontal, 12) - .padding(.vertical, 10) - .background(backgroundColorForStatus) - .cornerRadius(8) + .padding(.vertical, 6) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(Color.primary.opacity(0.03)) + ) .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(borderColorForStatus, lineWidth: 1) + RoundedRectangle(cornerRadius: 6) + .stroke(Color.primary.opacity(0.08), lineWidth: 0.5) ) } @@ -124,36 +122,6 @@ struct JobRowView: View { return .gray } } - - private var backgroundColorForStatus: Color { - switch job.status { - case .running: - return Color.blue.opacity(0.05) - case .completed: - return Color.green.opacity(0.05) - case .failed: - return Color.red.opacity(0.05) - case .paused: - return Color.orange.opacity(0.05) - case .queued: - return Color.gray.opacity(0.05) - } - } - - private var borderColorForStatus: Color { - switch job.status { - case .running: - return Color.blue.opacity(0.2) - case .completed: - return Color.green.opacity(0.2) - case .failed: - return Color.red.opacity(0.2) - case .paused: - return Color.orange.opacity(0.2) - case .queued: - return Color.gray.opacity(0.2) - } - } } #Preview {