#if os(iOS) import ActivityKit import SwiftUI import WidgetKit struct DownloadLiveActivityView: View { let context: ActivityViewContext var body: some View { HStack(spacing: 16) { VStack(alignment: .leading, spacing: 4) { Text(context.attributes.accountHandle) .font(.caption2) .foregroundColor(.secondary) switch context.state.status { case .fetchingData: Text("Fetching data...") .font(.caption) .foregroundColor(.primary) case .downloading: Text("Downloading") .font(.caption) .foregroundColor(.primary) case .paused: Text("Paused") .font(.caption) .foregroundColor(.orange) case .completed: Text("Complete") .font(.caption) .foregroundColor(.green) } } Spacer() if context.state.status == .downloading || context.state.status == .paused { VStack(alignment: .trailing, spacing: 4) { Text("\(Int(context.state.progress * 100))%") .font(.system(.title3, design: .rounded)) .fontWeight(.semibold) .foregroundColor(.primary) if let totalBlobs = context.state.totalBlobs { Text("\(context.state.downloadedBlobs)/\(totalBlobs)") .font(.caption2) .foregroundColor(.secondary) } } } else if context.state.status == .completed { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.title2) } } .padding(.horizontal, 20) .padding(.vertical, 12) .activityBackgroundTint(Color.clear) .activitySystemActionForegroundColor(Color.primary) } } struct DownloadLiveActivityExpandedView: View { let context: ActivityViewContext var body: some View { VStack(spacing: 12) { HStack { VStack(alignment: .leading, spacing: 4) { Text("Downloading Backup") .font(.headline) Text(context.attributes.accountHandle) .font(.subheadline) .foregroundColor(.secondary) } Spacer() } if context.state.status == .downloading || context.state.status == .paused { VStack(spacing: 8) { ProgressView(value: context.state.progress) .progressViewStyle(.linear) .tint(context.state.status == .paused ? .orange : .accentColor) HStack { Text(statusText) .font(.caption) .foregroundColor(.secondary) Spacer() Text("\(Int(context.state.progress * 100))%") .font(.caption) .fontWeight(.medium) } if let totalBlobs = context.state.totalBlobs { Text("\(context.state.downloadedBlobs) of \(totalBlobs) blobs") .font(.caption2) .foregroundColor(.secondary) } } } else if context.state.status == .fetchingData { HStack { ProgressView() .progressViewStyle(.circular) .scaleEffect(0.8) Text("Fetching repository data...") .font(.caption) .foregroundColor(.secondary) } } else if context.state.status == .completed { HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.title3) Text("Download complete") .font(.subheadline) .foregroundColor(.green) } } } .padding() .activityBackgroundTint(Color.clear) } private var statusText: String { switch context.state.status { case .downloading: return "Downloading..." case .paused: return "Download paused" default: return "" } } } struct DownloadActivityWidget: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DownloadActivityAttributes.self) { context in DownloadLiveActivityExpandedView(context: context) .background(Color(UIColor.systemBackground)) } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { VStack(alignment: .leading, spacing: 2) { Text(context.attributes.accountHandle) .font(.caption2) .foregroundColor(.secondary) statusLabel(for: context.state.status) } } DynamicIslandExpandedRegion(.trailing) { if context.state.status == .downloading || context.state.status == .paused { VStack(alignment: .trailing, spacing: 2) { Text("\(Int(context.state.progress * 100))%") .font(.system(.title3, design: .rounded)) .fontWeight(.semibold) if let totalBlobs = context.state.totalBlobs { Text("\(context.state.downloadedBlobs)/\(totalBlobs)") .font(.caption2) .foregroundColor(.secondary) } } } else if context.state.status == .completed { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .font(.title2) } } DynamicIslandExpandedRegion(.bottom) { if context.state.status == .downloading || context.state.status == .paused { ProgressView(value: context.state.progress) .progressViewStyle(.linear) .tint(context.state.status == .paused ? .orange : .accentColor) } } } compactLeading: { Image(systemName: iconName(for: context.state.status)) .foregroundColor(iconColor(for: context.state.status)) } compactTrailing: { if context.state.status == .downloading || context.state.status == .paused { Text("\(Int(context.state.progress * 100))%") .font(.caption) .fontWeight(.semibold) } else if context.state.status == .fetchingData { ProgressView() .progressViewStyle(.circular) .scaleEffect(0.6) } } minimal: { Image(systemName: iconName(for: context.state.status)) .foregroundColor(iconColor(for: context.state.status)) } .widgetURL(URL(string: "atprotobackup://download/\(context.attributes.accountDid)")) .keylineTint(iconColor(for: context.state.status)) } } private func iconName(for status: DownloadActivityAttributes.ContentState.DownloadStatus) -> String { switch status { case .fetchingData, .downloading: return "arrow.down.circle.fill" case .paused: return "pause.circle.fill" case .completed: return "checkmark.circle.fill" } } private func iconColor(for status: DownloadActivityAttributes.ContentState.DownloadStatus) -> Color { switch status { case .fetchingData, .downloading: return .accentColor case .paused: return .orange case .completed: return .green } } @ViewBuilder private func statusLabel(for status: DownloadActivityAttributes.ContentState.DownloadStatus) -> some View { switch status { case .fetchingData: Text("Fetching...") .font(.caption) case .downloading: Text("Downloading") .font(.caption) case .paused: Text("Paused") .font(.caption) .foregroundColor(.orange) case .completed: Text("Complete") .font(.caption) .foregroundColor(.green) } } } #endif