Advertisement

← poker-tutor

Sources/PokerUI/Tutorial.swift

3.7 KB Download file
import SwiftUI
import PokerCore
import PokerSession

/// What the app is, in six screens, before anyone has played a hand.
///
/// It explains the two things that are genuinely surprising about this app rather than teaching
/// poker: that the coach stays quiet most of the time on purpose, and that the history is where the
/// real learning is. Everything else can be discovered by pressing it.
struct Tutorial: View {
    @Environment(AppState.self) var app
    @Environment(\.metrics) var metrics
    @State var page = 0

    /// The writing itself is in `Copy`, with the rest of the app's prose, so the suite can read it.
    private var pages: [Copy.TutorialPage] {
        Copy.tutorial(for: app.variant.format, structure: app.cashSetup.structure)
    }

    var body: some View {
        VStack(spacing: 0) {
            // Every other sheet in the app scrolls; this one had a fixed height and no scroll view,
            // so its longest page ran off the bottom at the largest text size — on the one sheet
            // whose whole job is to be read, reachable from Settings long after the type has been
            // turned up. It grows with the words now, and scrolls when the window will not.
            ScrollView {
                VStack(alignment: .leading, spacing: 16) {
                    Symbol(pages[page].icon, size: metrics.compact ? 34 : 40)
                        .foregroundStyle(Theme.accent)
                        .frame(height: metrics.compact ? 44 : 52, alignment: .bottom)

                    Text(pages[page].title)
                        .font(.system(size: Theme.size(Theme.introBase(metrics.compact ? 21 : 24)),
                                      weight: .bold, design: .rounded))
                        .foregroundStyle(Theme.text)
                        .fixedSize(horizontal: false, vertical: true)

                    Text(pages[page].body)
                        .body(Theme.introBase(TextRole.body.base)).foregroundStyle(Theme.dim)
                        .lineSpacing(4)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(metrics.compact ? 22 : 28)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)

            HStack(spacing: 10) {
                ForEach(0..<pages.count, id: \.self) { index in
                    Circle()
                        .fill(index == page ? Theme.accent : Theme.dim.opacity(0.35))
                        .frame(width: 6, height: 6)
                }
                Spacer()
                if page > 0 {
                    GhostButton(title: "Back", narrow: true) {
                        withAnimation(.easeOut(duration: 0.18)) { page -= 1 }
                    }
                }
                if !metrics.compact { AdLabMark(size: 10); Spacer(minLength: 12) }
                PillButton(title: page == pages.count - 1 ? "Start playing" : "Next") {
                    if page == pages.count - 1 {
                        app.finishTutorial()
                    } else {
                        withAnimation(.easeOut(duration: 0.18)) { page += 1 }
                    }
                }
            }
            .padding(metrics.compact ? 18 : 22)
            .background(Theme.panelRaised)
        }
        .frame(width: metrics.compact ? nil : Theme.size(560))
        .frame(minHeight: metrics.compact ? nil : 420)
        .frame(maxWidth: metrics.compact ? .infinity : nil,
               maxHeight: metrics.compact ? .infinity : nil)
        .background(Theme.panel)
    }
}