Skip to content

Commit 84c81ca

Browse files
committed
Prompt:
I have found the following issues: 1. The image shown in the design is missing on the home screen. Please add it.(brew_setup folder) 2. The stepper increment should be 0.5 instead of 0.1. 3. I would like to be able to skip any step that is already completed and continue to the next one. 4. When I start brewing, the timer does not update until I tap pause and then resume. 5. I am unable to adjust the time.
1 parent 7430a10 commit 84c81ca

18 files changed

Lines changed: 266 additions & 87 deletions

File tree

.swiftlint.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ opt_in_rules:
6767
- multiline_parameters
6868
- multiline_parameters_brackets
6969
- nimble_operator
70-
- no_magic_numbers
7170
- non_overridable_class_declaration
7271
- nslocalizedstring_key
7372
- object_literal
255 KB
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "ChemexSetupHero.jpg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
}
12+
}

App/ChemexTimer/Scenes/ChemexCoach/Brew/Setup/BrewSetupView.swift

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct BrewSetupView: View, StoreContaining {
2929
doseSection
3030
ratioSection
3131
rinseFilterSection
32+
targetTimeSection
3233
metricsSection
3334
}
3435
.padding(.horizontal, 20)
@@ -62,30 +63,18 @@ private extension BrewSetupView {
6263

6364
var heroSection: some View {
6465
ZStack(alignment: .bottomLeading) {
65-
RoundedRectangle(cornerRadius: 24, style: .continuous)
66-
.fill(
66+
Image("ChemexSetupHero")
67+
.resizable()
68+
.scaledToFill()
69+
.frame(height: 124)
70+
.clipped()
71+
.overlay(
6772
LinearGradient(
68-
colors: [
69-
Color(red: 116 / 255, green: 61 / 255, blue: 41 / 255),
70-
Color(red: 62 / 255, green: 32 / 255, blue: 25 / 255),
71-
],
72-
startPoint: .topLeading,
73-
endPoint: .bottomTrailing
73+
colors: [Color.black.opacity(0.2), Color.black.opacity(0.6)],
74+
startPoint: .top,
75+
endPoint: .bottom
7476
)
7577
)
76-
.frame(height: 124)
77-
78-
Circle()
79-
.fill(Color.white.opacity(0.15))
80-
.frame(width: 130, height: 130)
81-
.blur(radius: 6)
82-
.offset(x: 38, y: -20)
83-
84-
Circle()
85-
.fill(Color.black.opacity(0.16))
86-
.frame(width: 170, height: 170)
87-
.blur(radius: 8)
88-
.offset(x: 90, y: 24)
8978

9079
VStack(alignment: .leading, spacing: 4) {
9180
Text("RECIPE")
@@ -99,6 +88,7 @@ private extension BrewSetupView {
9988
}
10089
.padding(16)
10190
}
91+
.clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
10292
.overlay(
10393
RoundedRectangle(cornerRadius: 24, style: .continuous)
10494
.stroke(Color.white.opacity(0.08), lineWidth: 1)
@@ -187,7 +177,7 @@ private extension BrewSetupView {
187177
Slider(
188178
value: store.binding(for: \.ratio, send: BrewSetupAction.didChangeRatio),
189179
in: ChemexBrewMath.minimumRatio ... ChemexBrewMath.maximumRatio,
190-
step: 0.1
180+
step: ChemexBrewMath.ratioStep
191181
)
192182
.accessibilityLabel("Brew ratio")
193183
.accessibilityValue(ChemexFormatters.ratio(store.state.ratio))
@@ -237,11 +227,58 @@ private extension BrewSetupView {
237227
.background(ChemexTheme.surfaceMuted, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
238228
.overlay(
239229
RoundedRectangle(cornerRadius: 20, style: .continuous)
240-
.stroke(ChemexTheme.stroke.opacity(0.55), lineWidth: 1)
230+
.stroke(ChemexTheme.stroke.opacity(0.55), lineWidth: 1)
241231
)
242232
.accessibilityHint("When enabled, the timer instructions assume the filter is pre-rinsed")
243233
}
244234

235+
var targetTimeSection: some View {
236+
HStack(spacing: 10) {
237+
Text("TARGET TIME")
238+
.font(.caption.weight(.semibold))
239+
.tracking(1.6)
240+
.foregroundStyle(ChemexTheme.textMuted)
241+
242+
Spacer()
243+
244+
Button(action: {
245+
store.send(action: .didTapTargetTimeDecrease)
246+
}) {
247+
Image(systemName: "minus")
248+
.font(.caption.weight(.bold))
249+
.frame(width: 28, height: 28)
250+
}
251+
.buttonStyle(.plain)
252+
.foregroundStyle(ChemexTheme.textSecondary)
253+
.accessibilityLabel("Decrease target time")
254+
255+
Text(ChemexFormatters.mmss(store.state.targetTotalTimeSeconds))
256+
.font(.headline.weight(.bold))
257+
.foregroundStyle(ChemexTheme.textPrimary)
258+
.frame(minWidth: 56)
259+
.accessibilityLabel("Target time")
260+
.accessibilityValue(ChemexFormatters.mmss(store.state.targetTotalTimeSeconds))
261+
262+
Button(action: {
263+
store.send(action: .didTapTargetTimeIncrease)
264+
}) {
265+
Image(systemName: "plus")
266+
.font(.caption.weight(.bold))
267+
.frame(width: 28, height: 28)
268+
}
269+
.buttonStyle(.plain)
270+
.foregroundStyle(ChemexTheme.textSecondary)
271+
.accessibilityLabel("Increase target time")
272+
}
273+
.padding(.horizontal, 16)
274+
.padding(.vertical, 12)
275+
.background(ChemexTheme.surfaceMuted, in: RoundedRectangle(cornerRadius: 16, style: .continuous))
276+
.overlay(
277+
RoundedRectangle(cornerRadius: 16, style: .continuous)
278+
.stroke(ChemexTheme.stroke.opacity(0.55), lineWidth: 1)
279+
)
280+
}
281+
245282
var metricsSection: some View {
246283
HStack(spacing: 12) {
247284
metricTile(title: "Total", value: ChemexFormatters.grams(store.state.plan.totalWaterGrams))

App/ChemexTimer/Scenes/ChemexCoach/Brew/Setup/Store/BrewSetupAction.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ enum BrewSetupAction: Sendable {
1313
case didTapDoseIncrease
1414
case didChangeRatio(Double)
1515
case didToggleRinseFilter(Bool)
16+
case didTapTargetTimeDecrease
17+
case didTapTargetTimeIncrease
1618

1719
case didTapStart
1820
}

App/ChemexTimer/Scenes/ChemexCoach/Brew/Setup/Store/BrewSetupState.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ struct BrewSetupState: StoreState {
1212
var doseGrams: Double = ChemexBrewSettings.defaults.defaultDoseGrams
1313
var ratio: Double = ChemexBrewSettings.defaults.defaultRatio
1414
var rinseFilter = true
15+
var targetTotalTimeSeconds = ChemexBrewMath.targetTotalTimeSeconds
1516
var plan: ChemexBrewPlan = ChemexBrewMath.makePlan(
1617
doseGrams: ChemexBrewSettings.defaults.defaultDoseGrams,
1718
ratio: ChemexBrewSettings.defaults.defaultRatio,
18-
rinseFilter: true
19+
rinseFilter: true,
20+
targetTotalTimeSeconds: ChemexBrewMath.targetTotalTimeSeconds
1921
)
2022

2123
var hasLoadedInitialSettings = false

App/ChemexTimer/Scenes/ChemexCoach/Brew/Setup/Store/BrewSetupStore.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ final class BrewSetupStore: Store {
6464
state.rinseFilter = enabled
6565
updatePlan()
6666

67+
case .didTapTargetTimeDecrease:
68+
updateTargetTime(state.targetTotalTimeSeconds - ChemexBrewMath.targetTimeStepSeconds)
69+
70+
case .didTapTargetTimeIncrease:
71+
updateTargetTime(state.targetTotalTimeSeconds + ChemexBrewMath.targetTimeStepSeconds)
72+
6773
case .didTapStart:
6874
let runtimeSettings = ChemexRuntimeSettings(
6975
soundEnabled: state.settings.soundEnabled,
@@ -77,7 +83,8 @@ final class BrewSetupStore: Store {
7783
startInput: ChemexBrewStartInput(
7884
doseGrams: state.doseGrams,
7985
ratio: state.ratio,
80-
rinseFilter: state.rinseFilter
86+
rinseFilter: state.rinseFilter,
87+
targetTotalTimeSeconds: state.targetTotalTimeSeconds
8188
),
8289
runtimeSettings: runtimeSettings
8390
)
@@ -130,7 +137,16 @@ private extension BrewSetupStore {
130137
state.plan = ChemexBrewMath.makePlan(
131138
doseGrams: state.doseGrams,
132139
ratio: state.ratio,
133-
rinseFilter: state.rinseFilter
140+
rinseFilter: state.rinseFilter,
141+
targetTotalTimeSeconds: state.targetTotalTimeSeconds
134142
)
135143
}
144+
145+
func updateTargetTime(_ target: Int) {
146+
let normalized = ChemexBrewMath.normalizedTargetTime(target)
147+
guard normalized != state.targetTotalTimeSeconds else { return }
148+
149+
state.targetTotalTimeSeconds = normalized
150+
updatePlan()
151+
}
136152
}

App/ChemexTimer/Scenes/ChemexCoach/Brew/Timer/BrewTimerView.swift

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,36 @@ struct BrewTimerView: View, StoreContaining {
2424
ZStack {
2525
ChemexScreenBackground()
2626

27-
VStack(spacing: 20) {
28-
Text("Classic Chemex")
29-
.font(.title2.weight(.bold))
30-
.foregroundStyle(ChemexTheme.textPrimary)
31-
.padding(.top, 8)
32-
.accessibilityAddTraits(.isHeader)
33-
34-
progressRing
35-
36-
currentStepCard
37-
.id(store.state.snapshot.currentStep?.kind.rawValue ?? "none")
38-
.transition(stepTransition)
39-
.animation(stepAnimation, value: store.state.snapshot.currentStep?.kind)
40-
41-
upNext
42-
43-
Spacer(minLength: 0)
44-
45-
controls
46-
.padding(.bottom, 20)
27+
ScrollView {
28+
VStack(spacing: 20) {
29+
Text("Classic Chemex")
30+
.font(.title2.weight(.bold))
31+
.foregroundStyle(ChemexTheme.textPrimary)
32+
.padding(.top, 8)
33+
.accessibilityAddTraits(.isHeader)
34+
35+
progressRing
36+
37+
currentStepCard
38+
.id(store.state.snapshot.currentStep?.kind.rawValue ?? "none")
39+
.transition(stepTransition)
40+
.animation(stepAnimation, value: store.state.snapshot.currentStep?.kind)
41+
42+
upNext
43+
44+
Spacer(minLength: 0)
45+
46+
controls
47+
.padding(.bottom, 20)
48+
}
49+
.padding(.horizontal, 20)
50+
.padding(.bottom, 20)
4751
}
48-
.padding(.horizontal, 20)
52+
.scrollIndicators(.hidden)
4953
}
5054
.toolbar(.hidden, for: .navigationBar)
5155
.onFirstAppear {
5256
store.send(action: .viewDidAppear)
53-
store.send(action: .didChangeScenePhase(scenePhase))
5457
}
5558
.onChange(of: scenePhase) { _, newPhase in
5659
store.send(action: .didChangeScenePhase(newPhase))
@@ -221,35 +224,48 @@ private extension BrewTimerView {
221224
}
222225

223226
var controls: some View {
224-
HStack(spacing: 12) {
225-
Button(action: {
226-
store.send(action: .didTapStop)
227-
}) {
228-
Image(systemName: "stop.fill")
227+
VStack(spacing: 10) {
228+
HStack(spacing: 12) {
229+
Button(action: {
230+
store.send(action: .didTapSkip)
231+
}) {
232+
Image(systemName: "forward.end.fill")
233+
.font(.title3.weight(.bold))
234+
.frame(width: 62, height: 56)
235+
}
236+
.foregroundStyle(ChemexTheme.textPrimary)
237+
.background(ChemexTheme.surfaceElevated, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
238+
.overlay(
239+
RoundedRectangle(cornerRadius: 20, style: .continuous)
240+
.stroke(ChemexTheme.stroke, lineWidth: 1)
241+
)
242+
.opacity(store.state.snapshot.nextStep == nil ? 0.55 : 1)
243+
.disabled(store.state.snapshot.nextStep == nil)
244+
.accessibilityLabel("Skip step")
245+
.accessibilityHint("Skips to the next brew step")
246+
247+
Button(action: {
248+
store.send(action: .didTapPauseResume)
249+
}) {
250+
Label(
251+
store.state.engine.status == .paused ? "Resume" : "Pause",
252+
systemImage: store.state.engine.status == .paused ? "play.fill" : "pause.fill"
253+
)
229254
.font(.title3.weight(.bold))
230-
.frame(width: 62, height: 56)
255+
.contentTransition(.symbolEffect(.replace))
256+
.accessibilityLabel(store.state.engine.status == .paused ? "Resume brew" : "Pause brew")
257+
}
258+
.buttonStyle(ChemexPrimaryButtonStyle())
231259
}
232-
.foregroundStyle(ChemexTheme.textPrimary)
233-
.background(ChemexTheme.surfaceElevated, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
234-
.overlay(
235-
RoundedRectangle(cornerRadius: 20, style: .continuous)
236-
.stroke(ChemexTheme.stroke, lineWidth: 1)
237-
)
238-
.accessibilityLabel("Stop brew")
239-
.accessibilityHint("Stops the current brew and discards it")
240260

241-
Button(action: {
242-
store.send(action: .didTapPauseResume)
243-
}) {
244-
Label(
245-
store.state.engine.status == .paused ? "Resume" : "Pause",
246-
systemImage: store.state.engine.status == .paused ? "play.fill" : "pause.fill"
247-
)
248-
.font(.title3.weight(.bold))
249-
.contentTransition(.symbolEffect(.replace))
250-
.accessibilityLabel(store.state.engine.status == .paused ? "Resume brew" : "Pause brew")
261+
Button("Stop", role: .destructive) {
262+
store.send(action: .didTapStop)
251263
}
252-
.buttonStyle(ChemexPrimaryButtonStyle())
264+
.font(.subheadline.weight(.semibold))
265+
.foregroundStyle(ChemexTheme.textSecondary)
266+
.padding(.vertical, 8)
267+
.accessibilityLabel("Stop brew")
268+
.accessibilityHint("Stops the current brew and discards it")
253269
}
254270
}
255271

App/ChemexTimer/Scenes/ChemexCoach/Brew/Timer/Store/BrewTimerAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ enum BrewTimerAction: Sendable {
1212
case timerTick(Date)
1313

1414
case didTapPauseResume
15+
case didTapSkip
1516
case didTapStop
1617
case didConfirmStop
1718
case didCancelStop

0 commit comments

Comments
 (0)