← poker-tutor
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)
}
}