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
10 changes: 9 additions & 1 deletion ios/HouseScan/Runtime/ResultClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,17 @@ final class UploadProgressDelegate: NSObject, URLSessionTaskDelegate, Sendable {
self.progress = progress
}

/// Once the whole body is sent the server is working out the spot in the same request, so
/// report 1 and let the screen move on to "Check clearances". Before that, stop at 0.99 so
/// rounding never shows 100%. Holding 0.99 until the answer left the screen on "Sending
/// measurements, 99%" for the whole analysis (field test run 1).
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
guard totalBytesExpectedToSend > 0 else { return }
progress(min(0.99, Double(totalBytesSent) / Double(totalBytesExpectedToSend)))
if totalBytesSent >= totalBytesExpectedToSend {
progress(1)
} else {
progress(min(0.99, Double(totalBytesSent) / Double(totalBytesExpectedToSend)))
}
}
}

Expand Down
27 changes: 20 additions & 7 deletions ios/HouseScan/UI/Copy/ScanCopy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ enum ScanCopy {
}
}

/// The name mid-sentence: "Tap the gas meter", "Mark AC unit". Lowercasing `name` read
/// "ac unit" (B-16).
static func noun(_ kind: FeatureKind) -> String {
switch kind {
case .acUnit: "AC unit"
default: name(kind).lowercased()
}
}

static func symbol(_ kind: FeatureKind) -> String {
switch kind {
case .gasMeter: "flame.fill"
Expand All @@ -165,10 +174,10 @@ enum ScanCopy {
}

static func markingPrompt(_ marking: MarkingState) -> Instruction {
let noun = name(marking.kind).lowercased()
let item = noun(marking.kind)
switch (marking.kind, marking.step) {
case (.door, 0), (.window, 0):
return Instruction(title: "Tap the \(noun)'s bottom-left corner", detail: "Put the circle on it and tap Mark, or tap it on screen.")
return Instruction(title: "Tap the \(item)'s bottom-left corner", detail: "Put the circle on it and tap Mark, or tap it on screen.")
case (.door, _), (.window, _):
return Instruction(title: "Now tap its top-right corner", detail: nil)
case (.driveway, 0):
Expand All @@ -180,7 +189,7 @@ enum ScanCopy {
case (.fence, _):
return Instruction(title: "Now tap the bottom at the other end", detail: nil)
case (.gasMeter, _), (.acUnit, _):
return Instruction(title: "Tap the \(noun)", detail: "Put the circle on it and tap Mark, or tap it on screen.")
return Instruction(title: "Tap the \(item)", detail: "Put the circle on it and tap Mark, or tap it on screen.")
}
}

Expand Down Expand Up @@ -366,14 +375,18 @@ enum ScanCopy {
}

/// "Measured 3 ft 2 in. The rule is 3 ft, and the measurement can be off by about 4 in."
static func measurement(_ row: CheckRow) -> String? {
/// `spoken` spells out feet and inches for VoiceOver, which reads "ft" and "in" as letters.
static func measurement(_ row: CheckRow, spoken: Bool = false) -> String? {
guard let measured = row.measured else { return nil }
var parts = ["Measured \(Distance.feetAndInches(measured))."]
func length(_ meters: Float) -> String {
spoken ? Distance.spoken(meters) : Distance.feetAndInches(meters)
}
var parts = ["Measured \(length(measured))."]
if let threshold = row.threshold {
if let plusMinus = row.plusMinus, plusMinus > 0 {
parts.append("The rule is \(Distance.feetAndInches(threshold)), and the measurement can be off by about \(Distance.feetAndInches(plusMinus)).")
parts.append("The rule is \(length(threshold)), and the measurement can be off by about \(length(plusMinus)).")
} else {
parts.append("The rule is \(Distance.feetAndInches(threshold)).")
parts.append("The rule is \(length(threshold)).")
}
}
return parts.joined(separator: " ")
Expand Down
19 changes: 4 additions & 15 deletions ios/HouseScan/UI/Result/ResultScene3D.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,33 +293,22 @@ struct ResultScene3D: View {

// MARK: - Accessibility

/// Lengths are spelled out: VoiceOver reads "ft" and "in" as letters (B-16).
private var accessibilitySummary: String {
guard let spot = result.spot else { return "No battery spot shown" }
var parts: [String]
if spot.span.lowerBound > 0 {
parts = ["Battery \(Self.feetAndInches(spot.span.lowerBound)) right of your meter"]
parts = ["Battery \(Distance.spoken(spot.span.lowerBound)) right of your meter"]
} else if spot.span.upperBound < 0 {
parts = ["Battery \(Self.feetAndInches(-spot.span.upperBound)) left of your meter"]
parts = ["Battery \(Distance.spoken(-spot.span.upperBound)) left of your meter"]
} else {
parts = ["Battery below your meter"]
}
if let cable = result.cableLength {
parts.append("cable \(Self.feetAndInches(cable))")
parts.append("cable \(Distance.spoken(cable))")
}
return parts.joined(separator: ", ")
}

/// "3 ft 4 in", "12 ft", "8 in", rounded to the nearest inch.
private static func feetAndInches(_ meters: Float) -> String {
let inches = Int((meters / 0.0254).rounded())
let feet = inches / 12
let rest = inches % 12
switch (feet, rest) {
case (0, _): return "\(rest) in"
case (_, 0): return "\(feet) ft"
default: return "\(feet) ft \(rest) in"
}
}
}

/// Fixed scene colors. They match the asset-catalog palette in light mode; a model of a house
Expand Down
4 changes: 2 additions & 2 deletions ios/HouseScan/UI/Screens/MarkFeaturesScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private struct FeatureRow: View {
.frame(width: Metrics.minTarget, height: Metrics.minTarget)
}
.foregroundStyle(Palette.danger)
.accessibilityLabel("Remove \(ScanCopy.name(feature.kind).lowercased())")
.accessibilityLabel("Remove \(ScanCopy.noun(feature.kind))")
.accessibilityIdentifier("action.deleteFeature")
}
if feature.kind == .window {
Expand Down Expand Up @@ -229,7 +229,7 @@ private struct FlowChips: View {
.contentShape(.rect(cornerRadius: 14))
}
.buttonStyle(PressableStyle())
.accessibilityLabel("Add \(ScanCopy.name(kind).lowercased())")
.accessibilityLabel("Add \(ScanCopy.noun(kind))")
.accessibilityIdentifier("feature.\(kind.rawValue)")
}
}
2 changes: 1 addition & 1 deletion ios/HouseScan/UI/Screens/ResultScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ private struct CheckRowView: View {
.padding(14)
.accessibilityElement(children: .combine)
.accessibilityLabel("\(row.title): \(ScanCopy.outcomeWord(row.outcome))")
.accessibilityValue([row.reason, ScanCopy.measurement(row), row.outcome == .unsure ? ScanCopy.unsureNote(row) : nil]
.accessibilityValue([row.reason, ScanCopy.measurement(row, spoken: true), row.outcome == .unsure ? ScanCopy.unsureNote(row) : nil]
.compactMap(\.self).joined(separator: ". "))
.accessibilityIdentifier("check.\(row.id)")
}
Expand Down
14 changes: 11 additions & 3 deletions ios/HouseScan/UI/Screens/WallWalkScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,21 @@ struct WallWalkScreen: View {
}
if let side = state.endQuestion { return ScanCopy.endQuestion(side) }
if state.overheadQuestion { return ScanCopy.overheadQuestion }
if let coaching = state.coaching { return ScanCopy.coaching(coaching) }
if let coaching { return ScanCopy.coaching(coaching) }
return ScanCopy.guidance(state.guidance)
}

/// "Slow down" is for walking. With the tray open the homeowner has stopped to pick a mark
/// and is only turning the phone, which the gate also reads as moving (field test run 1).
/// Tracking problems still show.
private var coaching: Coaching? {
if trayOpen, state.coaching == .slowDown { return nil }
return state.coaching
}

private var tone: InstructionCard.Tone {
if state.marking?.refusal != nil { return .refusal }
if state.marking == nil, state.endQuestion == nil, !state.overheadQuestion, let coaching = state.coaching {
if state.marking == nil, state.endQuestion == nil, !state.overheadQuestion, let coaching {
return .coaching(symbol: ScanCopy.coachingSymbol(coaching))
}
if state.marking == nil, case .markNextWall(_, _?) = state.guidance { return .refusal }
Expand Down Expand Up @@ -360,7 +368,7 @@ struct FeatureTray: View {
.contentShape(.rect(cornerRadius: 14))
}
.buttonStyle(PressableStyle())
.accessibilityLabel("Mark \(ScanCopy.name(kind).lowercased())")
.accessibilityLabel("Mark \(ScanCopy.noun(kind))")
.accessibilityIdentifier("feature.\(kind.rawValue)")
}
}
Expand Down
Loading