Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions appleApp/FTW/UI/PassFigures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import SwiftUI
/// meets. Each a mono title, a figure and a line, with space between them
/// and no borders. The first title takes the pass's colour; a plan that is
/// old reads in ink3. It swaps with the pass, with no animation, so a tap
/// answers at once.
/// answers at once. Where the three do not fit side by side, as at the
/// largest text sizes, they stand one under another rather than end in "…".
struct PassFigures: View, Equatable {
let pass: PlanPass
var grey = false
Expand All @@ -15,30 +16,42 @@ struct PassFigures: View, Equatable {
nonisolated static func == (a: Self, b: Self) -> Bool { a.pass == b.pass && a.grey == b.grey }

var body: some View {
HStack(alignment: .top, spacing: 12) {
ForEach(Array(pass.figures.enumerated()), id: \.offset) { i, f in
VStack(alignment: i == 2 ? .trailing : .leading, spacing: 1) {
Text(f.title)
.font(Theme.mono())
.tracking(0.5)
.foregroundStyle(grey ? Theme.ink3 : tint(f.tint))
.dynamicTypeSize(...DynamicTypeSize.accessibility1)
Text(f.value)
.font(Theme.figure(figure))
.foregroundStyle(grey ? Theme.ink3 : Theme.ink)
.minimumScaleFactor(0.7)
Text(f.sub)
.font(.caption)
.foregroundStyle(grey ? Theme.ink3 : Theme.ink2)
let figures = Array(pass.figures.enumerated())
ViewThatFits(in: .horizontal) {
HStack(alignment: .top, spacing: 12) {
ForEach(figures, id: \.offset) { i, f in
column(f, trailing: i == 2)
.frame(maxWidth: .infinity, alignment: i == 2 ? .trailing : .leading)
}
}
VStack(alignment: .leading, spacing: 12) {
ForEach(figures, id: \.offset) { _, f in
column(f, trailing: false)
}
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: i == 2 ? .trailing : .leading)
}
}
.accessibilityElement(children: .ignore)
.accessibilityLabel(pass.spoken)
}

private func column(_ f: PlanPass.Figure, trailing: Bool) -> some View {
VStack(alignment: trailing ? .trailing : .leading, spacing: 1) {
Text(f.title)
.font(Theme.mono())
.tracking(0.5)
.foregroundStyle(grey ? Theme.ink3 : tint(f.tint))
.dynamicTypeSize(...DynamicTypeSize.accessibility1)
Text(f.value)
.font(Theme.figure(figure))
.foregroundStyle(grey ? Theme.ink3 : Theme.ink)
.minimumScaleFactor(0.7)
Text(f.sub)
.font(.caption)
.foregroundStyle(grey ? Theme.ink3 : Theme.ink2)
}
.lineLimit(1)
}

private func tint(_ t: PlanPass.Figure.Tint) -> Color {
switch t {
case .gives: return Theme.gives
Expand Down
36 changes: 29 additions & 7 deletions appleApp/FTW/UI/PlanStrip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,25 @@ struct PlanStripView: View, Equatable {
}

/// The kicker and its chevron, and room for the price, which sits over
/// the strip as a link of its own.
/// the strip as a link of its own. Beside the price where both fit; at
/// the largest sizes the price takes the first line and the kicker the
/// next, so the two never overlap.
private var header: some View {
ViewThatFits(in: .horizontal) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
kickerLine
Spacer(minLength: 8)
priceRoom
}
VStack(alignment: .leading, spacing: 4) {
priceRoom
kickerLine
}
}
.animation(Theme.spring, value: strip.kicker)
}

private var kickerLine: some View {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(priceLabel.map { strip.kicker + " · " + $0 } ?? strip.kicker)
.font(mono)
Expand All @@ -172,13 +189,18 @@ struct PlanStripView: View, Equatable {
.font(.caption2.weight(.semibold))
.foregroundStyle(Theme.ink3)
}
Spacer(minLength: 8)
// The price's line, never drawn: it gives the header the price's
// height and baseline, so the kicker sits on the same line.
Text("\(Text(verbatim: "0").font(Theme.figure(figure))) \(Text(verbatim: unit).font(mono).tracking(0.5))")
.hidden()
}
.animation(Theme.spring, value: strip.kicker)
}

/// The price's line, never drawn: it gives the header the price's
/// width, height and baseline, so the kicker sits on the same line and
/// clear of it.
private var priceRoom: some View {
let figures = priceNow.map { AppWords.decimal($0) } ?? "0"
let words = priceNow == nil ? unit : "\(unit) \(PlanGlance.nowWord)"
return Text("\(Text(verbatim: figures).font(Theme.figure(figure))) \(Text(verbatim: words).font(mono).tracking(0.5))")
.lineLimit(1)
.hidden()
}

private func follow(_ x: CGFloat) {
Expand Down
18 changes: 12 additions & 6 deletions appleApp/FTWCharts/PlanStripDrawing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ struct PlanStripLayout: Equatable {

var priceTop: CGFloat { Self.lanesBottom + levelHeight }
var priceBottom: CGFloat { priceTop + priceHeight }
/// 100 % and 0 % in the level's row, clear of the lanes and the bars.
/// 100 % and 0 % in the level's row, clear of the lanes and the bars,
/// and 0 %'s figure clear of the price's top figure at any text size.
var levelTop: CGFloat { Self.lanesBottom + 5 }
var levelBottom: CGFloat { priceTop - 14 }
var levelBottom: CGFloat { priceTop - max(14, (axis * 1.2).rounded(.up)) }
func levelY(_ pct: Double) -> CGFloat { levelBottom - CGFloat(min(100, max(0, pct)) / 100) * (levelBottom - levelTop) }
var hoursTop: CGFloat { priceBottom + 3 }
/// Room on the right for the scale's figures, "600", in the axis's
Expand Down Expand Up @@ -177,17 +178,22 @@ struct PlanStripDrawing: ChartDrawing {
if layout.levelHeight > 0 { drawLevel(in: &g, layout: layout, width: width, px: px, ghost: ghost, chosen: chosen) }

// The scale: a hairline at half the top and at the top, each with
// its figure in the gutter, and the zero line when a price falls
// under it.
// its figure in the gutter where there is room for it, and the zero
// line when a price falls under it. In a short row at a large text
// size the top keeps its figure and half the top keeps only its line.
let priceScale = strip.scale
var lines = Path()
for v in priceScale.lines + (priceScale.bottom < 0 ? [0] : []) {
lines.addRect(CGRect(x: 0, y: snap(layout.priceY(v, priceScale)), width: width, height: px))
}
g.fill(lines, with: .color(Theme.ink.opacity(0.1)))
for v in priceScale.lines {
var lastY: CGFloat?
for v in priceScale.lines.sorted(by: >) {
let text = g.resolve(ChartText.label(priceScale.label(v), size: layout.axis * 0.9))
g.draw(text, at: CGPoint(x: width + 4, y: layout.priceY(v, priceScale)), anchor: .leading)
let y = layout.priceY(v, priceScale)
if let lastY, abs(y - lastY) < text.measure(in: size).height + 2 { continue }
g.draw(text, at: CGPoint(x: width + 4, y: y), anchor: .leading)
lastY = y
}

// The price: a bar per stretch of equal price and equal ink. Ink by
Expand Down
Loading