| @@ -6,26 +6,68 @@ struct TourPicker: View { |
| 6 | 6 | var onPick: (Tour) -> Void |
| 7 | 7 | @Environment(\.dismiss) private var dismiss |
| 8 | 8 | |
| 9 | private let ink = Color(red: 0.95, green: 0.95, blue: 0.93) |
| 10 | private let accent = Color(red: 0.88, green: 0.24, blue: 0.19) |
| 11 | private let month = Calendar.current.component(.month, from: .now) |
| 12 | |
| 9 | 13 | var body: some View { |
| 10 | 14 | NavigationStack { |
| 11 | | List(Tour.ordered(month: Calendar.current.component(.month, from: .now))) { tour in |
| 12 | | Button { |
| 13 | | onPick(tour) |
| 14 | | dismiss() |
| 15 | | } label: { |
| 16 | | VStack(alignment: .leading, spacing: 4) { |
| 17 | | Text(tour.title).font(.body.weight(.medium)) |
| 18 | | Text(tour.intro).font(.footnote).foregroundStyle(.secondary) |
| 19 | | Text("\(tour.steps.count) stops").font(.caption).foregroundStyle(.secondary) |
| 20 | | } |
| 21 | | .padding(.vertical, 4) |
| 15 | ScrollView { |
| 16 | VStack(alignment: .leading, spacing: 0) { |
| 17 | Text("Guided tours").font(.title.weight(.medium)) |
| 18 | Rectangle().fill(accent).frame(width: 48, height: 5).padding(.top, 8).padding(.bottom, 12) |
| 19 | Text("A walk around the evening sky, one sight at a time. The view turns to each stop and marks what to look for. Best outside after dark, but they work anywhere.") |
| 20 | .font(.subheadline).foregroundStyle(ink.opacity(0.75)) |
| 21 | .padding(.bottom, 20) |
| 22 | ForEach(Tour.ordered(month: month)) { tour in card(tour) } |
| 22 | 23 | } |
| 23 | | .buttonStyle(.plain) |
| 24 | .padding(20) |
| 25 | .readableWidth() |
| 24 | 26 | } |
| 25 | | .navigationTitle("Guided tours") |
| 26 | | .navigationBarTitleDisplayMode(.inline) |
| 27 | .background(Color.black.ignoresSafeArea()) |
| 27 | 28 | .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } |
| 28 | 29 | } |
| 30 | .foregroundStyle(ink) |
| 31 | .tint(accent) |
| 32 | } |
| 33 | |
| 34 | private func card(_ tour: Tour) -> some View { |
| 35 | let now = tour.months.contains(month) |
| 36 | return Button { |
| 37 | onPick(tour) |
| 38 | dismiss() |
| 39 | } label: { |
| 40 | VStack(alignment: .leading, spacing: 10) { |
| 41 | HStack { |
| 42 | if now { |
| 43 | Text("THIS MONTH").font(.caption2.weight(.bold)).tracking(0.8) |
| 44 | .padding(.horizontal, 6).padding(.vertical, 2) |
| 45 | .background(accent, in: RoundedRectangle(cornerRadius: 3)) |
| 46 | } |
| 47 | Text(tour.title).font(.caption.weight(.medium)).foregroundStyle(ink.opacity(0.7)) |
| 48 | Spacer() |
| 49 | Text("\(tour.steps.count) stops").font(.caption).foregroundStyle(ink.opacity(0.6)) |
| 50 | } |
| 51 | Text(tour.steps.prefix(4).map(\.title).joined(separator: " · ") + (tour.steps.count > 4 ? " …" : "")) |
| 52 | .font(.title3.weight(.medium)) |
| 53 | .multilineTextAlignment(.leading) |
| 54 | Text(tour.intro).font(.footnote).foregroundStyle(ink.opacity(0.75)).multilineTextAlignment(.leading) |
| 55 | HStack(spacing: 6) { |
| 56 | Text("Start tour").font(.subheadline.weight(.semibold)) |
| 57 | Image(systemName: "arrow.right").font(.footnote.weight(.semibold)) |
| 58 | } |
| 59 | .foregroundStyle(now ? accent : ink.opacity(0.8)) |
| 60 | .padding(.top, 2) |
| 61 | } |
| 62 | .padding(18) |
| 63 | .frame(maxWidth: .infinity, alignment: .leading) |
| 64 | .background(ink.opacity(now ? 0.09 : 0.05), in: RoundedRectangle(cornerRadius: 18)) |
| 65 | .overlay(RoundedRectangle(cornerRadius: 18).stroke(now ? accent.opacity(0.6) : ink.opacity(0.1), lineWidth: now ? 1 : 0.5)) |
| 66 | .contentShape(RoundedRectangle(cornerRadius: 18)) |
| 67 | } |
| 68 | .buttonStyle(.plain) |
| 69 | .padding(.bottom, 14) |
| 70 | .accessibilityHint("Starts the tour") |
| 29 | 71 | } |
| 30 | 72 | } |
| 31 | 73 | |