- Create new iOS app at apps/ios-github-mobile - Session list view with repository info and status - Chat view with collapsible tool executions (Write, Bash, Read) - Message input with model/repo/branch selection chips - PR creation flow simulation - Dark theme matching Claude Code aesthetic - Mock data for repos, sessions, and tool calls - Modern SwiftUI with @Observable, NavigationStack https://claude.ai/code/session_018qmTB96S8xvNRQFrhcvzJp
109 lines
2.8 KiB
Swift
109 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
struct StatusIndicator: View {
|
|
let status: SessionStatus
|
|
|
|
var body: some View {
|
|
HStack(spacing: AppTheme.spacingXS) {
|
|
Circle()
|
|
.fill(statusColor)
|
|
.frame(width: 8, height: 8)
|
|
|
|
Text(status.rawValue)
|
|
.font(.subheadline)
|
|
.foregroundStyle(AppTheme.secondaryText)
|
|
}
|
|
}
|
|
|
|
private var statusColor: Color {
|
|
switch status {
|
|
case .idle:
|
|
return AppTheme.secondaryText
|
|
case .running:
|
|
return AppTheme.accent
|
|
case .completed:
|
|
return AppTheme.success
|
|
case .error:
|
|
return AppTheme.error
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Loading Indicator
|
|
|
|
struct LoadingIndicator: View {
|
|
@State private var isAnimating = false
|
|
|
|
var body: some View {
|
|
HStack(spacing: AppTheme.spacingXS) {
|
|
ForEach(0..<3) { index in
|
|
Circle()
|
|
.fill(AppTheme.accent)
|
|
.frame(width: 6, height: 6)
|
|
.scaleEffect(isAnimating ? 1.0 : 0.5)
|
|
.animation(
|
|
.easeInOut(duration: 0.6)
|
|
.repeatForever()
|
|
.delay(Double(index) * 0.2),
|
|
value: isAnimating
|
|
)
|
|
}
|
|
}
|
|
.onAppear {
|
|
isAnimating = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Tool Status Badge
|
|
|
|
struct ToolStatusBadge: View {
|
|
let status: ToolCallStatus
|
|
|
|
var body: some View {
|
|
HStack(spacing: AppTheme.spacingXS) {
|
|
switch status {
|
|
case .pending:
|
|
Image(systemName: "clock")
|
|
.foregroundStyle(AppTheme.secondaryText)
|
|
case .running:
|
|
ProgressView()
|
|
.scaleEffect(0.7)
|
|
.tint(AppTheme.accent)
|
|
case .completed:
|
|
Image(systemName: "checkmark.circle.fill")
|
|
.foregroundStyle(AppTheme.success)
|
|
case .failed:
|
|
Image(systemName: "xmark.circle.fill")
|
|
.foregroundStyle(AppTheme.error)
|
|
}
|
|
}
|
|
.font(.caption)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
VStack(spacing: 20) {
|
|
StatusIndicator(status: .idle)
|
|
StatusIndicator(status: .running)
|
|
StatusIndicator(status: .completed)
|
|
StatusIndicator(status: .error)
|
|
|
|
Divider()
|
|
|
|
LoadingIndicator()
|
|
|
|
Divider()
|
|
|
|
HStack(spacing: 20) {
|
|
ToolStatusBadge(status: .pending)
|
|
ToolStatusBadge(status: .running)
|
|
ToolStatusBadge(status: .completed)
|
|
ToolStatusBadge(status: .failed)
|
|
}
|
|
}
|
|
.padding()
|
|
.background(AppTheme.background)
|
|
.preferredColorScheme(.dark)
|
|
}
|