Skip to content

Commit 4a8ae67

Browse files
committed
[LOOP-4687] favorite food carb entry flow
1 parent c25ca70 commit 4a8ae67

File tree

2 files changed

+146
-4
lines changed

2 files changed

+146
-4
lines changed

Loop/View Models/CarbEntryViewModel.swift

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ final class CarbEntryViewModel: ObservableObject {
5050
@Published var selectedDefaultAbsorptionTimeEmoji: String = ""
5151
@Published var usesCustomFoodType = false
5252
@Published var absorptionTimeWasEdited = false // if true, selecting an emoji will not alter the absorption time
53+
private var absorptionEditIsProgrammatic = false // needed for when absorption time is changed due to favorite food selection, so that absorptionTimeWasEdited does not get set to true
5354

5455
@Published var absorptionTime: TimeInterval
5556
let defaultAbsorptionTimes: CarbStore.DefaultAbsorptionTimes
@@ -59,6 +60,9 @@ final class CarbEntryViewModel: ObservableObject {
5960
return minAbsorptionTime...maxAbsorptionTime
6061
}
6162

63+
@Published var favoriteFoods = UserDefaults.standard.favoriteFoods
64+
@Published var selectedFavoriteFoodIndex = -1
65+
6266
weak var delegate: CarbEntryViewModelDelegate?
6367

6468
private lazy var cancellables = Set<AnyCancellable>()
@@ -71,6 +75,8 @@ final class CarbEntryViewModel: ObservableObject {
7175
self.shouldBeginEditingQuantity = true
7276

7377
observeAbsorptionTimeChange()
78+
observeFavoriteFoodChange()
79+
observeFavoriteFoodIndexChange()
7480
}
7581

7682
/// Initalizer for when`CarbEntryView` has an entry to edit
@@ -89,6 +95,7 @@ final class CarbEntryViewModel: ObservableObject {
8995
}
9096

9197
var originalCarbEntry: StoredCarbEntry? = nil
98+
private var favoriteFood: FavoriteFood? = nil
9299

93100
private var updatedCarbEntry: NewCarbEntry? {
94101
if let quantity = carbsQuantity, quantity != 0 {
@@ -111,7 +118,7 @@ final class CarbEntryViewModel: ObservableObject {
111118

112119
var saveFavoriteFoodButtonDisabled: Bool {
113120
get {
114-
if let carbsQuantity, 0...maxCarbEntryQuantity.doubleValue(for: preferredCarbUnit) ~= carbsQuantity, foodType != "" {
121+
if let carbsQuantity, 0...maxCarbEntryQuantity.doubleValue(for: preferredCarbUnit) ~= carbsQuantity, foodType != "", selectedFavoriteFoodIndex == -1 {
115122
return false
116123
}
117124
return true
@@ -142,7 +149,7 @@ final class CarbEntryViewModel: ObservableObject {
142149
self.alert = .maxQuantityExceded
143150
return
144151
}
145-
else if quantity.compare(warningCarbEntryQuantity) == .orderedDescending {
152+
else if quantity.compare(warningCarbEntryQuantity) == .orderedDescending, selectedFavoriteFoodIndex == -1 {
146153
self.alert = .warningQuantityValidation
147154
return
148155
}
@@ -181,13 +188,64 @@ final class CarbEntryViewModel: ObservableObject {
181188
}
182189
}
183190

191+
// MARK: - Favorite Foods
192+
func onFavoriteFoodSave(_ food: NewFavoriteFood) {
193+
let newStoredFood = StoredFavoriteFood(name: food.name, carbsQuantity: food.carbsQuantity, foodType: food.foodType, absorptionTime: food.absorptionTime)
194+
favoriteFoods.append(newStoredFood)
195+
selectedFavoriteFoodIndex = favoriteFoods.count - 1
196+
}
197+
198+
private func observeFavoriteFoodIndexChange() {
199+
$selectedFavoriteFoodIndex
200+
.receive(on: RunLoop.main)
201+
.dropFirst()
202+
.sink { [weak self] index in
203+
self?.favoriteFoodSelected(at: index)
204+
}
205+
.store(in: &cancellables)
206+
}
207+
208+
private func observeFavoriteFoodChange() {
209+
$favoriteFoods
210+
.dropFirst()
211+
.removeDuplicates()
212+
.sink { newValue in
213+
UserDefaults.standard.favoriteFoods = newValue
214+
}
215+
.store(in: &cancellables)
216+
}
217+
218+
private func favoriteFoodSelected(at index: Int) {
219+
self.absorptionEditIsProgrammatic = true
220+
if index == -1 {
221+
self.carbsQuantity = 0
222+
self.foodType = ""
223+
self.absorptionTime = defaultAbsorptionTimes.medium
224+
self.absorptionTimeWasEdited = false
225+
self.usesCustomFoodType = false
226+
}
227+
else {
228+
let food = favoriteFoods[index]
229+
self.carbsQuantity = food.carbsQuantity.doubleValue(for: preferredCarbUnit)
230+
self.foodType = food.foodType
231+
self.absorptionTime = food.absorptionTime
232+
self.absorptionTimeWasEdited = true
233+
self.usesCustomFoodType = true
234+
}
235+
}
236+
184237
// MARK: - Utility
185238
private func observeAbsorptionTimeChange() {
186239
$absorptionTime
187240
.receive(on: RunLoop.main)
188241
.dropFirst()
189242
.sink { [weak self] _ in
190-
self?.absorptionTimeWasEdited = true
243+
if self?.absorptionEditIsProgrammatic == true {
244+
self?.absorptionEditIsProgrammatic = false
245+
}
246+
else {
247+
self?.absorptionTimeWasEdited = true
248+
}
191249
}
192250
.store(in: &cancellables)
193251
}

Loop/Views/CarbEntryView.swift

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
2020
@State private var expandedRow: Row?
2121

2222
@State private var showHowAbsorptionTimeWorks = false
23+
@State private var showAddFavoriteFood = false
2324

2425
private let isNewEntry: Bool
2526

@@ -70,6 +71,10 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
7071

7172
continueActionButton
7273

74+
if isNewEntry {
75+
favoriteFoodsCard
76+
}
77+
7378
let isBolusViewActive = Binding(get: { viewModel.bolusViewModel != nil }, set: { _, _ in viewModel.bolusViewModel = nil })
7479
NavigationLink(destination: bolusView, isActive: isBolusViewActive) {
7580
EmptyView()
@@ -80,6 +85,9 @@ struct CarbEntryView: View, HorizontalSizeClassOverride {
8085
}
8186
}
8287
.alert(item: $viewModel.alert, content: alert(for:))
88+
.sheet(isPresented: $showAddFavoriteFood, onDismiss: clearExpandedRow) {
89+
AddEditFavoriteFoodView(carbsQuantity: $viewModel.carbsQuantity.wrappedValue, foodType: $viewModel.foodType.wrappedValue, absorptionTime: $viewModel.absorptionTime.wrappedValue, onSave: onFavoriteFoodSave(_:))
90+
}
8391
.sheet(isPresented: $showHowAbsorptionTimeWorks) {
8492
HowAbsorptionTimeWorksView()
8593
}
@@ -171,10 +179,86 @@ extension CarbEntryView {
171179
.padding()
172180
.disabled(viewModel.continueButtonDisabled)
173181
}
182+
183+
private var favoriteFoodsCard: some View {
184+
VStack(alignment: .leading, spacing: 6) {
185+
Text("FAVORITE FOODS")
186+
.font(.footnote)
187+
.foregroundColor(.secondary)
188+
.padding(.horizontal, 26)
189+
190+
VStack(spacing: 10) {
191+
if !viewModel.favoriteFoods.isEmpty {
192+
VStack {
193+
HStack {
194+
Text("Choose Favorite:")
195+
196+
let selectedFavorite = favoritedFoodTextFromIndex(viewModel.selectedFavoriteFoodIndex)
197+
Text(selectedFavorite)
198+
.minimumScaleFactor(0.8)
199+
.frame(maxWidth: .infinity, alignment: .trailing)
200+
}
201+
202+
if expandedRow == .favoriteFoodSelection {
203+
Picker("", selection: $viewModel.selectedFavoriteFoodIndex) {
204+
ForEach(-1..<viewModel.favoriteFoods.count, id: \.self) { index in
205+
Text(favoritedFoodTextFromIndex(index))
206+
.tag(index)
207+
}
208+
}
209+
.pickerStyle(.wheel)
210+
}
211+
}
212+
.onTapGesture {
213+
withAnimation {
214+
if expandedRow == .favoriteFoodSelection {
215+
expandedRow = nil
216+
}
217+
else {
218+
expandedRow = .favoriteFoodSelection
219+
}
220+
}
221+
}
222+
223+
CardSectionDivider()
224+
}
225+
226+
Button(action: saveAsFavoriteFood) {
227+
Text("Save as favorite food")
228+
.frame(maxWidth: .infinity)
229+
}
230+
.disabled(viewModel.saveFavoriteFoodButtonDisabled)
231+
}
232+
.padding(.vertical, 12)
233+
.padding(.horizontal)
234+
.background(CardBackground())
235+
.padding(.horizontal)
236+
}
237+
}
238+
239+
private func favoritedFoodTextFromIndex(_ index: Int) -> String {
240+
if index == -1 {
241+
return "None"
242+
}
243+
else {
244+
let food = viewModel.favoriteFoods[index]
245+
return "\(food.name) \(food.foodType)"
246+
}
247+
}
248+
249+
private func saveAsFavoriteFood() {
250+
self.showAddFavoriteFood = true
251+
}
252+
253+
private func onFavoriteFoodSave(_ food: NewFavoriteFood) {
254+
clearExpandedRow()
255+
self.showAddFavoriteFood = false
256+
viewModel.onFavoriteFoodSave(food)
257+
}
174258
}
175259

176260
extension CarbEntryView {
177261
enum Row {
178-
case amountConsumed, time, foodType, absorptionTime
262+
case amountConsumed, time, foodType, absorptionTime, favoriteFoodSelection
179263
}
180264
}

0 commit comments

Comments
 (0)