Skip to content

Commit b4b080d

Browse files
Merge branch 'main' into jiayu-chang/LLM-for-protein-and-classification-update
2 parents 0f527e4 + 43385a7 commit b4b080d

19 files changed

+183
-321
lines changed

README.md

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,38 @@ The App consists of four main screens.
3535
### Activity
3636
The Activity module in Stanford 360 features a progress ring that tracks daily physical activity, aiming for 60 minutes. Users can log activities like Walking, Running, and Sports by selecting a date and entering active minutes. Logged activities sync with Firestore, HealthKit, and local storage. HealthKit integration converts steps into active minutes (100 steps/min), with a warning if disconnected.
3737

38+
<table>
39+
<tr>
40+
<td><img src="https://github.com/user-attachments/assets/018c545c-1b34-420d-85e2-f8b3f9856c6b#gh-light-mode-only" width="250"></td>
41+
<td><img src="https://github.com/user-attachments/assets/29efa538-c3f0-465e-9096-3637dfd44647#gh-dark-mode-only" width="250"></td>
42+
<td><img src="https://github.com/user-attachments/assets/d100c139-fc57-49ee-bf81-15c1d3974e2c#gh-light-mode-only" width="250"></td>
43+
<td><img src="https://github.com/user-attachments/assets/cd801be2-c51b-49f3-aafd-25398df0ad54#gh-dark-mode-only" width="250"></td>
44+
</tr>
45+
</table>
46+
3847
### Hydration
3948
The central focus is a progress ring that visually represents the user's current water intake. Below, users can quickly log their hydration using preset portion buttons, each featuring clear icons for different drink sizes. A "Log Water Intake" button ensures seamless entry, while a reset button allows users to easily correct accidental logs. Any additions or deletions will be saved to or removed from Firestore, ensuring that hydration data is securely stored and synchronized across devices. Changes will also automatically update the corresponding card in the History View, maintaining real-time accuracy in tracking.
4049

41-
<img src="https://github.com/user-attachments/assets/386052d2-c30d-4c04-a642-8c8dd73ffb24#gh-light-mode-only" width="25%">
42-
<img src="https://github.com/user-attachments/assets/991e1ca4-d702-482f-bf7b-53737a38e857#gh-dark-mode-only" width="25%">
43-
<img src="https://github.com/user-attachments/assets/d5f7f580-c4aa-4859-ae89-3756ee9c8799#gh-light-mode-only" width="25%">
44-
<img src="https://github.com/user-attachments/assets/2d1bc9ea-1b64-4107-a858-d22f55784fde#gh-dark-mode-only" width="25%">
50+
<table>
51+
<tr>
52+
<td><img src="https://github.com/user-attachments/assets/386052d2-c30d-4c04-a642-8c8dd73ffb24#gh-light-mode-only" width="250"></td>
53+
<td><img src="https://github.com/user-attachments/assets/991e1ca4-d702-482f-bf7b-53737a38e857#gh-dark-mode-only" width="250"></td>
54+
<td><img src="https://github.com/user-attachments/assets/d5f7f580-c4aa-4859-ae89-3756ee9c8799#gh-light-mode-only" width="250"></td>
55+
<td><img src="https://github.com/user-attachments/assets/2d1bc9ea-1b64-4107-a858-d22f55784fde#gh-dark-mode-only" width="250"></td>
56+
</tr>
57+
</table>
4558

4659
### Protein
4760
The protein module designed to help users monitor their daily protein intake through interactive charts, milestone feedback, and AI-powered meal recognition. The app provides a visual representation of daily protein intake, displaying it through a chart and sending milestone notifications every time the intake reaches 20 grams, offering positive reinforcement to encourage healthy eating habits. Users can log their meals using two entry methods: manual entry, where they input food items and specify protein content, and picture-based entry, where they upload meal images. The backend processes these images by concurrently sending requests to two models to identify the meal name, while SpeziLLM analyzes the meal’s protein content and automatically fills in the intake data. Additionally, the app enables users to store and retrieve meal images and intake records, allowing them to track their nutrition history in detail. Future enhancements include integrating additional food databases for improved accuracy and providing personalized nutrition insights based on dietary patterns.
4861

49-
<img src="https://github.com/user-attachments/assets/1cee00ac-0791-4e98-aa4f-eb5aa431aa43#gh-light-mode-only" width="25%">
50-
<img src="https://github.com/user-attachments/assets/9baedd9e-f2cd-4e5e-abb9-87e9fba618fa#gh-dark-mode-only" width="25%">
51-
<img src="https://github.com/user-attachments/assets/caf88f14-f049-4c91-9141-28ee3c227ccc#gh-dark-mode-only" width="25%">
52-
<img src="https://github.com/user-attachments/assets/bafd39fe-d6c9-4cf1-815d-be36655ca002#gh-light-mode-only" width="25%">
53-
<img src="https://github.com/user-attachments/assets/2b77d8aa-ebd2-4bf4-9be5-352a94fa3517#gh-light-mode-only" width="25%">
54-
55-
56-
62+
<table>
63+
<tr>
64+
<td><img src="https://github.com/user-attachments/assets/1abee39f-5b83-43c7-8758-e54b45eb69ea#gh-light-mode-only" width="250"></td>
65+
<td><img src="https://github.com/user-attachments/assets/8bc09404-7496-4f7c-9955-2e525599907e#gh-dark-mode-only" width="250"></td>
66+
<td><img src="https://github.com/user-attachments/assets/ec7b8ac7-2a08-4216-a5d8-3f0261b46644#gh-light-mode-only" width="250"></td>
67+
<td><img src="https://github.com/user-attachments/assets/caf88f14-f049-4c91-9141-28ee3c227ccc#gh-dark-mode-only" width="250"></td>
68+
</tr>
69+
</table>
5770

5871
In addition to the specific features of each screen, we also provide essential functionalities that enhance the overall app experience and ensure seamless integration across all features.
5972

@@ -63,12 +76,17 @@ The History feature allows users to track their complete past records for Protei
6376
### Discover
6477
The Discover feature provides suggestions and educational insights for Protein, Activity, and Hydration, helping users make informed choices and build healthier habits.
6578

66-
<img src="https://github.com/user-attachments/assets/878d0105-7219-4129-8392-a4e94034780a#gh-light-mode-only" width="25%">
67-
<img src="https://github.com/user-attachments/assets/70d4ddb9-caf7-4b0c-be64-f6d4c721b55e#gh-dark-mode-only" width="25%">
68-
<img src="https://github.com/user-attachments/assets/0fcefe4a-0af9-4d3f-b8aa-dffa73aefc89#gh-light-mode-only" width="25%">
69-
<img src="https://github.com/user-attachments/assets/119fc31b-780c-4529-8b7c-79f4a0a95b7d#gh-dark-mode-only" width="25%">
70-
<img src="https://github.com/user-attachments/assets/881d95ef-0e9b-4dc9-b385-9c850dfbac1d#gh-light-mode-only" width="25%">
71-
<img src="https://github.com/user-attachments/assets/79bc6d15-ffb4-4671-913d-0d176dcb428e#gh-dark-mode-only" width="25%">
79+
<table>
80+
<tr>
81+
<td><img src="https://github.com/user-attachments/assets/878d0105-7219-4129-8392-a4e94034780a#gh-light-mode-only" width="250"></td>
82+
<td><img src="https://github.com/user-attachments/assets/70d4ddb9-caf7-4b0c-be64-f6d4c721b55e#gh-dark-mode-only" width="250"></td>
83+
<td><img src="https://github.com/user-attachments/assets/0fcefe4a-0af9-4d3f-b8aa-dffa73aefc89#gh-light-mode-only" width="250"></td>
84+
<td><img src="https://github.com/user-attachments/assets/119fc31b-780c-4529-8b7c-79f4a0a95b7d#gh-dark-mode-only" width="250"></td>
85+
<td><img src="https://github.com/user-attachments/assets/881d95ef-0e9b-4dc9-b385-9c850dfbac1d#gh-light-mode-only" width="250"></td>
86+
<td><img src="https://github.com/user-attachments/assets/79bc6d15-ffb4-4671-913d-0d176dcb428e#gh-dark-mode-only" width="250"></td>
87+
</tr>
88+
</table>
89+
7290

7391

7492
### Milestone/Goal
@@ -78,10 +96,14 @@ A goal check text is displayed in the center of each view. Before reaching 60, i
7896
#### Milestone Tracking
7997
Every 20-unit increment triggers a milestone message to encourage progress. Upon reaching 60 units, a special milestone message appears, celebrating the achievement along with the current streak day.
8098

81-
<img src="https://github.com/user-attachments/assets/fba07491-de2f-4417-8944-1c3d4f91bc92#gh-light-mode-only" width="25%">
82-
<img src="https://github.com/user-attachments/assets/17d529d2-be3f-4d0e-a045-4d37b2d96be6#gh-dark-mode-only" width="25%">
83-
<img src="https://github.com/user-attachments/assets/3cbe03ce-9485-4e91-8aaa-a26297ee33d2#gh-light-mode-only" width="25%">
84-
<img src="https://github.com/user-attachments/assets/3a9962df-ebe9-40a8-9cf7-99717e870e9d#gh-dark-mode-only" width="25%">
99+
<table>
100+
<tr>
101+
<td><img src="https://github.com/user-attachments/assets/fba07491-de2f-4417-8944-1c3d4f91bc92#gh-light-mode-only" width="250"></td>
102+
<td><img src="https://github.com/user-attachments/assets/17d529d2-be3f-4d0e-a045-4d37b2d96be6#gh-dark-mode-only" width="250"></td>
103+
<td><img src="https://github.com/user-attachments/assets/3cbe03ce-9485-4e91-8aaa-a26297ee33d2#gh-light-mode-only" width="250"></td>
104+
<td><img src="https://github.com/user-attachments/assets/3a9962df-ebe9-40a8-9cf7-99717e870e9d#gh-dark-mode-only" width="250"></td>
105+
</tr>
106+
</table>
85107

86108
### Notification
87109

Stanford360/Activity/Model/ActivityManager.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,6 @@ class ActivityManager: Module, EnvironmentAccessible {
8585
minutes * 100
8686
}
8787

88-
func triggerMotivation() -> String {
89-
if getTodayTotalMinutes() >= 60 {
90-
return "🎉 Amazing! You've reached your daily goal of 60 minutes!"
91-
} else if getTodayTotalMinutes() > 0 {
92-
let remainingMinutes = 60 - getTodayTotalMinutes()
93-
return "Keep going! Only \(remainingMinutes) more minutes to reach today's goal! 🚀"
94-
} else {
95-
return "Start your activity today and move towards your goal! 💪"
96-
}
97-
}
98-
9988
func saveToStorage() {
10089
do {
10190
let data = try JSONEncoder().encode(activities)

Stanford360/Activity/Model/ActivityScheduler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extension Stanford360Scheduler {
2929
}
3030
}
3131

32+
// periphery:ignore
3233
/// "delete" notification occurence of belowHalfActivity5PMNotificationTaskID by marking it complete such that it doesn't fire
3334
@MainActor
3435
func deleteNotificationOccurence(taskIds: [String]) {
@@ -46,6 +47,7 @@ extension Stanford360Scheduler {
4647
}
4748
}
4849

50+
// periphery:ignore
4951
@MainActor
5052
func handleOnLoggedBefore5PM(_ surpassed30Minutes: Bool, _ surpassed60Minutes: Bool) {
5153
if surpassed30Minutes || surpassed60Minutes {
@@ -87,6 +89,7 @@ extension Stanford360Scheduler {
8789
}
8890
}
8991

92+
// periphery:ignore
9093
@MainActor
9194
func handleOnLoggedAtOrAfter5PM(_ surpassed30Minutes: Bool, _ surpassed60Minutes: Bool) {
9295
let potentialEventIDs = [
@@ -128,6 +131,7 @@ extension Stanford360Scheduler {
128131
}
129132
}
130133

134+
// periphery:ignore
131135
@MainActor
132136
func handleNotificationsOnLoggedActivity(prevActivityMinutes: Int, newActivityMinutes: Int) async {
133137
let now = Date()

Stanford360/Activity/View/ActivityAddView.swift

Lines changed: 60 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -13,119 +13,70 @@ import SwiftUI
1313

1414
struct ActivityAddView: View {
1515
@Environment(ActivityManager.self) private var activityManager
16-
@Environment(Stanford360Standard.self) private var standard
17-
@Environment(Stanford360Scheduler.self) var scheduler
18-
19-
// Activity properties that can be initialized for editing
20-
@State private var activeMinutes: String
21-
@State private var selectedActivity: String
22-
@State private var selectedDate: Date
23-
@State private var showingDateError = false
24-
@State private var showingAddActivity = false
25-
private var activityId: String?
2616

27-
var body: some View {
28-
ZStack {
29-
VStack(spacing: 20) {
30-
PercentageRing(
31-
currentValue: activityManager.getTodayTotalMinutes(),
32-
maxValue: 60,
33-
iconName: "figure.walk",
34-
ringWidth: 25,
35-
backgroundColor: Color.activityColorBackground,
36-
foregroundColors: [Color.activityColor, Color.activityColorGradient],
37-
unitLabel: "minutes",
38-
iconSize: 13,
39-
showProgressTextInCenter: true
40-
)
41-
.frame(width: 210, height: 210) // Fixed dimensions
42-
.padding(.top, 30)
43-
44-
Text.goalMessage(current: Double(activityManager.getTodayTotalMinutes()), goal: 60, unit: "min")
45-
.padding(.top, 10)
46-
47-
// Activity input components
48-
ActivityPickerView(selectedActivity: $selectedActivity)
49-
.padding(.top, 20)
50-
51-
saveNewActivityButton(showingAddActivity: $showingAddActivity)
52-
.padding(.bottom, 30)
53-
}
54-
.padding(.horizontal)
55-
56-
MilestoneMessageView(unit: "minutes of activity")
57-
.environmentObject(activityManager.milestoneManager)
58-
.offset(y: -250)
59-
}
60-
.sheet(isPresented: $showingAddActivity) {
61-
AddActivitySheet(selectedActivity: selectedActivity)
62-
}
63-
}
64-
65-
private var saveButtonView: some View {
66-
ActionButton(
67-
title: "Save My Activity! 🌟",
68-
action: {
69-
Task {
70-
await saveNewActivity()
71-
}
72-
},
73-
isDisabled: activeMinutes.isEmpty
74-
)
75-
}
76-
77-
// MARK: - Initializers
78-
init(selectedActivity: String = "Walking", activeMinutes: String = "", selectedDate: Date = Date()) {
79-
self._selectedActivity = State(initialValue: selectedActivity)
80-
self._activeMinutes = State(initialValue: activeMinutes)
81-
self._selectedDate = State(initialValue: selectedDate)
82-
self.activityId = nil
83-
}
84-
85-
init(activity: Activity) {
86-
self._activeMinutes = State(initialValue: "\(activity.activeMinutes)")
87-
self._selectedActivity = State(initialValue: activity.activityType)
88-
self._selectedDate = State(initialValue: activity.date)
89-
self.activityId = activity.id
90-
}
91-
92-
private func saveNewActivity() async {
93-
let minutes = Int(activeMinutes) ?? 0
94-
let estimatedSteps = activityManager.getStepsFromMinutes(minutes)
95-
96-
let newActivity = Activity(
97-
date: Date(),
98-
steps: estimatedSteps,
99-
activeMinutes: minutes,
100-
activityType: selectedActivity
101-
)
102-
103-
let prevActivityMinutes = activityManager.getTodayTotalMinutes()
104-
let lastRecordedMilestone = activityManager.getLatestMilestone()
105-
activityManager.activities.append(newActivity)
106-
let activityMinutes = activityManager.getTodayTotalMinutes()
107-
let updatedStreak = activityManager.streak
108-
await standard.addActivityToFirestore(newActivity)
109-
await scheduler.handleNotificationsOnLoggedActivity(prevActivityMinutes: prevActivityMinutes, newActivityMinutes: activityMinutes)
110-
activityManager.milestoneManager.displayMilestoneMessage(
111-
newTotal: Double(activityManager.getTodayTotalMinutes()),
112-
lastMilestone: lastRecordedMilestone,
113-
unit: "minutes of activity",
114-
streak: updatedStreak
115-
)
116-
}
117-
118-
func saveNewActivityButton(showingAddActivity: Binding<Bool>) -> some View {
119-
SaveActivityButton(
120-
showingAddActivity: showingAddActivity,
121-
selectedActivity: showingAddActivity.wrappedValue ? "Walking" : nil,
122-
minutes: showingAddActivity.wrappedValue ? "0" : nil
123-
)
124-
}
17+
// Activity properties that can be initialized for editing
18+
@State private var activeMinutes: String
19+
@State private var selectedActivity: String
20+
@State private var selectedDate: Date
21+
@State private var showingAddActivity = false
22+
23+
var body: some View {
24+
ZStack {
25+
VStack(spacing: 20) {
26+
PercentageRing(
27+
currentValue: activityManager.getTodayTotalMinutes(),
28+
maxValue: 60,
29+
iconName: "figure.walk",
30+
ringWidth: 25,
31+
backgroundColor: Color.activityColorBackground,
32+
foregroundColors: [Color.activityColor, Color.activityColorGradient],
33+
unitLabel: "minutes",
34+
iconSize: 13,
35+
showProgressTextInCenter: true
36+
)
37+
.frame(height: 210)
38+
.padding(.top, 30)
39+
40+
Text.goalMessage(current: Double(activityManager.getTodayTotalMinutes()), goal: 60, unit: "min")
41+
.padding(.top, 10)
42+
43+
// Activity input components
44+
ActivityPickerView(selectedActivity: $selectedActivity)
45+
.padding(.top, 20)
46+
47+
HStack {
48+
saveNewActivityButton(showingAddActivity: $showingAddActivity)
49+
50+
ActivityRecallButton()
51+
}
52+
}
53+
.padding(.horizontal)
54+
55+
MilestoneMessageView(unit: "minutes of activity")
56+
.environmentObject(activityManager.milestoneManager)
57+
.offset(y: -250)
58+
}
59+
.sheet(isPresented: $showingAddActivity) {
60+
AddActivitySheet(selectedActivity: selectedActivity)
61+
}
62+
}
63+
64+
// MARK: - Initializers
65+
init(selectedActivity: String = "Walking", activeMinutes: String = "", selectedDate: Date = Date()) {
66+
self._selectedActivity = State(initialValue: selectedActivity)
67+
self._activeMinutes = State(initialValue: activeMinutes)
68+
self._selectedDate = State(initialValue: selectedDate)
69+
}
70+
71+
func saveNewActivityButton(showingAddActivity: Binding<Bool>) -> some View {
72+
SaveActivityButton(
73+
showingAddActivity: showingAddActivity
74+
)
75+
}
12576
}
12677

12778
#Preview {
12879
@Previewable @State var activityManager = ActivityManager(activities: activitiesData)
129-
ActivityAddView()
80+
ActivityAddView()
13081
.environment(activityManager)
13182
}

Stanford360/Activity/View/ActivityCardView.swift

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,61 +12,25 @@ import SwiftUI
1212

1313
struct ActivityCardView: View {
1414
let activity: Activity
15-
@Environment(ActivityManager.self) private var activityManager
16-
@Environment(Stanford360Standard.self) private var standard
17-
@State private var showingAddActivitySheet = false
18-
@State private var isPerformingAction = false
1915

2016
var body: some View {
2117
HStack {
2218
// Activity Type with Emoji
2319
Text(activity.activityType)
24-
.font(.title3.bold())
20+
.font(.system(size: 20))
21+
.foregroundColor(.textSecondary)
2522

2623
Spacer()
2724

2825
// Minutes with emphasis
2926
Text("\(activity.activeMinutes) min")
30-
.font(.title3)
31-
.foregroundStyle(.blue)
27+
.font(.system(size: 20))
28+
.foregroundColor(Color.activityColor)
3229
}
33-
// .background(
34-
// RoundedRectangle(cornerRadius: 12)
35-
// .fill(Color.white)
36-
// .shadow(radius: 2)
37-
// )
3830
.padding(16)
3931
.background(Color.cardBackground)
4032
.cornerRadius(15)
4133
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 4)
42-
.swipeActions(edge: .leading, allowsFullSwipe: true) {
43-
Button {
44-
showingAddActivitySheet = true
45-
} label: {
46-
Label("Edit", systemImage: "pencil")
47-
}
48-
.tint(.blue)
49-
.disabled(isPerformingAction)
50-
}
51-
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
52-
Button(role: .destructive) {
53-
isPerformingAction = true
54-
Task {
55-
await standard.deleteActivity(activity)
56-
var updatedActivities = activityManager.activities
57-
updatedActivities.removeAll { $0.id == activity.id }
58-
activityManager.activities = updatedActivities
59-
isPerformingAction = false
60-
}
61-
} label: {
62-
Label("Delete", systemImage: "trash")
63-
}
64-
.disabled(isPerformingAction)
65-
}
66-
.sheet(isPresented: $showingAddActivitySheet) {
67-
// Use the AddActivitySheet with the activity for editing
68-
AddActivitySheet(activity: activity)
69-
}
7034
}
7135
}
7236

0 commit comments

Comments
 (0)