|
import SwiftUI |
|
import Foundation |
|
import HealthKit |
|
|
|
enum AchievementType: String, CaseIterable { |
|
case personalBest = "Personal Best" |
|
case streak = "Streaks" |
|
case dateAndTime = "Date and time based" |
|
case totalTypesOfWorkout = "Total types of workout" |
|
case workoutType = "Workout types" |
|
|
|
var description: String { |
|
switch self { |
|
case .dateAndTime: return "Mix up your schedule by working out at different times." |
|
case .workoutType: return "Discover a new hobby" |
|
case .totalTypesOfWorkout: return "Gotta try 'em all" |
|
case .personalBest: return "Do stuff in Personal Best, earn achievements" |
|
case .streak: return "Streak your way to success" |
|
} |
|
} |
|
} |
|
|
|
struct AchievementManager { |
|
|
|
static func getEarnedAchievements(forWorkouts workouts: [HKWorkout], leaderboardsCount: Int) -> [Achievement] { |
|
let allAchievements = getAchievements(forWorkouts: workouts, leaderboardsCount: leaderboardsCount) |
|
|
|
var earnedAchievements = [Achievement]() |
|
allAchievements.values.forEach { achievementsForType in |
|
achievementsForType.forEach { achievement in |
|
if achievement.isAchieved { |
|
earnedAchievements.append(achievement) |
|
} |
|
} |
|
} |
|
|
|
return earnedAchievements |
|
} |
|
|
|
static func getAchievements(forWorkouts workouts: [HKWorkout], leaderboardsCount: Int) -> [AchievementType: [Achievement]] { |
|
|
|
var allAchievementsForTotalTypesOfWorkout = AchievementForTotalTypesOfWorkout.allAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForWorkoutType = AchievementForWorkoutType.allAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForTimeOfDay = AchievementForTimeOfDay.allAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForDate = AchievementForDate.allAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForDayStreak = AchievementForStreak.allDayAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForMonthStreak = AchievementForStreak.allMonthAchievements.toDictionary(with: { $0.id }) |
|
var allAchievementsForYearStreak = AchievementForStreak.allYearAchievements.toDictionary(with: { $0.id }) |
|
|
|
var workoutsGroupedByType = [HKWorkoutActivityType: [HKWorkout]]() |
|
var workoutsGroupedByHour = [Int: Set<HKWorkout>]() |
|
var workoutsGroupedByYear = [Date: [HKWorkout]]() |
|
var workoutsGroupedByMonthAndYear = [Date: [HKWorkout]]() |
|
var workoutsGroupedByDayAndMonth = [Date: [HKWorkout]]() |
|
var workoutsGroupedByDayMonthAndYear = [Date: [HKWorkout]]() |
|
|
|
for workout in workouts { |
|
|
|
workoutsGroupedByType.add(workout.workoutActivityType, value: workout) |
|
|
|
let startHour = workout.startDateWithTimeZone.hour |
|
let endHour = workout.startDateWithTimeZone.hour |
|
workoutsGroupedByHour.add(startHour, value: workout) |
|
if endHour != startHour { |
|
workoutsGroupedByHour.add(endHour, value: workout) |
|
} |
|
|
|
if let dayMonthYear = Calendar.current.date(from: workout.endDateWithTimeZone.get(.day, .month, .year)) { |
|
workoutsGroupedByDayMonthAndYear.add(dayMonthYear, value: workout) |
|
} |
|
|
|
if let dayMonth = Calendar.current.date(from: workout.endDateWithTimeZone.get(.day, .month)) { |
|
workoutsGroupedByDayAndMonth.add(dayMonth, value: workout) |
|
} |
|
|
|
if let monthYear = Calendar.current.date(from: workout.endDateWithTimeZone.get(.month, .year)) { |
|
workoutsGroupedByMonthAndYear.add(monthYear, value: workout) |
|
} |
|
|
|
if let year = Calendar.current.date(from: workout.endDateWithTimeZone.get(.year)) { |
|
workoutsGroupedByYear.add(year, value: workout) |
|
} |
|
|
|
} // for |
|
|
|
// Streaks (day) |
|
if let highestNumberOfWorkoutsInOneDay = workoutsGroupedByDayMonthAndYear.values.map({ $0.count }).max() { |
|
for id in allAchievementsForDayStreak.keys { |
|
allAchievementsForDayStreak[id]?.checkAchievement(totalWorkouts: highestNumberOfWorkoutsInOneDay) |
|
} |
|
} |
|
|
|
// Streaks (month) |
|
if let highestNumberOfWorkoutsInOneMonth = workoutsGroupedByMonthAndYear.values.map({ $0.count }).max() { |
|
for id in allAchievementsForMonthStreak.keys { |
|
allAchievementsForMonthStreak[id]?.checkAchievement(totalWorkouts: highestNumberOfWorkoutsInOneMonth) |
|
} |
|
} |
|
|
|
// Streaks (year) |
|
if let highestNumberOfWorkoutsInOneYear = workoutsGroupedByYear.values.map({ $0.count }).max() { |
|
for id in allAchievementsForYearStreak.keys { |
|
allAchievementsForYearStreak[id]?.checkAchievement(totalWorkouts: highestNumberOfWorkoutsInOneYear) |
|
} |
|
} |
|
|
|
// Total types completed. |
|
let totalTypesOfWorkoutCompleted = workoutsGroupedByType.keys.count |
|
for id in allAchievementsForTotalTypesOfWorkout.keys { |
|
allAchievementsForTotalTypesOfWorkout[id]?.checkAchievement(totalTypesCompleted: totalTypesOfWorkoutCompleted) |
|
} |
|
|
|
// Workout types. |
|
for id in allAchievementsForWorkoutType.keys { |
|
if let desiredWorkoutType = allAchievementsForWorkoutType[id]?.workoutType, |
|
let workoutsForDesiredType = workoutsGroupedByType[desiredWorkoutType] { |
|
allAchievementsForWorkoutType[id]?.checkAchievement(totalTypesCompleted: workoutsForDesiredType.count) |
|
} |
|
} |
|
|
|
// Time of day. |
|
for id in allAchievementsForTimeOfDay.keys { |
|
let hoursWithWorkouts = Array(workoutsGroupedByHour.keys) |
|
allAchievementsForTimeOfDay[id]?.checkAchievement(forHours: hoursWithWorkouts) |
|
} |
|
|
|
// Date. |
|
for id in allAchievementsForDate.keys { |
|
let datesWithWorkouts = Array(workoutsGroupedByDayAndMonth.keys) |
|
allAchievementsForDate[id]?.checkAchievement(forDates: datesWithWorkouts) |
|
} |
|
|
|
// Personal Best. |
|
var allAchievementsForPersonalBest = [AchievementForPersonalBest]() |
|
|
|
var hasPersonalBest = AchievementForPersonalBest.hasPersonalBest |
|
hasPersonalBest.isAchieved = true |
|
allAchievementsForPersonalBest.append(hasPersonalBest) |
|
|
|
var hasPersonalBestPro = AchievementForPersonalBest.hasPersonalBestPro |
|
if UserPreferences.hasAccessToPro { |
|
hasPersonalBestPro.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(hasPersonalBestPro) |
|
|
|
var changedAppIcon = AchievementForPersonalBest.changedAppIcon |
|
if AchievementRecord.changedAppIcon.isAchieved || UIApplication.shared.alternateIconName != nil { |
|
changedAppIcon.isAchieved = true |
|
AchievementRecord.changedAppIcon.setAchieved() |
|
} |
|
allAchievementsForPersonalBest.append(changedAppIcon) |
|
|
|
var leftATip = AchievementForPersonalBest.leftATip |
|
if AchievementRecord.leftATip.isAchieved { |
|
leftATip.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(leftATip) |
|
|
|
var customLeaderboard = AchievementForPersonalBest.customLeaderboard |
|
if AchievementRecord.madeCustomLeaderboard.isAchieved || leaderboardsCount > 0 { |
|
customLeaderboard.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(customLeaderboard) |
|
|
|
var confettiCannon = AchievementForPersonalBest.confettiCannon |
|
if AchievementRecord.firedConfettiCannon.isAchieved { |
|
confettiCannon.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(confettiCannon) |
|
|
|
var sharedWorkout = AchievementForPersonalBest.sharedWorkout |
|
if AchievementRecord.sharedWorkout.isAchieved { |
|
sharedWorkout.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(sharedWorkout) |
|
|
|
var openedMap = AchievementForPersonalBest.openedMap |
|
if AchievementRecord.openedMap.isAchieved { |
|
openedMap.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(openedMap) |
|
|
|
var openedSplits = AchievementForPersonalBest.openedSplits |
|
if AchievementRecord.openedSplits.isAchieved { |
|
openedSplits.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(openedSplits) |
|
|
|
var changedStatsPeriod = AchievementForPersonalBest.changedStatisticTimePeriod |
|
if AchievementRecord.changedStatisticsTimePeriod.isAchieved { |
|
changedStatsPeriod.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(changedStatsPeriod) |
|
|
|
var completedYearInReview2023 = AchievementForPersonalBest.completedYearInReview2023 |
|
if AchievementRecord.completedYearInReview2023.isAchieved { |
|
completedYearInReview2023.isAchieved = true |
|
} |
|
allAchievementsForPersonalBest.append(completedYearInReview2023) |
|
|
|
let workoutTypeAchievements = Array(allAchievementsForWorkoutType.values).sorted(by: AchievementForWorkoutType.sortFunction) |
|
let dateAndTimeAchievements: [Achievement] = Array(allAchievementsForTimeOfDay.values).sorted(by: AchievementForTimeOfDay.sortFunction) + Array(allAchievementsForDate.values).sorted(by: AchievementForDate.sortFunction) |
|
let totalTypesOfWorkoutAchievements = Array(allAchievementsForTotalTypesOfWorkout.values).sorted(by: AchievementForTotalTypesOfWorkout.sortFunction) |
|
let streakAchievements = Array(allAchievementsForDayStreak.values).sorted(by: AchievementForStreak.sortFunction) + Array(allAchievementsForMonthStreak.values).sorted(by: AchievementForStreak.sortFunction) + Array(allAchievementsForYearStreak.values).sorted(by: AchievementForStreak.sortFunction) |
|
|
|
return [ |
|
.workoutType: workoutTypeAchievements, |
|
.dateAndTime: dateAndTimeAchievements, |
|
.totalTypesOfWorkout: totalTypesOfWorkoutAchievements, |
|
.personalBest: allAchievementsForPersonalBest, |
|
.streak: streakAchievements, |
|
] |
|
} |
|
} |