Skip to content
RPRichard PruittTech & Cyber
Navigation
repo://simon-says-duo/MyApp/ParticipantView.swift

MyApp/ParticipantView.swift

4.4 KB · 97 lines · swift · Download from the repository page

MyApp/ParticipantView.swift
import SwiftUIimport AVKit struct DirectionProbe: UIViewRepresentable {    let outer: Bool    let changed: ([String], Bool) -> Void    final class Coordinator {        var direction: AVCaptureDeviceDirectionCoordinator?    }    func makeCoordinator() -> Coordinator { Coordinator() }    func makeUIView(context: Context) -> UIView {        let view = UIView()        let outer = outer        let changed = changed        context.coordinator.direction = AVCaptureDeviceDirectionCoordinator(view: view, deviceTypes: [.builtInWideAngleCamera]) { map in            let devices = outer ? map.forwardFacingDeviceDescriptors : map.backwardFacingDeviceDescriptors            changed(devices.map(\.uniqueID), outer)        }        return view    }    func updateUIView(_ uiView: UIView, context: Context) {}} struct ParticipantView: View {    let presentation: ParticipantPresentation    let image: UIImage?    let simulated: Bool    let stop: () -> Void    let directionChanged: ([String], Bool) -> Void     var cueAppeared: (UUID) -> Void = { _ in }    var cueDisappeared: (UUID) -> Void = { _ in }     @State private var recDotOn = true    @Environment(\.accessibilityReduceMotion) private var reduceMotion     var body: some View {        VStack(spacing: RetroTheme.spaceM) {            if simulated {                RetroTag(title: "SIMULATED PARTICIPANT VIEW", color: RetroTheme.pink)            }            HStack(spacing: RetroTheme.spaceS) {                if presentation.recording {                    Circle().fill(RetroTheme.error).frame(width: 12, height: 12)                        .opacity(reduceMotion ? 1 : (recDotOn ? 1 : 0.25))                        .onAppear { if !reduceMotion { withAnimation(.easeInOut(duration: 0.6).repeatForever(autoreverses: true)) { recDotOn.toggle() } } }                        .accessibilityHidden(true)                }                Text(presentation.status.uppercased())                    .font(RetroTheme.heading(.title))            }            PreviewImage(image: image).overlay {                if let cue = presentation.cue {                    Text(cue.letter)                        .font(.system(size: 120, weight: .black, design: .rounded))                        .foregroundStyle(RetroTheme.ink).frame(width: 160, height: 160)                        .background(RetroTheme.yellow, in: .rect(cornerRadius: RetroTheme.radius))                        .overlay(RoundedRectangle(cornerRadius: RetroTheme.radius).stroke(.white, lineWidth: RetroTheme.border))                        .id(cue.id)                        .onAppear { if !simulated { cueAppeared(cue.id) } }                        .onDisappear { if !simulated { cueDisappeared(cue.id) } }                }            }            Text("Keep your head and hands in view. Leave room to move safely.")                .font(.callout).foregroundStyle(.white.opacity(0.75))            if let countdown = presentation.countdown {                Text("\(countdown)").font(.system(size: 64, weight: .black, design: .rounded)).monospacedDigit()                    .foregroundStyle(RetroTheme.yellow)            }            Button("Stop", systemImage: "stop.fill", action: stop)                .buttonStyle(RetroButtonStyle(color: RetroTheme.pink)).controlSize(.large)        }        .padding(.horizontal, RetroTheme.screenPaddingH)        .padding(.vertical, RetroTheme.spaceL)        .frame(maxWidth: .infinity, maxHeight: .infinity)        .background(Color.black).foregroundStyle(.white)        .overlay(RoundedRectangle(cornerRadius: 0).stroke(RetroTheme.yellow, lineWidth: RetroTheme.border))        .background { DirectionProbe(outer: true, changed: directionChanged) }    }} struct PreviewImage: View {    let image: UIImage?    var body: some View {        ZStack {            RoundedRectangle(cornerRadius: 4).fill(.black)            if let image {                Image(uiImage: image).resizable().scaledToFit().scaleEffect(x: -1, y: 1)                    .accessibilityLabel("Mirrored participant camera preview")            } else {                ContentUnavailableView("Camera preview", systemImage: "camera", description: Text("No live camera image"))                    .foregroundStyle(.white)            }        }.frame(minHeight: 180, idealHeight: 280, maxHeight: 340).clipShape(.rect(cornerRadius: 4)).overlay(RoundedRectangle(cornerRadius: 4).stroke(RetroTheme.yellow, lineWidth: 2))    }}