Skip to content
Open
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
4 changes: 4 additions & 0 deletions Stanford360.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
4F331D212D5C34B000CCC8AA /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 4F331D202D5C34B000CCC8AA /* FirebaseAuth */; };
4FA061BE2D4AF594008DE21A /* HealthKitOnFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 4FA061BD2D4AF594008DE21A /* HealthKitOnFHIR */; };
4FA061C02D4AF59C008DE21A /* ResearchKitOnFHIR in Frameworks */ = {isa = PBXBuildFile; productRef = 4FA061BF2D4AF59C008DE21A /* ResearchKitOnFHIR */; };
4FC5EF042D78162800BFDFFD /* KidsOnboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC5EF032D78162800BFDFFD /* KidsOnboarding.swift */; };
4FCAD0592D5AAD20007324A6 /* ActivityViewUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCAD0582D5AAD20007324A6 /* ActivityViewUITest.swift */; };
5680DD3E2AB8CD84004E6D4A /* ContributionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */; };
56E708352BB06B7100B08F0A /* SpeziLicense in Frameworks */ = {isa = PBXBuildFile; productRef = 56E708342BB06B7100B08F0A /* SpeziLicense */; };
Expand Down Expand Up @@ -133,6 +134,7 @@
2FF53D8C2A8729D600042B76 /* Stanford360Standard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stanford360Standard.swift; sourceTree = "<group>"; };
459A2CA22D6FD9DB00D429E9 /* Stanford360StandardHydration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stanford360StandardHydration.swift; sourceTree = "<group>"; };
45E038222D503BFA009D07E2 /* HydrationTrackerViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HydrationTrackerViewTests.swift; sourceTree = "<group>"; };
4FC5EF032D78162800BFDFFD /* KidsOnboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KidsOnboarding.swift; sourceTree = "<group>"; };
4FCAD0582D5AAD20007324A6 /* ActivityViewUITest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityViewUITest.swift; sourceTree = "<group>"; };
5680DD3D2AB8CD84004E6D4A /* ContributionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributionsTest.swift; sourceTree = "<group>"; };
653A254D283387FE005D4D48 /* Stanford360.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stanford360.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -252,6 +254,7 @@
2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */,
2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */,
2F65B44D2A3B8B0600A36932 /* NotificationPermissions.swift */,
4FC5EF032D78162800BFDFFD /* KidsOnboarding.swift */,
);
path = Onboarding;
sourceTree = "<group>";
Expand Down Expand Up @@ -615,6 +618,7 @@
2FE5DC3729EDD7CA004B9AB4 /* OnboardingFlow.swift in Sources */,
2F1AC9DF2B4E840E00C24973 /* Stanford360.docc in Sources */,
C156248A2D6D6D9300298CEB /* DayTimelineView.swift in Sources */,
4FC5EF042D78162800BFDFFD /* KidsOnboarding.swift in Sources */,
2FF53D8D2A8729D600042B76 /* Stanford360Standard.swift in Sources */,
C1CF13DA2D6D76BE0057BC77 /* ScheduleTask.swift in Sources */,
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--showOnboarding"
isEnabled = "NO">
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--skipOnboarding"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--testSchedule"
Expand Down
107 changes: 50 additions & 57 deletions Stanford360/Activity/Model/ActivityManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,55 @@ import UserNotifications
class ActivityManager: Module, EnvironmentAccessible {
// MARK: - Properties
var activities: [Activity] = []
var activitiesByDate: [Date: [Activity]] {
var activitiesByDate: [Date: [Activity]] = [:]
for activity in activities {
let normalizedDate = Calendar.current.startOfDay(for: activity.date)
activitiesByDate[normalizedDate, default: []].append(activity)
}

return activitiesByDate
}
var activitiesByDate: [Date: [Activity]] {
var activitiesByDate: [Date: [Activity]] = [:]
for activity in activities {
let normalizedDate = Calendar.current.startOfDay(for: activity.date)
activitiesByDate[normalizedDate, default: []].append(activity)
}

return activitiesByDate
}

// Streak Calculation
var streak: Int {
let calendar = Calendar.current
let now = Date()
let todayStart = calendar.startOfDay(for: now)

// Safely calculate yesterday
guard let previousDate = calendar.date(byAdding: .day, value: -1, to: todayStart) else {
return 0 // error
}
var dateToCheck = previousDate

// Calculate streak for previous days
var streakCount = 0
while let activities = activitiesByDate[dateToCheck] {
let minutes = getTotalActivityMinutes(activities)
if minutes >= 60 {
streakCount += 1
} else {
break
}
guard let newDate = calendar.date(byAdding: .day, value: -1, to: dateToCheck) else {
return 0 // error
}
dateToCheck = newDate
}

// Handle today's activity separately
if let todaysActivities = activitiesByDate[todayStart],
getTotalActivityMinutes(todaysActivities) >= 60 {
streakCount += 1
}

return streakCount
}

// MARK: - Initialization
init(activities: [Activity] = []) {
self.activities = activities
init(activities: [Activity] = []) {
self.activities = activities
}

// MARK: - Methods
Expand All @@ -41,27 +77,10 @@ class ActivityManager: Module, EnvironmentAccessible {
.filter { Calendar.current.isDate($0.date, inSameDayAs: today) }
.reduce(0) { $0 + $1.activeMinutes }
}

func getTotalActivityMinutes(_ activities: [Activity]) -> Int {
activities.reduce(0) { $0 + $1.activeMinutes }
}

// // Method to edit an existing activity
// func editActivity(_ updatedActivity: Activity) {
// if let index = activities.firstIndex(where: { $0.id == updatedActivity.id }) {
// activities[index] = updatedActivity
// }
// }
//
// // Method to delete an activity
// func deleteActivity(_ activity: Activity) {
// activities.removeAll { $0.id == activity.id }
// }

// func getTodayActivity() -> Activity? {
// let today = Calendar.current.startOfDay(for: Date())
// return activities.first { Calendar.current.startOfDay(for: $0.date) == today }
// }
func getTotalActivityMinutes(_ activities: [Activity]) -> Int {
activities.reduce(0) { $0 + $1.activeMinutes }
}

func getWeeklySummary() -> [Activity] {
let calendar = Calendar.current
Expand All @@ -81,25 +100,6 @@ class ActivityManager: Module, EnvironmentAccessible {
.sorted { $0.date < $1.date }
}

// func checkStreak() -> Int {
// var streak = 0
// let calendar = Calendar.current
// let sortedActivities = activities.sorted(by: { $0.date > $1.date })
// var previousDate: Date?
//
// for activity in sortedActivities {
// let activityDate = calendar.startOfDay(for: activity.date)
// if let prev = previousDate, calendar.date(byAdding: .day, value: -1, to: prev) != activityDate {
// break
// }
// if activity.activeMinutes >= 60 {
// streak += 1
// previousDate = activityDate
// }
// }
// return streak
// }
//
func triggerMotivation() -> String {
if getTodayTotalMinutes() >= 60 {
return "🎉 Amazing! You've reached your daily goal of 60 minutes!"
Expand All @@ -111,13 +111,6 @@ class ActivityManager: Module, EnvironmentAccessible {
}
}

// private func loadFromStorage() {
// if let data = UserDefaults.standard.data(forKey: "activities"),
// let decoded = try? JSONDecoder().decode([Activity].self, from: data) {
// activities = decoded
// }
// }

func saveToStorage() {
do {
let data = try JSONEncoder().encode(activities)
Expand Down
15 changes: 9 additions & 6 deletions Stanford360/Activity/View/ActivityCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ struct ActivityCardView: View {
.font(.title3)
.foregroundStyle(.blue)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.white)
.shadow(radius: 2)
)
// .background(
// RoundedRectangle(cornerRadius: 12)
// .fill(Color.white)
// .shadow(radius: 2)
// )
.padding(16)
.background(Color.cardBackground)
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 4)
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button {
showingAddActivitySheet = true
Expand Down
126 changes: 74 additions & 52 deletions Stanford360/Activity/View/ActivityChartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import Charts
import SwiftUI

struct ActivityChartView: View {
let activities: [Activity]
let title: String
let isWeekly: Bool
@Environment(ActivityManager.self) private var activityManager
var isWeekly: Bool

// Add colors for different activities
private let activityColors: [String: Color] = [
Expand All @@ -31,75 +30,98 @@ struct ActivityChartView: View {
]

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(title)
.font(.headline)
.padding(.horizontal)

VStack {
if isWeekly {
weeklyChart
} else {
monthlyChart
}
}
}

private var weeklyChart: some View {
let timeFrame = TimeFrame.week
let startDateAxis = Calendar.current.date(byAdding: .day, value: -7, to: Date()) ?? timeFrame.dateRange().start

return Group {
Chart {
ForEach(activities) { activity in
if isWeekly {
BarMark(
x: .value("Date", activity.date, unit: .day),
y: .value("Minutes", activity.activeMinutes)
)
.foregroundStyle(activityColors[activity.activityType] ?? .blue)
} else {
LineMark(
x: .value("Date", activity.date, unit: .day),
y: .value("Minutes", activity.activeMinutes)
)
.foregroundStyle(activityColors[activity.activityType] ?? .blue)
.symbol {
Circle()
.fill(activityColors[activity.activityType] ?? .blue)
.frame(width: 8, height: 8)
let weeklyActivities = activityManager.getWeeklySummary()
ForEach(weeklyActivities) { activity in
BarMark(
x: .value("Date", activity.date, unit: .day),
y: .value("Minutes", activity.activeMinutes)
)
.foregroundStyle(activityColors[activity.activityType] ?? .blue)
}
goalLine()
}
.frame(height: 200)
.padding()
.chartYScale(domain: 0...150)
.chartXAxis {
AxisMarks(values: .automatic) { value in
if let date = value.as(Date.self) {
AxisValueLabel {
Text(date, format: .dateTime.weekday().day())
}
}
}

}
.chartXScale(domain: startDateAxis...Date())
}
}

private var monthlyChart: some View {
let timeFrame = TimeFrame.month
let startDateAxis = Calendar.current.date(byAdding: .month, value: -1, to: Date()) ?? timeFrame.dateRange().start

return Group {
Chart {
let monthlyActivities = activityManager.getMonthlyActivities()
let activitiesByDate = Dictionary(grouping: monthlyActivities) { Calendar.current.startOfDay(for: $0.date) }

ForEach(activitiesByDate.keys.sorted(), id: \.self) { date in
let totalMinutes = activityManager.getTotalActivityMinutes(activitiesByDate[date] ?? [])

LineMark(
x: .value("Date", date),
y: .value("Total Minutes", totalMinutes)
)
.foregroundStyle(Color.activityColor)
.symbol {
Circle()
.fill(Color.activityColor)
.frame(width: 8, height: 8)
}
}
goalLine()
}
.frame(height: 200)
.padding()
.chartYScale(domain: 0...150)
.chartXAxis {
AxisMarks(values: .automatic) { value in
if let date = value.as(Date.self) {
AxisValueLabel {
Text(date, format: .dateTime.month().day())
}
}
}
}
.chartXScale(domain: startDateAxis...Date())
}
}
}

#Preview {
// Sample activities for the last week
let sampleActivities: [Activity] = [
Activity(
date: Date(),
steps: 8000,
activeMinutes: 45,
activityType: "Running"
),
{
guard let date = Calendar.current.date(byAdding: .day, value: -1, to: Date()) else {
fatalError("Failed to generate date")
}
return Activity(
date: date,
steps: 6000,
activeMinutes: 30,
activityType: "Walking"
)
}()
]

return VStack {
VStack {
// Preview weekly chart
ActivityChartView(
activities: sampleActivities,
title: "Weekly Progress",
isWeekly: true
)

// Preview monthly chart
ActivityChartView(
activities: sampleActivities,
title: "Monthly Progress",
isWeekly: false
)
}
Expand Down
Loading