From e301acd7f455907e58c18f3681e248221ea47c93 Mon Sep 17 00:00:00 2001 From: Damian Mazurkiewicz Date: Tue, 19 Dec 2017 14:54:51 +0000 Subject: [PATCH] Progress Charts (#29) --- BodyweightFitness.xcodeproj/project.pbxproj | 360 +++++++++---- .../AbstractViewController.swift | 76 +++ ... Fitness.plist => BodyweightFitness.plist} | 0 .../Controller/Cell/ProgressCardCell.swift | 2 +- .../Modal/LogWorkoutController.swift | 21 +- BodyweightFitness/Date+Extensions.swift | 17 +- BodyweightFitness/Domain/CompletionRate.swift | 9 + .../Domain/DataEntriesCompanion.swift | 76 +++ .../ListOfRepositoryExercisesCompanion.swift | 67 +++ .../Domain/RepositoryCategory.swift | 50 -- .../Domain/RepositoryExerciseCompanion.swift | 65 +++ .../Domain/RepositoryRoutine.swift | 134 ----- .../Domain/RepositoryRoutineCompanion.swift | 78 +++ .../Domain/RepositorySetCompanion.swift | 40 ++ .../Domain/RepositoryStream.swift | 1 - BodyweightFitness/Domain/Routine.swift | 2 + .../Domain/WorkoutChartType.swift | 4 + .../Domain/WorkoutDataEntry.swift | 17 + .../tab_home.imageset/Contents.json | 4 +- .../tab_home.imageset/Home-30.png | Bin 518 -> 0 bytes .../tab_home.imageset/Home-50.png | Bin 667 -> 0 bytes .../icons8-home-filled-30.png | Bin 0 -> 614 bytes .../icons8-home-filled-50.png | Bin 0 -> 850 bytes .../tab_settings.imageset/Contents.json | 4 +- .../tab_settings.imageset/Settings-30.png | Bin 1034 -> 0 bytes .../tab_settings.imageset/Settings-50.png | Bin 1686 -> 0 bytes .../icons8-settings-filled-30.png | Bin 0 -> 1037 bytes .../icons8-settings-filled-50.png | Bin 0 -> 1577 bytes .../Contents.json | 4 +- .../Help-30.png | Bin 841 -> 0 bytes .../Help-50.png | Bin 1499 -> 0 bytes .../icons8-help-filled-30.png | Bin 0 -> 945 bytes .../icons8-help-filled-50.png | Bin 0 -> 1575 bytes .../Calendar 10-30.png | Bin 614 -> 0 bytes .../Calendar 10-50.png | Bin 854 -> 0 bytes .../tab_workout_log.imageset/Contents.json | 4 +- .../icons8-calendar-10-filled-30.png | Bin 0 -> 664 bytes .../icons8-calendar-10-filled-50.png | Bin 0 -> 863 bytes .../Main/CalendarViewController.swift | 427 ++++++++++----- .../Main/HomeViewController.swift | 360 ++++++++++--- BodyweightFitness/Main/Main.storyboard | 322 +---------- .../Main/SupportDeveloperViewController.swift | 3 +- .../Main/View/CalendarCell.swift | 198 ------- ...itness_recommended_routine_unit_test.json} | 0 BodyweightFitness/View/CardView.swift | 8 +- BodyweightFitness/View/Views.swift | 95 ++++ BodyweightFitness/View/WorkoutChartView.swift | 116 ++++ .../RestTimerViewController.swift | 0 .../TimedViewController.swift | 2 +- .../WeightedViewController.swift | 2 +- .../ProgressGeneralViewController.swift | 110 ---- .../ProgressPageViewController.swift | 110 ---- .../WorkoutLog/WorkoutLog.storyboard | 2 +- .../WorkoutLogCategoryViewController.swift | 230 ++++++++ .../WorkoutLogFullReportViewController.swift | 102 ++++ .../WorkoutLogGeneralViewController.swift | 484 +++++++++++++++++ .../WorkoutLog/WorkoutLogViewController.swift | 37 +- .../BodyweightFitnessTests.swift | 23 +- .../Domain/DataEntriesCompanionSpec.swift | 100 ++++ ...stOfRepositoryExercisesCompanionSpec.swift | 504 ++++++++++++++++++ .../RepositoryExerciseCompanionSpec.swift | 295 ++++++++++ .../RepositoryRoutineCompanionSpec.swift | 218 ++++++++ .../Domain/RepositorySetCompanionSpec.swift | 140 +++++ BodyweightFitnessTests/Info.plist | 4 +- Podfile | 36 +- 65 files changed, 3696 insertions(+), 1267 deletions(-) create mode 100644 BodyweightFitness/AbstractViewController.swift rename BodyweightFitness/{Bodyweight Fitness.plist => BodyweightFitness.plist} (100%) create mode 100644 BodyweightFitness/Domain/CompletionRate.swift create mode 100644 BodyweightFitness/Domain/DataEntriesCompanion.swift create mode 100644 BodyweightFitness/Domain/ListOfRepositoryExercisesCompanion.swift create mode 100644 BodyweightFitness/Domain/RepositoryExerciseCompanion.swift create mode 100644 BodyweightFitness/Domain/RepositoryRoutineCompanion.swift create mode 100644 BodyweightFitness/Domain/RepositorySetCompanion.swift create mode 100644 BodyweightFitness/Domain/WorkoutChartType.swift create mode 100644 BodyweightFitness/Domain/WorkoutDataEntry.swift delete mode 100644 BodyweightFitness/Images.xcassets/tab_home.imageset/Home-30.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_home.imageset/Home-50.png create mode 100644 BodyweightFitness/Images.xcassets/tab_home.imageset/icons8-home-filled-30.png create mode 100644 BodyweightFitness/Images.xcassets/tab_home.imageset/icons8-home-filled-50.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-30.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-50.png create mode 100644 BodyweightFitness/Images.xcassets/tab_settings.imageset/icons8-settings-filled-30.png create mode 100644 BodyweightFitness/Images.xcassets/tab_settings.imageset/icons8-settings-filled-50.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_support_developer.imageset/Help-30.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_support_developer.imageset/Help-50.png create mode 100644 BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-30.png create mode 100644 BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-50.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_workout_log.imageset/Calendar 10-30.png delete mode 100644 BodyweightFitness/Images.xcassets/tab_workout_log.imageset/Calendar 10-50.png create mode 100644 BodyweightFitness/Images.xcassets/tab_workout_log.imageset/icons8-calendar-10-filled-30.png create mode 100644 BodyweightFitness/Images.xcassets/tab_workout_log.imageset/icons8-calendar-10-filled-50.png delete mode 100644 BodyweightFitness/Main/View/CalendarCell.swift rename BodyweightFitness/Resources/{TestRoutine.json => bodyweight_fitness_recommended_routine_unit_test.json} (100%) create mode 100644 BodyweightFitness/View/Views.swift create mode 100644 BodyweightFitness/View/WorkoutChartView.swift rename BodyweightFitness/{Controller => Workout}/RestTimerViewController.swift (100%) rename BodyweightFitness/{Controller => Workout}/TimedViewController.swift (99%) rename BodyweightFitness/{Controller => Workout}/WeightedViewController.swift (99%) delete mode 100644 BodyweightFitness/WorkoutLog/ProgressGeneralViewController.swift delete mode 100644 BodyweightFitness/WorkoutLog/ProgressPageViewController.swift create mode 100644 BodyweightFitness/WorkoutLog/WorkoutLogCategoryViewController.swift create mode 100644 BodyweightFitness/WorkoutLog/WorkoutLogFullReportViewController.swift create mode 100644 BodyweightFitness/WorkoutLog/WorkoutLogGeneralViewController.swift create mode 100644 BodyweightFitnessTests/Domain/DataEntriesCompanionSpec.swift create mode 100644 BodyweightFitnessTests/Domain/ListOfRepositoryExercisesCompanionSpec.swift create mode 100644 BodyweightFitnessTests/Domain/RepositoryExerciseCompanionSpec.swift create mode 100644 BodyweightFitnessTests/Domain/RepositoryRoutineCompanionSpec.swift create mode 100644 BodyweightFitnessTests/Domain/RepositorySetCompanionSpec.swift diff --git a/BodyweightFitness.xcodeproj/project.pbxproj b/BodyweightFitness.xcodeproj/project.pbxproj index 8d04c2d..e9d2712 100755 --- a/BodyweightFitness.xcodeproj/project.pbxproj +++ b/BodyweightFitness.xcodeproj/project.pbxproj @@ -7,11 +7,30 @@ objects = { /* Begin PBXBuildFile section */ + 46B020360D75A80752429FF4 /* WorkoutDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B0238107010BE20DA270F9 /* WorkoutDataEntry.swift */; }; + 46B0206C2FB198B77A55D58B /* RepositorySetCompanionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02E81E0E6B7E91B837D18 /* RepositorySetCompanionSpec.swift */; }; + 46B021554334D4A0E403E91D /* WorkoutLogFullReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B0200067A6AD55EEC8C78C /* WorkoutLogFullReportViewController.swift */; }; 46B021B6A56C2000CA08000B /* CalendarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02AEDB4B0B259C2EAE1E8 /* CalendarViewController.swift */; }; + 46B021FDBB6A7E1B7E98EDC6 /* RepositoryRoutineCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B020705508E9962E756BEF /* RepositoryRoutineCompanion.swift */; }; + 46B0228CAE19F330773C3B4D /* WorkoutChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B0265FEE50A960EC0335F9 /* WorkoutChartView.swift */; }; + 46B023C64AF59B2DAAC02813 /* CompletionRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B026D8FF35A60FEA72641A /* CompletionRate.swift */; }; + 46B0249136D6147956309E7B /* RepositoryExerciseCompanionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B024DF95FEB56EBBABCC5F /* RepositoryExerciseCompanionSpec.swift */; }; + 46B0261810EF8860661743BE /* WorkoutChartType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02B23C756AB46D0590C59 /* WorkoutChartType.swift */; }; + 46B02674689A34473A36826D /* RepositorySetCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B021BE7E895523B27CEC51 /* RepositorySetCompanion.swift */; }; + 46B026956DE6CC558370B9FE /* RepositoryRoutineCompanionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B0288C269FF355D6443614 /* RepositoryRoutineCompanionSpec.swift */; }; 46B0271B985965324463B90C /* RoutineStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02B2F18B13D956F1E2AD6 /* RoutineStream.swift */; }; 46B02780EEC08B16FB384A4F /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B020C26FAF7645B926FA26 /* CardView.swift */; }; + 46B027EB1551695226B1E7FB /* DataEntriesCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02031F8ECBCA46AF76185 /* DataEntriesCompanion.swift */; }; 46B02827BB8DE1BAFE67526A /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B022B7A7B5E9A8B85DEBA0 /* PersistenceManager.swift */; }; + 46B0290A46500C9466C686E6 /* DataEntriesCompanionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B027987C3B7C0FCAB8955C /* DataEntriesCompanionSpec.swift */; }; + 46B0298B0EDBEA87D2268270 /* ListOfRepositoryExercisesCompanionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02D377395D66E54A3A405 /* ListOfRepositoryExercisesCompanionSpec.swift */; }; + 46B029ACFEA5B810F06CFFA9 /* ListOfRepositoryExercisesCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B023135387177F75329BFC /* ListOfRepositoryExercisesCompanion.swift */; }; + 46B02D4A90A267975DE60D70 /* RepositoryExerciseCompanion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02DE6A0CBDB57EE450605 /* RepositoryExerciseCompanion.swift */; }; 46B02FDDA16C9D54DBC1A61D /* RepositoryStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B02F2362EA8A9111571B02 /* RepositoryStream.swift */; }; + 731A41EA1F991B1600C44922 /* WorkoutLogCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A41E91F991B1600C44922 /* WorkoutLogCategoryViewController.swift */; }; + 731A41EC1F991B3400C44922 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A41EB1F991B3400C44922 /* Views.swift */; }; + 731A41EE1F99300A00C44922 /* AbstractViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731A41ED1F99300A00C44922 /* AbstractViewController.swift */; }; + 7328DCBE1F8FF56700BD96CD /* WorkoutLogGeneralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7328DCBD1F8FF56700BD96CD /* WorkoutLogGeneralViewController.swift */; }; 7329B68C1D0D8476005F5F74 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7329B68B1D0D8476005F5F74 /* HomeViewController.swift */; }; 732BA0241C5AE75900C1CDB5 /* ProgressPageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 732BA0231C5AE75900C1CDB5 /* ProgressPageViewController.xib */; }; 732BA0261C5AE8EF00C1CDB5 /* ProgressCardCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 732BA0251C5AE8EF00C1CDB5 /* ProgressCardCell.xib */; }; @@ -20,7 +39,6 @@ 732BA02C1C5B76E500C1CDB5 /* ProgressSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 732BA02B1C5B76E500C1CDB5 /* ProgressSectionCell.swift */; }; 732BA02E1C5B7A8D00C1CDB5 /* ProgressGeneralViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 732BA02D1C5B7A8D00C1CDB5 /* ProgressGeneralViewController.xib */; }; 732BA03D1C5BC9D400C1CDB5 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 732BA03C1C5BC9D400C1CDB5 /* LaunchScreen.xib */; }; - 732BA0431C5EB9C300C1CDB5 /* CalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 732BA0421C5EB9C300C1CDB5 /* CalendarCell.swift */; }; 732BA0451C5EB9CE00C1CDB5 /* WorkoutLogCardCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 732BA0441C5EB9CE00C1CDB5 /* WorkoutLogCardCell.xib */; }; 732CDDEF1D84485D004C6607 /* bodyweight_fitness_foot_supported_l_sit.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 732CDDEA1D84485D004C6607 /* bodyweight_fitness_foot_supported_l_sit.mp4 */; }; 732CDDF01D84485D004C6607 /* bodyweight_fitness_l_sit.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 732CDDEB1D84485D004C6607 /* bodyweight_fitness_l_sit.mp4 */; }; @@ -56,7 +74,6 @@ 734B70051C4BF7C700CFB6BD /* RepositoryRoutine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734B70041C4BF7C700CFB6BD /* RepositoryRoutine.swift */; }; 734B70081C4BF7DD00CFB6BD /* RepositoryCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734B70071C4BF7DD00CFB6BD /* RepositoryCategory.swift */; }; 734B700B1C4BF7E900CFB6BD /* RepositorySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734B700A1C4BF7E900CFB6BD /* RepositorySection.swift */; }; - 7366D8DD1C4EEFD500819790 /* ProgressPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7366D8DC1C4EEFD500819790 /* ProgressPageViewController.swift */; }; 7366D8E11C51810600819790 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7366D8E01C51810600819790 /* Extensions.swift */; }; 7366D8EA1C52416C00819790 /* SupportDeveloperViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7366D8E91C52416C00819790 /* SupportDeveloperViewController.swift */; }; 7366D8ED1C52943D00819790 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7366D8EC1C52943D00819790 /* StoreKit.framework */; }; @@ -128,6 +145,7 @@ 738B96BD1D761192009A8625 /* molding_mobility_flexibility_routine.json in Resources */ = {isa = PBXBuildFile; fileRef = 738B96BC1D761192009A8625 /* molding_mobility_flexibility_routine.json */; }; 738B96CD1D763319009A8625 /* HomeBarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 738B96CC1D763319009A8625 /* HomeBarView.xib */; }; 738B96CF1D76337C009A8625 /* HomeBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738B96CE1D76337C009A8625 /* HomeBarView.swift */; }; + 738C24851FC7461A00036005 /* BodyweightFitnessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 738C24841FC7461A00036005 /* BodyweightFitnessTests.swift */; }; 73AA28EA1CABE69200DEED79 /* DashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73AA28E91CABE69200DEED79 /* DashboardViewController.swift */; }; 73AA28ED1CABE8F800DEED79 /* DashboardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 73AA28EC1CABE8F800DEED79 /* DashboardView.xib */; }; 73AA28EF1CB7ABB500DEED79 /* DashboardSectionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 73AA28EE1CB7ABB500DEED79 /* DashboardSectionCell.xib */; }; @@ -142,21 +160,20 @@ 73BADE191CC3E347001ACEDC /* TimedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BADE181CC3E347001ACEDC /* TimedViewController.swift */; }; 73BADE1B1CC3F956001ACEDC /* WeightedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BADE1A1CC3F956001ACEDC /* WeightedViewController.swift */; }; 73BADE1D1CC3F98D001ACEDC /* WeightedView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 73BADE1C1CC3F98D001ACEDC /* WeightedView.xib */; }; - 73BADE6E1CC78356001ACEDC /* BodyweightFitnessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BADE6D1CC78356001ACEDC /* BodyweightFitnessTests.swift */; }; - 73BADE7D1CC79648001ACEDC /* TestRoutine.json in Resources */ = {isa = PBXBuildFile; fileRef = 73BADE7C1CC79648001ACEDC /* TestRoutine.json */; }; + 73BADE7D1CC79648001ACEDC /* bodyweight_fitness_recommended_routine_unit_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 73BADE7C1CC79648001ACEDC /* bodyweight_fitness_recommended_routine_unit_test.json */; }; 73BF3BB51CBEB08B00E0258D /* LogWorkoutModalView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 73BF3BB41CBEB08B00E0258D /* LogWorkoutModalView.xib */; }; 73BF3BBB1CBEFB1E00E0258D /* WorkoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BF3BBA1CBEFB1E00E0258D /* WorkoutViewController.swift */; }; 73BF3BC11CBF05ED00E0258D /* SnackBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BF3BC01CBF05ED00E0258D /* SnackBar.swift */; }; 73BF63801F3CAAE2007F37C8 /* CellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73BF637F1F3CAAE2007F37C8 /* CellView.swift */; }; 73C328321F3C81D60076AF19 /* CAPSPageMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C328311F3C81D60076AF19 /* CAPSPageMenu.swift */; }; - 73C8E4AF1C5A1D30005E0248 /* ProgressGeneralViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73C8E4AE1C5A1D30005E0248 /* ProgressGeneralViewController.swift */; }; + 73CD7B321FC759ED00A7306D /* Pods_BodyweightFitness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 73CD7B331FC759ED00A7306D /* Pods_BodyweightFitness.framework */; }; 73D393271F8D4B7D00D9F4DD /* WorkoutLog.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 73D393261F8D4B7D00D9F4DD /* WorkoutLog.storyboard */; }; 73D393291F8D4BAE00D9F4DD /* WorkoutLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D393281F8D4BAE00D9F4DD /* WorkoutLogViewController.swift */; }; 73D508D61DA6C76500D98F20 /* molding_mobility_3_plane_neck_movement.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 73D508D51DA6C76500D98F20 /* molding_mobility_3_plane_neck_movement.mp4 */; }; 73E8F4551E21724F00D544A0 /* bodyweight_fitness_left_side_plank.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 73E8F4531E21724F00D544A0 /* bodyweight_fitness_left_side_plank.mp4 */; }; 73E8F4561E21724F00D544A0 /* bodyweight_fitness_right_side_plank.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 73E8F4541E21724F00D544A0 /* bodyweight_fitness_right_side_plank.mp4 */; }; 73FE1F5B1C4BF82A0047C97A /* RepositoryExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FE1F5A1C4BF82A0047C97A /* RepositoryExercise.swift */; }; - 78163125961ED33EEAA36EA0 /* Pods_Bodyweight_Fitness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AD935083C2986D423F02E75 /* Pods_Bodyweight_Fitness.framework */; }; + 7B377FF5F57E8F1E5374D256 /* Pods_BodyweightFitness_BodyweightFitnessTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1445A7C286C120C5BB49F5F7 /* Pods_BodyweightFitness_BodyweightFitnessTests.framework */; }; E0F5872E1B5272C400540104 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0F5872C1B5272C400540104 /* AppDelegate.swift */; }; E0F587311B5272E200540104 /* Routine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0F587301B5272E200540104 /* Routine.swift */; }; E0F587351B5272F400540104 /* ActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0F587331B5272F400540104 /* ActionView.swift */; }; @@ -168,7 +185,7 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 73BADE701CC78356001ACEDC /* PBXContainerItemProxy */ = { + 738C24871FC7461A00036005 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 2BD2CA7B1B458FFD00841AB7 /* Project object */; proxyType = 1; @@ -178,12 +195,35 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 0CAA43DD8E9A2A7D985EE7DE /* Pods-BodyweightFitness-BodyweightFitnessTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BodyweightFitness-BodyweightFitnessTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BodyweightFitness-BodyweightFitnessTests/Pods-BodyweightFitness-BodyweightFitnessTests.debug.xcconfig"; sourceTree = ""; }; + 1445A7C286C120C5BB49F5F7 /* Pods_BodyweightFitness_BodyweightFitnessTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BodyweightFitness_BodyweightFitnessTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2BD2CA831B458FFD00841AB7 /* Bodyweight Fitness.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bodyweight Fitness.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 46B0200067A6AD55EEC8C78C /* WorkoutLogFullReportViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkoutLogFullReportViewController.swift; sourceTree = ""; }; + 46B02031F8ECBCA46AF76185 /* DataEntriesCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataEntriesCompanion.swift; sourceTree = ""; }; + 46B020705508E9962E756BEF /* RepositoryRoutineCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryRoutineCompanion.swift; sourceTree = ""; }; 46B020C26FAF7645B926FA26 /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = ""; }; + 46B021BE7E895523B27CEC51 /* RepositorySetCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositorySetCompanion.swift; sourceTree = ""; }; 46B022B7A7B5E9A8B85DEBA0 /* PersistenceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = ""; }; + 46B023135387177F75329BFC /* ListOfRepositoryExercisesCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOfRepositoryExercisesCompanion.swift; sourceTree = ""; }; + 46B0238107010BE20DA270F9 /* WorkoutDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkoutDataEntry.swift; sourceTree = ""; }; + 46B024DF95FEB56EBBABCC5F /* RepositoryExerciseCompanionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryExerciseCompanionSpec.swift; sourceTree = ""; }; + 46B0265FEE50A960EC0335F9 /* WorkoutChartView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkoutChartView.swift; sourceTree = ""; }; + 46B026D8FF35A60FEA72641A /* CompletionRate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompletionRate.swift; sourceTree = ""; }; + 46B027987C3B7C0FCAB8955C /* DataEntriesCompanionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataEntriesCompanionSpec.swift; sourceTree = ""; }; + 46B0288C269FF355D6443614 /* RepositoryRoutineCompanionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryRoutineCompanionSpec.swift; sourceTree = ""; }; 46B02AEDB4B0B259C2EAE1E8 /* CalendarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarViewController.swift; sourceTree = ""; }; + 46B02B23C756AB46D0590C59 /* WorkoutChartType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkoutChartType.swift; sourceTree = ""; }; 46B02B2F18B13D956F1E2AD6 /* RoutineStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoutineStream.swift; sourceTree = ""; }; + 46B02D377395D66E54A3A405 /* ListOfRepositoryExercisesCompanionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOfRepositoryExercisesCompanionSpec.swift; sourceTree = ""; }; + 46B02DE6A0CBDB57EE450605 /* RepositoryExerciseCompanion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryExerciseCompanion.swift; sourceTree = ""; }; + 46B02E81E0E6B7E91B837D18 /* RepositorySetCompanionSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositorySetCompanionSpec.swift; sourceTree = ""; }; 46B02F2362EA8A9111571B02 /* RepositoryStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryStream.swift; sourceTree = ""; }; + 57E3C9E9DB8DAC7C6AEAF13D /* Pods-BodyweightFitness.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BodyweightFitness.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BodyweightFitness/Pods-BodyweightFitness.debug.xcconfig"; sourceTree = ""; }; + 628DF7CC1CBCA3E8CB0633B9 /* Pods-BodyweightFitness-BodyweightFitnessTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BodyweightFitness-BodyweightFitnessTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-BodyweightFitness-BodyweightFitnessTests/Pods-BodyweightFitness-BodyweightFitnessTests.release.xcconfig"; sourceTree = ""; }; + 731A41E91F991B1600C44922 /* WorkoutLogCategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutLogCategoryViewController.swift; sourceTree = ""; }; + 731A41EB1F991B3400C44922 /* Views.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = ""; }; + 731A41ED1F99300A00C44922 /* AbstractViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractViewController.swift; sourceTree = ""; }; + 7328DCBD1F8FF56700BD96CD /* WorkoutLogGeneralViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutLogGeneralViewController.swift; sourceTree = ""; }; 7329B68B1D0D8476005F5F74 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 732BA0231C5AE75900C1CDB5 /* ProgressPageViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProgressPageViewController.xib; sourceTree = ""; }; 732BA0251C5AE8EF00C1CDB5 /* ProgressCardCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProgressCardCell.xib; sourceTree = ""; }; @@ -192,7 +232,6 @@ 732BA02B1C5B76E500C1CDB5 /* ProgressSectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressSectionCell.swift; sourceTree = ""; }; 732BA02D1C5B7A8D00C1CDB5 /* ProgressGeneralViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProgressGeneralViewController.xib; sourceTree = ""; }; 732BA03C1C5BC9D400C1CDB5 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; - 732BA0421C5EB9C300C1CDB5 /* CalendarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalendarCell.swift; sourceTree = ""; }; 732BA0441C5EB9CE00C1CDB5 /* WorkoutLogCardCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WorkoutLogCardCell.xib; sourceTree = ""; }; 732CDDEA1D84485D004C6607 /* bodyweight_fitness_foot_supported_l_sit.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = bodyweight_fitness_foot_supported_l_sit.mp4; sourceTree = ""; }; 732CDDEB1D84485D004C6607 /* bodyweight_fitness_l_sit.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = bodyweight_fitness_l_sit.mp4; sourceTree = ""; }; @@ -228,7 +267,6 @@ 734B70041C4BF7C700CFB6BD /* RepositoryRoutine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryRoutine.swift; sourceTree = ""; }; 734B70071C4BF7DD00CFB6BD /* RepositoryCategory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryCategory.swift; sourceTree = ""; }; 734B700A1C4BF7E900CFB6BD /* RepositorySection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositorySection.swift; sourceTree = ""; }; - 7366D8DC1C4EEFD500819790 /* ProgressPageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressPageViewController.swift; sourceTree = ""; }; 7366D8E01C51810600819790 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 7366D8E91C52416C00819790 /* SupportDeveloperViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportDeveloperViewController.swift; sourceTree = ""; }; 7366D8EC1C52943D00819790 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; @@ -300,6 +338,9 @@ 738B96BC1D761192009A8625 /* molding_mobility_flexibility_routine.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = molding_mobility_flexibility_routine.json; sourceTree = ""; }; 738B96CC1D763319009A8625 /* HomeBarView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HomeBarView.xib; sourceTree = ""; }; 738B96CE1D76337C009A8625 /* HomeBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeBarView.swift; sourceTree = ""; }; + 738C24821FC7461A00036005 /* BodyweightFitnessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BodyweightFitnessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 738C24841FC7461A00036005 /* BodyweightFitnessTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyweightFitnessTests.swift; sourceTree = ""; }; + 738C24861FC7461A00036005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73AA28E91CABE69200DEED79 /* DashboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashboardViewController.swift; sourceTree = ""; }; 73AA28EC1CABE8F800DEED79 /* DashboardView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DashboardView.xib; sourceTree = ""; }; 73AA28EE1CB7ABB500DEED79 /* DashboardSectionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DashboardSectionCell.xib; sourceTree = ""; }; @@ -314,25 +355,20 @@ 73BADE181CC3E347001ACEDC /* TimedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimedViewController.swift; sourceTree = ""; }; 73BADE1A1CC3F956001ACEDC /* WeightedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeightedViewController.swift; sourceTree = ""; }; 73BADE1C1CC3F98D001ACEDC /* WeightedView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WeightedView.xib; sourceTree = ""; }; - 73BADE6B1CC78356001ACEDC /* BodyweightFitnessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BodyweightFitnessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 73BADE6D1CC78356001ACEDC /* BodyweightFitnessTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyweightFitnessTests.swift; sourceTree = ""; }; - 73BADE6F1CC78356001ACEDC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 73BADE7C1CC79648001ACEDC /* TestRoutine.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TestRoutine.json; sourceTree = ""; }; + 73BADE7C1CC79648001ACEDC /* bodyweight_fitness_recommended_routine_unit_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = bodyweight_fitness_recommended_routine_unit_test.json; sourceTree = ""; }; 73BF3BB41CBEB08B00E0258D /* LogWorkoutModalView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LogWorkoutModalView.xib; sourceTree = ""; }; 73BF3BBA1CBEFB1E00E0258D /* WorkoutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkoutViewController.swift; sourceTree = ""; }; 73BF3BC01CBF05ED00E0258D /* SnackBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnackBar.swift; sourceTree = ""; }; 73BF637F1F3CAAE2007F37C8 /* CellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CellView.swift; sourceTree = ""; }; 73C328311F3C81D60076AF19 /* CAPSPageMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAPSPageMenu.swift; sourceTree = ""; }; - 73C8E4AE1C5A1D30005E0248 /* ProgressGeneralViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressGeneralViewController.swift; sourceTree = ""; }; + 73CD7B331FC759ED00A7306D /* Pods_BodyweightFitness.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Pods_BodyweightFitness.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 73D393261F8D4B7D00D9F4DD /* WorkoutLog.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WorkoutLog.storyboard; sourceTree = ""; }; 73D393281F8D4BAE00D9F4DD /* WorkoutLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutLogViewController.swift; sourceTree = ""; }; 73D508D51DA6C76500D98F20 /* molding_mobility_3_plane_neck_movement.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = molding_mobility_3_plane_neck_movement.mp4; sourceTree = ""; }; 73E8F4531E21724F00D544A0 /* bodyweight_fitness_left_side_plank.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = bodyweight_fitness_left_side_plank.mp4; sourceTree = ""; }; 73E8F4541E21724F00D544A0 /* bodyweight_fitness_right_side_plank.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = bodyweight_fitness_right_side_plank.mp4; sourceTree = ""; }; 73FE1F5A1C4BF82A0047C97A /* RepositoryExercise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepositoryExercise.swift; sourceTree = ""; }; - 8AD935083C2986D423F02E75 /* Pods_Bodyweight_Fitness.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Bodyweight_Fitness.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - A6439A24ABCBB97852E0D5D1 /* Pods-Bodyweight Fitness.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bodyweight Fitness.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Bodyweight Fitness/Pods-Bodyweight Fitness.debug.xcconfig"; sourceTree = ""; }; - BA7457843B13F20696FA3BA8 /* Pods-Bodyweight Fitness.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Bodyweight Fitness.release.xcconfig"; path = "Pods/Target Support Files/Pods-Bodyweight Fitness/Pods-Bodyweight Fitness.release.xcconfig"; sourceTree = ""; }; + BAEC71510B6C5949149A65E0 /* Pods-BodyweightFitness.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BodyweightFitness.release.xcconfig"; path = "Pods/Target Support Files/Pods-BodyweightFitness/Pods-BodyweightFitness.release.xcconfig"; sourceTree = ""; }; E0F5872C1B5272C400540104 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E0F587301B5272E200540104 /* Routine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Routine.swift; sourceTree = ""; }; E0F587331B5272F400540104 /* ActionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionView.swift; sourceTree = ""; }; @@ -340,7 +376,7 @@ E0F587441B52730C00540104 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TimePickerModalView.xib; sourceTree = ""; }; E0F587481B52731C00540104 /* finished.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = finished.mp3; sourceTree = ""; }; E0F587491B52731C00540104 /* bodyweight_fitness_recommended_routine.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = bodyweight_fitness_recommended_routine.json; sourceTree = ""; }; - E0F587B11B52734000540104 /* Bodyweight Fitness.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Bodyweight Fitness.plist"; sourceTree = ""; }; + E0F587B11B52734000540104 /* BodyweightFitness.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = BodyweightFitness.plist; sourceTree = ""; }; E0F587B31B52734900540104 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; /* End PBXFileReference section */ @@ -349,15 +385,16 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 73CD7B321FC759ED00A7306D /* Pods_BodyweightFitness.framework in Frameworks */, 7366D8ED1C52943D00819790 /* StoreKit.framework in Frameworks */, - 78163125961ED33EEAA36EA0 /* Pods_Bodyweight_Fitness.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 73BADE681CC78356001ACEDC /* Frameworks */ = { + 738C247F1FC7461A00036005 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7B377FF5F57E8F1E5374D256 /* Pods_BodyweightFitness_BodyweightFitnessTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -368,7 +405,7 @@ isa = PBXGroup; children = ( 2BD2CA851B458FFD00841AB7 /* BodyweightFitness */, - 73BADE6C1CC78356001ACEDC /* BodyweightFitnessTests */, + 738C24831FC7461A00036005 /* BodyweightFitnessTests */, E00242FB1B4F1CFD0003A230 /* Frameworks */, 2BD2CA841B458FFD00841AB7 /* Products */, F15ECA1986EEE14D78F646EB /* Pods */, @@ -379,7 +416,7 @@ isa = PBXGroup; children = ( 2BD2CA831B458FFD00841AB7 /* Bodyweight Fitness.app */, - 73BADE6B1CC78356001ACEDC /* BodyweightFitnessTests.xctest */, + 738C24821FC7461A00036005 /* BodyweightFitnessTests.xctest */, ); name = Products; sourceTree = ""; @@ -387,10 +424,11 @@ 2BD2CA851B458FFD00841AB7 /* BodyweightFitness */ = { isa = PBXGroup; children = ( - E0F587B11B52734000540104 /* Bodyweight Fitness.plist */, + E0F587B11B52734000540104 /* BodyweightFitness.plist */, E0F5872C1B5272C400540104 /* AppDelegate.swift */, 7366D8E01C51810600819790 /* Extensions.swift */, 73737FF61F8D064000C616B8 /* Date+Extensions.swift */, + 731A41ED1F99300A00C44922 /* AbstractViewController.swift */, 7347DC2E1F8D3CD8001E1BE5 /* Main */, 7347DC2F1F8D3CF1001E1BE5 /* Workout */, 73D393251F8D474E00D9F4DD /* WorkoutLog */, @@ -404,6 +442,18 @@ path = BodyweightFitness; sourceTree = ""; }; + 46B02F831887099043F9C107 /* Domain */ = { + isa = PBXGroup; + children = ( + 46B024DF95FEB56EBBABCC5F /* RepositoryExerciseCompanionSpec.swift */, + 46B02D377395D66E54A3A405 /* ListOfRepositoryExercisesCompanionSpec.swift */, + 46B0288C269FF355D6443614 /* RepositoryRoutineCompanionSpec.swift */, + 46B027987C3B7C0FCAB8955C /* DataEntriesCompanionSpec.swift */, + 46B02E81E0E6B7E91B837D18 /* RepositorySetCompanionSpec.swift */, + ); + path = Domain; + sourceTree = ""; + }; 734132E61E130FC300BAEFD2 /* Starting Stretching - Flexibility Routine */ = { isa = PBXGroup; children = ( @@ -433,7 +483,6 @@ 7347DC2E1F8D3CD8001E1BE5 /* Main */ = { isa = PBXGroup; children = ( - 73D3932A1F8D4E4000D9F4DD /* View */, 7329B68B1D0D8476005F5F74 /* HomeViewController.swift */, 46B02AEDB4B0B259C2EAE1E8 /* CalendarViewController.swift */, 7366D8E91C52416C00819790 /* SupportDeveloperViewController.swift */, @@ -446,6 +495,9 @@ 7347DC2F1F8D3CF1001E1BE5 /* Workout */ = { isa = PBXGroup; children = ( + 73374A8D1E167A6300EBC47B /* RestTimerViewController.swift */, + 73BADE181CC3E347001ACEDC /* TimedViewController.swift */, + 73BADE1A1CC3F956001ACEDC /* WeightedViewController.swift */, 73BF3BBA1CBEFB1E00E0258D /* WorkoutViewController.swift */, 7347DC2C1F8D3243001E1BE5 /* Workout.storyboard */, ); @@ -560,6 +612,16 @@ name = "Molding Mobility - Flexibility Routine"; sourceTree = ""; }; + 738C24831FC7461A00036005 /* BodyweightFitnessTests */ = { + isa = PBXGroup; + children = ( + 738C24841FC7461A00036005 /* BodyweightFitnessTests.swift */, + 738C24861FC7461A00036005 /* Info.plist */, + 46B02F831887099043F9C107 /* Domain */, + ); + path = BodyweightFitnessTests; + sourceTree = ""; + }; 73AA28EB1CABE8E000DEED79 /* UI */ = { isa = PBXGroup; children = ( @@ -583,22 +645,10 @@ 73BF3BB71CBEF0CC00E0258D /* Cell */, 73BF3BB61CBEB0C100E0258D /* Modal */, 73AA28E91CABE69200DEED79 /* DashboardViewController.swift */, - 73374A8D1E167A6300EBC47B /* RestTimerViewController.swift */, - 73BADE181CC3E347001ACEDC /* TimedViewController.swift */, - 73BADE1A1CC3F956001ACEDC /* WeightedViewController.swift */, ); path = Controller; sourceTree = ""; }; - 73BADE6C1CC78356001ACEDC /* BodyweightFitnessTests */ = { - isa = PBXGroup; - children = ( - 73BADE6D1CC78356001ACEDC /* BodyweightFitnessTests.swift */, - 73BADE6F1CC78356001ACEDC /* Info.plist */, - ); - path = BodyweightFitnessTests; - sourceTree = ""; - }; 73BF3BB61CBEB0C100E0258D /* Modal */ = { isa = PBXGroup; children = ( @@ -625,27 +675,21 @@ 73D393251F8D474E00D9F4DD /* WorkoutLog */ = { isa = PBXGroup; children = ( - 73C8E4AE1C5A1D30005E0248 /* ProgressGeneralViewController.swift */, - 7366D8DC1C4EEFD500819790 /* ProgressPageViewController.swift */, 73D393281F8D4BAE00D9F4DD /* WorkoutLogViewController.swift */, + 7328DCBD1F8FF56700BD96CD /* WorkoutLogGeneralViewController.swift */, + 731A41E91F991B1600C44922 /* WorkoutLogCategoryViewController.swift */, 73D393261F8D4B7D00D9F4DD /* WorkoutLog.storyboard */, + 46B0200067A6AD55EEC8C78C /* WorkoutLogFullReportViewController.swift */, ); path = WorkoutLog; sourceTree = ""; }; - 73D3932A1F8D4E4000D9F4DD /* View */ = { - isa = PBXGroup; - children = ( - 732BA0421C5EB9C300C1CDB5 /* CalendarCell.swift */, - ); - path = View; - sourceTree = ""; - }; E00242FB1B4F1CFD0003A230 /* Frameworks */ = { isa = PBXGroup; children = ( + 73CD7B331FC759ED00A7306D /* Pods_BodyweightFitness.framework */, + 1445A7C286C120C5BB49F5F7 /* Pods_BodyweightFitness_BodyweightFitnessTests.framework */, 7366D8EC1C52943D00819790 /* StoreKit.framework */, - 8AD935083C2986D423F02E75 /* Pods_Bodyweight_Fitness.framework */, 73C328311F3C81D60076AF19 /* CAPSPageMenu.swift */, ); name = Frameworks; @@ -658,6 +702,8 @@ 46B020C26FAF7645B926FA26 /* CardView.swift */, 73BF3BC01CBF05ED00E0258D /* SnackBar.swift */, 738B96CE1D76337C009A8625 /* HomeBarView.swift */, + 731A41EB1F991B3400C44922 /* Views.swift */, + 46B0265FEE50A960EC0335F9 /* WorkoutChartView.swift */, ); path = View; sourceTree = ""; @@ -674,6 +720,14 @@ 734B700A1C4BF7E900CFB6BD /* RepositorySection.swift */, 734B6FFE1C4BF46400CFB6BD /* RepositorySet.swift */, E0F587301B5272E200540104 /* Routine.swift */, + 46B026D8FF35A60FEA72641A /* CompletionRate.swift */, + 46B02DE6A0CBDB57EE450605 /* RepositoryExerciseCompanion.swift */, + 46B020705508E9962E756BEF /* RepositoryRoutineCompanion.swift */, + 46B023135387177F75329BFC /* ListOfRepositoryExercisesCompanion.swift */, + 46B02031F8ECBCA46AF76185 /* DataEntriesCompanion.swift */, + 46B0238107010BE20DA270F9 /* WorkoutDataEntry.swift */, + 46B02B23C756AB46D0590C59 /* WorkoutChartType.swift */, + 46B021BE7E895523B27CEC51 /* RepositorySetCompanion.swift */, ); path = Domain; sourceTree = ""; @@ -687,7 +741,7 @@ E0F587491B52731C00540104 /* bodyweight_fitness_recommended_routine.json */, 734132E41E130FAE00BAEFD2 /* starting_stretching_flexibility_routine.json */, 738B96BC1D761192009A8625 /* molding_mobility_flexibility_routine.json */, - 73BADE7C1CC79648001ACEDC /* TestRoutine.json */, + 73BADE7C1CC79648001ACEDC /* bodyweight_fitness_recommended_routine_unit_test.json */, E0F587481B52731C00540104 /* finished.mp3 */, ); path = Resources; @@ -696,8 +750,10 @@ F15ECA1986EEE14D78F646EB /* Pods */ = { isa = PBXGroup; children = ( - A6439A24ABCBB97852E0D5D1 /* Pods-Bodyweight Fitness.debug.xcconfig */, - BA7457843B13F20696FA3BA8 /* Pods-Bodyweight Fitness.release.xcconfig */, + 57E3C9E9DB8DAC7C6AEAF13D /* Pods-BodyweightFitness.debug.xcconfig */, + BAEC71510B6C5949149A65E0 /* Pods-BodyweightFitness.release.xcconfig */, + 0CAA43DD8E9A2A7D985EE7DE /* Pods-BodyweightFitness-BodyweightFitnessTests.debug.xcconfig */, + 628DF7CC1CBCA3E8CB0633B9 /* Pods-BodyweightFitness-BodyweightFitnessTests.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -705,9 +761,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 2BD2CA821B458FFD00841AB7 /* Bodyweight Fitness */ = { + 2BD2CA821B458FFD00841AB7 /* BodyweightFitness */ = { isa = PBXNativeTarget; - buildConfigurationList = 2BD2CAA51B458FFE00841AB7 /* Build configuration list for PBXNativeTarget "Bodyweight Fitness" */; + buildConfigurationList = 2BD2CAA51B458FFE00841AB7 /* Build configuration list for PBXNativeTarget "BodyweightFitness" */; buildPhases = ( 3C4F1FC485B7DDDCB3D34864 /* [CP] Check Pods Manifest.lock */, 2BD2CA7F1B458FFD00841AB7 /* Sources */, @@ -720,27 +776,30 @@ ); dependencies = ( ); - name = "Bodyweight Fitness"; + name = BodyweightFitness; productName = TabsTest; productReference = 2BD2CA831B458FFD00841AB7 /* Bodyweight Fitness.app */; productType = "com.apple.product-type.application"; }; - 73BADE6A1CC78356001ACEDC /* BodyweightFitnessTests */ = { + 738C24811FC7461A00036005 /* BodyweightFitnessTests */ = { isa = PBXNativeTarget; - buildConfigurationList = 73BADE721CC78356001ACEDC /* Build configuration list for PBXNativeTarget "BodyweightFitnessTests" */; + buildConfigurationList = 738C24891FC7461A00036005 /* Build configuration list for PBXNativeTarget "BodyweightFitnessTests" */; buildPhases = ( - 73BADE671CC78356001ACEDC /* Sources */, - 73BADE681CC78356001ACEDC /* Frameworks */, - 73BADE691CC78356001ACEDC /* Resources */, + B37D0496848DCB1BD3E36A26 /* [CP] Check Pods Manifest.lock */, + 738C247E1FC7461A00036005 /* Sources */, + 738C247F1FC7461A00036005 /* Frameworks */, + 738C24801FC7461A00036005 /* Resources */, + 860343D414AAEC2E26D09818 /* [CP] Embed Pods Frameworks */, + 22C8D8C14C320DE843C6A896 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( - 73BADE711CC78356001ACEDC /* PBXTargetDependency */, + 738C24881FC7461A00036005 /* PBXTargetDependency */, ); name = BodyweightFitnessTests; productName = BodyweightFitnessTests; - productReference = 73BADE6B1CC78356001ACEDC /* BodyweightFitnessTests.xctest */; + productReference = 738C24821FC7461A00036005 /* BodyweightFitnessTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -749,7 +808,7 @@ 2BD2CA7B1B458FFD00841AB7 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; + LastSwiftUpdateCheck = 0900; LastUpgradeCheck = 0900; ORGANIZATIONNAME = "Damian Mazurkiewicz"; TargetAttributes = { @@ -764,9 +823,9 @@ }; }; }; - 73BADE6A1CC78356001ACEDC = { - CreatedOnToolsVersion = 7.3; - LastSwiftMigration = 0830; + 738C24811FC7461A00036005 = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; TestTargetID = 2BD2CA821B458FFD00841AB7; }; }; @@ -784,8 +843,8 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 2BD2CA821B458FFD00841AB7 /* Bodyweight Fitness */, - 73BADE6A1CC78356001ACEDC /* BodyweightFitnessTests */, + 2BD2CA821B458FFD00841AB7 /* BodyweightFitness */, + 738C24811FC7461A00036005 /* BodyweightFitnessTests */, ); }; /* End PBXProject section */ @@ -892,7 +951,7 @@ 738B96A51D760F66009A8625 /* molding_mobility_circular_shrugs.mp4 in Resources */, 738B96841D760F66009A8625 /* bodyweight_fitness_parallel_bar_dips.mp4 in Resources */, 732CDDEF1D84485D004C6607 /* bodyweight_fitness_foot_supported_l_sit.mp4 in Resources */, - 73BADE7D1CC79648001ACEDC /* TestRoutine.json in Resources */, + 73BADE7D1CC79648001ACEDC /* bodyweight_fitness_recommended_routine_unit_test.json in Resources */, 734133001E130FE900BAEFD2 /* starting_stretching_shoulder_extension_beginner.mp4 in Resources */, 732BA03D1C5BC9D400C1CDB5 /* LaunchScreen.xib in Resources */, 738B96881D760F66009A8625 /* bodyweight_fitness_pullup.mp4 in Resources */, @@ -914,7 +973,7 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 73BADE691CC78356001ACEDC /* Resources */ = { + 738C24801FC7461A00036005 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -924,6 +983,21 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 22C8D8C14C320DE843C6A896 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness-BodyweightFitnessTests/Pods-BodyweightFitness-BodyweightFitnessTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 2E14369486F78F9BAF8C8A2F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -936,7 +1010,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Bodyweight Fitness/Pods-Bodyweight Fitness-resources.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness/Pods-BodyweightFitness-resources.sh\"\n"; showEnvVarsInLog = 0; }; 3C4F1FC485B7DDDCB3D34864 /* [CP] Check Pods Manifest.lock */ = { @@ -950,7 +1024,63 @@ ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Bodyweight Fitness-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-BodyweightFitness-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 860343D414AAEC2E26D09818 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness-BodyweightFitnessTests/Pods-BodyweightFitness-BodyweightFitnessTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", + "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", + "${BUILT_PRODUCTS_DIR}/JTAppleCalendar/JTAppleCalendar.framework", + "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", + "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", + "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", + "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", + "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", + "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", + "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JTAppleCalendar.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness-BodyweightFitnessTests/Pods-BodyweightFitness-BodyweightFitnessTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B37D0496848DCB1BD3E36A26 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-BodyweightFitness-BodyweightFitnessTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -963,28 +1093,32 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Bodyweight Fitness/Pods-Bodyweight Fitness-frameworks.sh", + "${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness/Pods-BodyweightFitness-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", "${BUILT_PRODUCTS_DIR}/JTAppleCalendar/JTAppleCalendar.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/JTAppleCalendar.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Bodyweight Fitness/Pods-Bodyweight Fitness-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-BodyweightFitness/Pods-BodyweightFitness-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -998,20 +1132,21 @@ E0F587351B5272F400540104 /* ActionView.swift in Sources */, 734B70081C4BF7DD00CFB6BD /* RepositoryCategory.swift in Sources */, 7366D8E11C51810600819790 /* Extensions.swift in Sources */, + 7328DCBE1F8FF56700BD96CD /* WorkoutLogGeneralViewController.swift in Sources */, + 731A41EC1F991B3400C44922 /* Views.swift in Sources */, 732BA02C1C5B76E500C1CDB5 /* ProgressSectionCell.swift in Sources */, E0F5873D1B52730000540104 /* TimePickerController.swift in Sources */, - 7366D8DD1C4EEFD500819790 /* ProgressPageViewController.swift in Sources */, 73AA28FD1CB7ACF500DEED79 /* DashboardDoubleItemCell.swift in Sources */, 734B6FFF1C4BF46400CFB6BD /* RepositorySet.swift in Sources */, 73AA28F31CB7ACC100DEED79 /* DashboardCategoryCell.swift in Sources */, E0F587311B5272E200540104 /* Routine.swift in Sources */, 7366D8EA1C52416C00819790 /* SupportDeveloperViewController.swift in Sources */, - 732BA0431C5EB9C300C1CDB5 /* CalendarCell.swift in Sources */, 73BADE1B1CC3F956001ACEDC /* WeightedViewController.swift in Sources */, 73BF3BBB1CBEFB1E00E0258D /* WorkoutViewController.swift in Sources */, 732BA02A1C5B76DD00C1CDB5 /* ProgressCardCell.swift in Sources */, E0F5872E1B5272C400540104 /* AppDelegate.swift in Sources */, 73AA28FB1CB7ACEB00DEED79 /* DashboardSingleItemCell.swift in Sources */, + 731A41EE1F99300A00C44922 /* AbstractViewController.swift in Sources */, 73AA28EA1CABE69200DEED79 /* DashboardViewController.swift in Sources */, 73D393291F8D4BAE00D9F4DD /* WorkoutLogViewController.swift in Sources */, 7347DC2B1F8D2091001E1BE5 /* SettingsViewController.swift in Sources */, @@ -1021,7 +1156,6 @@ 7329B68C1D0D8476005F5F74 /* HomeViewController.swift in Sources */, 73FE1F5B1C4BF82A0047C97A /* RepositoryExercise.swift in Sources */, 73BF3BC11CBF05ED00E0258D /* SnackBar.swift in Sources */, - 73C8E4AF1C5A1D30005E0248 /* ProgressGeneralViewController.swift in Sources */, 73BADE191CC3E347001ACEDC /* TimedViewController.swift in Sources */, 734B6F3F1C4AE7E900CFB6BD /* LogWorkoutController.swift in Sources */, 46B02780EEC08B16FB384A4F /* CardView.swift in Sources */, @@ -1029,28 +1163,44 @@ 73737FF71F8D064000C616B8 /* Date+Extensions.swift in Sources */, 738B96CF1D76337C009A8625 /* HomeBarView.swift in Sources */, 46B02827BB8DE1BAFE67526A /* PersistenceManager.swift in Sources */, + 731A41EA1F991B1600C44922 /* WorkoutLogCategoryViewController.swift in Sources */, 46B02FDDA16C9D54DBC1A61D /* RepositoryStream.swift in Sources */, 46B0271B985965324463B90C /* RoutineStream.swift in Sources */, 73C328321F3C81D60076AF19 /* CAPSPageMenu.swift in Sources */, 73BF63801F3CAAE2007F37C8 /* CellView.swift in Sources */, + 46B023C64AF59B2DAAC02813 /* CompletionRate.swift in Sources */, + 46B02D4A90A267975DE60D70 /* RepositoryExerciseCompanion.swift in Sources */, + 46B021FDBB6A7E1B7E98EDC6 /* RepositoryRoutineCompanion.swift in Sources */, + 46B029ACFEA5B810F06CFFA9 /* ListOfRepositoryExercisesCompanion.swift in Sources */, + 46B027EB1551695226B1E7FB /* DataEntriesCompanion.swift in Sources */, + 46B020360D75A80752429FF4 /* WorkoutDataEntry.swift in Sources */, + 46B0261810EF8860661743BE /* WorkoutChartType.swift in Sources */, + 46B0228CAE19F330773C3B4D /* WorkoutChartView.swift in Sources */, + 46B021554334D4A0E403E91D /* WorkoutLogFullReportViewController.swift in Sources */, + 46B02674689A34473A36826D /* RepositorySetCompanion.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 73BADE671CC78356001ACEDC /* Sources */ = { + 738C247E1FC7461A00036005 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 73BADE6E1CC78356001ACEDC /* BodyweightFitnessTests.swift in Sources */, + 738C24851FC7461A00036005 /* BodyweightFitnessTests.swift in Sources */, + 46B0249136D6147956309E7B /* RepositoryExerciseCompanionSpec.swift in Sources */, + 46B0298B0EDBEA87D2268270 /* ListOfRepositoryExercisesCompanionSpec.swift in Sources */, + 46B026956DE6CC558370B9FE /* RepositoryRoutineCompanionSpec.swift in Sources */, + 46B0290A46500C9466C686E6 /* DataEntriesCompanionSpec.swift in Sources */, + 46B0206C2FB198B77A55D58B /* RepositorySetCompanionSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 73BADE711CC78356001ACEDC /* PBXTargetDependency */ = { + 738C24881FC7461A00036005 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 2BD2CA821B458FFD00841AB7 /* Bodyweight Fitness */; - targetProxy = 73BADE701CC78356001ACEDC /* PBXContainerItemProxy */; + target = 2BD2CA821B458FFD00841AB7 /* BodyweightFitness */; + targetProxy = 738C24871FC7461A00036005 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -1177,7 +1327,7 @@ }; 2BD2CAA61B458FFE00841AB7 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A6439A24ABCBB97852E0D5D1 /* Pods-Bodyweight Fitness.debug.xcconfig */; + baseConfigurationReference = 57E3C9E9DB8DAC7C6AEAF13D /* Pods-BodyweightFitness.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -1189,7 +1339,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - INFOPLIST_FILE = "$(SRCROOT)/BodyweightFitness/Bodyweight Fitness.plist"; + INFOPLIST_FILE = "$(SRCROOT)/BodyweightFitness/BodyweightFitness.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.mazur.$(PRODUCT_NAME:rfc1034identifier)"; @@ -1206,7 +1356,7 @@ }; 2BD2CAA71B458FFE00841AB7 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BA7457843B13F20696FA3BA8 /* Pods-Bodyweight Fitness.release.xcconfig */; + baseConfigurationReference = BAEC71510B6C5949149A65E0 /* Pods-BodyweightFitness.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -1218,7 +1368,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - INFOPLIST_FILE = "$(SRCROOT)/BodyweightFitness/Bodyweight Fitness.plist"; + INFOPLIST_FILE = "$(SRCROOT)/BodyweightFitness/BodyweightFitness.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "io.mazur.$(PRODUCT_NAME:rfc1034identifier)"; @@ -1232,41 +1382,53 @@ }; name = Release; }; - 73BADE731CC78356001ACEDC /* Debug */ = { + 738C248A1FC7461A00036005 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0CAA43DD8E9A2A7D985EE7DE /* Pods-BodyweightFitness-BodyweightFitnessTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; - FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/Carthage/Build/iOS"; - GCC_NO_COMMON_BLOCKS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = BodyweightFitnessTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.mazur.BodyweightFitnessTests; PRODUCT_NAME = "$(TARGET_NAME)"; - "SWIFT_INCLUDE_PATHS[arch=*]" = "$(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Bodyweight Fitness.app/Bodyweight Fitness"; }; name = Debug; }; - 73BADE741CC78356001ACEDC /* Release */ = { + 738C248B1FC7461A00036005 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 628DF7CC1CBCA3E8CB0633B9 /* Pods-BodyweightFitness-BodyweightFitnessTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/Carthage/Build/iOS"; - GCC_NO_COMMON_BLOCKS = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = BodyweightFitnessTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.3; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = io.mazur.BodyweightFitnessTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Bodyweight Fitness.app/Bodyweight Fitness"; }; name = Release; @@ -1283,7 +1445,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 2BD2CAA51B458FFE00841AB7 /* Build configuration list for PBXNativeTarget "Bodyweight Fitness" */ = { + 2BD2CAA51B458FFE00841AB7 /* Build configuration list for PBXNativeTarget "BodyweightFitness" */ = { isa = XCConfigurationList; buildConfigurations = ( 2BD2CAA61B458FFE00841AB7 /* Debug */, @@ -1292,11 +1454,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 73BADE721CC78356001ACEDC /* Build configuration list for PBXNativeTarget "BodyweightFitnessTests" */ = { + 738C24891FC7461A00036005 /* Build configuration list for PBXNativeTarget "BodyweightFitnessTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 73BADE731CC78356001ACEDC /* Debug */, - 73BADE741CC78356001ACEDC /* Release */, + 738C248A1FC7461A00036005 /* Debug */, + 738C248B1FC7461A00036005 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/BodyweightFitness/AbstractViewController.swift b/BodyweightFitness/AbstractViewController.swift new file mode 100644 index 0000000..31f9dbc --- /dev/null +++ b/BodyweightFitness/AbstractViewController.swift @@ -0,0 +1,76 @@ +import SnapKit + +class AbstractViewController: UIViewController { + let scrollView = UIScrollView() + let contentView = UIView() + let contentStackView = UIStackView() + + override func viewDidLoad() { + super.viewDidLoad() + + self.initializeScrollView(attachToView: mainView()) + } + + func mainView() -> UIView { + return self.view + } + + func initializeScrollView(attachToView: UIView) { + attachToView.backgroundColor = UIColor(red:0.96, green:0.96, blue:0.97, alpha:1.00) + attachToView.addSubview(self.scrollView) + + self.scrollView.snp.makeConstraints { (make) -> Void in + make.edges.equalTo(attachToView) + } + + self.scrollView.addSubview(self.contentView) + self.contentView.snp.makeConstraints { (make) -> Void in + make.top.bottom.equalTo(self.scrollView) + make.left.right.equalTo(attachToView) + } + + self.createBackgroundView() + + self.contentStackView.axis = .vertical + self.contentStackView.distribution = .fill + self.contentStackView.alignment = .fill + self.contentStackView.spacing = 16 + self.contentStackView.translatesAutoresizingMaskIntoConstraints = false + self.contentView.addSubview(self.contentStackView) + + self.contentStackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(contentView).offset(8) + make.left.equalTo(contentView).offset(8) + make.right.equalTo(contentView).offset(-8) + make.bottom.equalTo(contentView).offset(-8) + } + } + + func initializeContent() { + self.removeAllViews() + } + + func createBackgroundView(height: Int = 50) { + let view = UIView() + view.backgroundColor = UIColor.primary() + + self.contentView.addSubview(view) + + view.snp.makeConstraints { (make) -> Void in + make.top.equalTo(contentView) + make.left.equalTo(contentView) + make.right.equalTo(contentView) + make.height.equalTo(height) + } + } + + func addView(_ view: UIView) { + self.contentStackView.addArrangedSubview(view) + } + + func removeAllViews() { + for view in self.contentStackView.subviews { + view.removeFromSuperview() + } + } +} diff --git a/BodyweightFitness/Bodyweight Fitness.plist b/BodyweightFitness/BodyweightFitness.plist similarity index 100% rename from BodyweightFitness/Bodyweight Fitness.plist rename to BodyweightFitness/BodyweightFitness.plist diff --git a/BodyweightFitness/Controller/Cell/ProgressCardCell.swift b/BodyweightFitness/Controller/Cell/ProgressCardCell.swift index 648fbfa..76b51ae 100644 --- a/BodyweightFitness/Controller/Cell/ProgressCardCell.swift +++ b/BodyweightFitness/Controller/Cell/ProgressCardCell.swift @@ -20,7 +20,7 @@ class ProgressCardCell: UITableViewCell { let logWorkoutController = LogWorkoutController() logWorkoutController.parentController = self.parentController - logWorkoutController.setRepositoryRoutine(current!, repositoryRoutine: RepositoryStream.sharedInstance.getRepositoryRoutineForToday()) + logWorkoutController.setRepositoryRoutine(repositoryExercise: current!, repositoryRoutine: RepositoryStream.sharedInstance.getRepositoryRoutineForToday()) logWorkoutController.modalTransitionStyle = .coverVertical logWorkoutController.modalPresentationStyle = .custom diff --git a/BodyweightFitness/Controller/Modal/LogWorkoutController.swift b/BodyweightFitness/Controller/Modal/LogWorkoutController.swift index 5aa38f0..bdbf53f 100644 --- a/BodyweightFitness/Controller/Modal/LogWorkoutController.swift +++ b/BodyweightFitness/Controller/Modal/LogWorkoutController.swift @@ -1,4 +1,5 @@ import UIKit +import RealmSwift class LogWorkoutController: UIViewController { var exercise: RepositoryExercise? @@ -42,9 +43,9 @@ class LogWorkoutController: UIViewController { var setView: SetView? var set: RepositorySet? - - let realm = RepositoryStream.sharedInstance.getRealm() - + + let realm = try! Realm() + var parentController: UIViewController? func increaseRepsButtonDown(_ sender: AnyObject) { @@ -190,17 +191,15 @@ class LogWorkoutController: UIViewController { self.verticalStackView?.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor).isActive = true self.verticalStackView?.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true self.verticalStackView?.isHidden = false - - for set in (self.exercise?.sets)! { - self.addSet(set) + + if let e = exercise { + for set in e.sets { + self.addSet(set) + } } - - // Disable Navigation Drawer -// let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate -// appDelegate?.sideNavigationViewController?.enabled = false } - func setRepositoryRoutine(_ repositoryExercise: RepositoryExercise, repositoryRoutine: RepositoryRoutine) { + func setRepositoryRoutine(repositoryExercise: RepositoryExercise, repositoryRoutine: RepositoryRoutine) { self.exercise = repositoryExercise self.routine = repositoryRoutine } diff --git a/BodyweightFitness/Date+Extensions.swift b/BodyweightFitness/Date+Extensions.swift index 296c0fc..5d0a690 100644 --- a/BodyweightFitness/Date+Extensions.swift +++ b/BodyweightFitness/Date+Extensions.swift @@ -1,12 +1,16 @@ import Foundation +func secondsToHoursMinutesSeconds(_ seconds : Int) -> (Int, Int, Int) { + return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) +} + extension Date { public var globalDescription: String { get { let month = dateFormattedStringWithFormat("MMMM", fromDate: self) let year = dateFormattedStringWithFormat("YYYY", fromDate: self) - return "\(month), \(year)" + return "\(month) \(year)" } } @@ -16,7 +20,16 @@ extension Date { let month = dateFormattedStringWithFormat("MMMM", fromDate: self) let year = dateFormattedStringWithFormat("YYYY", fromDate: self) - return "\(day) \(month), \(year)" + return "\(day) \(month) \(year)" + } + } + + public var description: String { + get { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE, d MMMM YYYY" + + return formatter.string(from: self) } } diff --git a/BodyweightFitness/Domain/CompletionRate.swift b/BodyweightFitness/Domain/CompletionRate.swift new file mode 100644 index 0000000..9158d57 --- /dev/null +++ b/BodyweightFitness/Domain/CompletionRate.swift @@ -0,0 +1,9 @@ +class CompletionRate { + let percentage: Int + let label: String + + init(percentage: Int, label: String) { + self.percentage = percentage + self.label = label + } +} diff --git a/BodyweightFitness/Domain/DataEntriesCompanion.swift b/BodyweightFitness/Domain/DataEntriesCompanion.swift new file mode 100644 index 0000000..5d96724 --- /dev/null +++ b/BodyweightFitness/Domain/DataEntriesCompanion.swift @@ -0,0 +1,76 @@ +import Foundation + +class DataEntriesCompanion { + + func getDataEntries(fromDate: Date, + numberOfDays: Int, + repositoryRoutines: [RepositoryRoutine], + workoutChartType: WorkoutChartType + ) -> [WorkoutDataEntry] { + var dataEntries: [WorkoutDataEntry] = [] + + let baseLineDay: Date = (Calendar.current as NSCalendar).date( + byAdding: .day, + value: -numberOfDays, + to: fromDate, + options: [] + )! + + for index in 0...numberOfDays { + let date = (Calendar.current as NSCalendar).date( + byAdding: .day, + value: index, + to: baseLineDay, + options: [] + )! + + let routinesForDay = repositoryRoutines.filter({ + return $0.startTime.commonDescription == date.commonDescription + }) + + if let repositoryRoutine = routinesForDay.first { + dataEntries.append( + WorkoutDataEntry( + x: Double(index), + y: self.getValue(repositoryRoutine: repositoryRoutine, workoutChartType: workoutChartType), + title: self.getTitle(repositoryRoutine: repositoryRoutine), + label: self.getLabel(repositoryRoutine: repositoryRoutine, workoutChartType: workoutChartType) + ) + ) + } else { + dataEntries.append( + WorkoutDataEntry( + x: Double(index), + y: 0, + title: date.description, + label: "No Data" + ) + ) + } + } + + return dataEntries + } + + private func getValue(repositoryRoutine: RepositoryRoutine, workoutChartType: WorkoutChartType) -> Double { + switch workoutChartType { + case .WorkoutLength: + return RepositoryRoutineCompanion(repositoryRoutine).workoutLengthInMinutes() + case .CompletionRate: + return Double(ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises).completionRate().percentage) + } + } + + private func getTitle(repositoryRoutine: RepositoryRoutine) -> String { + return RepositoryRoutineCompanion(repositoryRoutine).date() + } + + private func getLabel(repositoryRoutine: RepositoryRoutine, workoutChartType: WorkoutChartType) -> String { + switch workoutChartType { + case .WorkoutLength: + return RepositoryRoutineCompanion(repositoryRoutine).workoutLength() + case .CompletionRate: + return ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises).completionRate().label + } + } +} diff --git a/BodyweightFitness/Domain/ListOfRepositoryExercisesCompanion.swift b/BodyweightFitness/Domain/ListOfRepositoryExercisesCompanion.swift new file mode 100644 index 0000000..71f7971 --- /dev/null +++ b/BodyweightFitness/Domain/ListOfRepositoryExercisesCompanion.swift @@ -0,0 +1,67 @@ +import Foundation +import RealmSwift + +class ListOfRepositoryExercisesCompanion { + let repositoryExercises: List + + init(_ repositoryExercises: List) { + self.repositoryExercises = repositoryExercises + } + + func numberOfExercises() -> Int { + let filtered = self.repositoryExercises.filter({ + $0.visible + }) + + return filtered.count + } + + func numberOfCompletedExercises() -> Int { + let filtered = self.repositoryExercises.filter({ + $0.visible && RepositoryExerciseCompanion($0).isCompleted() + }) + + return filtered.count + } + + func allExercisesCompleted() -> Bool { + return numberOfCompletedExercises() == numberOfExercises() + } + + func completionRate() -> CompletionRate { + let numberOfExercises = self.numberOfExercises() + let numberOfCompletedExercises = self.numberOfCompletedExercises() + + if (numberOfExercises == 0 || numberOfCompletedExercises == 0) { + return CompletionRate(percentage: 0, label: "0%") + } + + let value = numberOfCompletedExercises * 100 / numberOfExercises + + return CompletionRate(percentage: value, label: "\(value)%") + } + + func notCompletedExercises() -> [RepositoryExercise] { + let filtered = Array(self.repositoryExercises).filter({ + $0.visible && !RepositoryExerciseCompanion($0).isCompleted() + }) + + return filtered + } + + func visibleExercises() -> [RepositoryExercise] { + let filtered = Array(self.repositoryExercises).filter({ + $0.visible + }) + + return filtered + } + + func visibleOrCompletedExercises() -> [RepositoryExercise] { + let filtered = Array(self.repositoryExercises).filter({ + $0.visible || RepositoryExerciseCompanion($0).isCompleted() + }) + + return filtered + } +} diff --git a/BodyweightFitness/Domain/RepositoryCategory.swift b/BodyweightFitness/Domain/RepositoryCategory.swift index 44c159e..4108719 100644 --- a/BodyweightFitness/Domain/RepositoryCategory.swift +++ b/BodyweightFitness/Domain/RepositoryCategory.swift @@ -1,56 +1,6 @@ import Foundation import RealmSwift -class CompletionRate { - let percentage: Int - let label: String - - init(percentage: Int, label: String) { - self.percentage = percentage - self.label = label - } -} - -class RepositoryCategoryHelper { - class func getCompletionRate(_ repositoryCategory: RepositoryCategory) -> CompletionRate { - if (numberOfExercises(repositoryCategory) == 0) { - return CompletionRate(percentage: 0, label: "0%") - } - - let percentage = numberOfCompletedExercises(repositoryCategory) * 100 / numberOfExercises(repositoryCategory) - - return CompletionRate(percentage: percentage, label: String(percentage) + "%") - } - - class func isCompleted(_ repositoryExercise: RepositoryExercise) -> Bool { - let size = repositoryExercise.sets.count - - if (size == 0) { - return false - } - - let firstSet = repositoryExercise.sets[0] as RepositorySet - - if (size == 1 && firstSet.seconds == 0 && firstSet.reps == 0) { - return false - } - - return true - } - - class func numberOfCompletedExercises(_ repositoryCategory: RepositoryCategory) -> Int { - return repositoryCategory.exercises.filter({ - $0.visible && isCompleted($0) - }).count - } - - class func numberOfExercises(_ repositoryCategory: RepositoryCategory) -> Int { - return repositoryCategory.exercises.filter({ - $0.visible - }).count - } -} - class RepositoryCategory: Object { dynamic var id = "Category-" + UUID().uuidString dynamic var categoryId = "" diff --git a/BodyweightFitness/Domain/RepositoryExerciseCompanion.swift b/BodyweightFitness/Domain/RepositoryExerciseCompanion.swift new file mode 100644 index 0000000..5385591 --- /dev/null +++ b/BodyweightFitness/Domain/RepositoryExerciseCompanion.swift @@ -0,0 +1,65 @@ +import Foundation +import RealmSwift + +class RepositoryExerciseCompanion { + let repositoryExercise: RepositoryExercise + + init(_ repositoryExercise: RepositoryExercise) { + self.repositoryExercise = repositoryExercise + } + + func isCompleted() -> Bool { + if let firstSet = self.repositoryExercise.sets.first as RepositorySet? { + if (firstSet.isTimed) { + let totalTime = self.repositoryExercise.sets.map({ $0.seconds }).reduce(0, +) + + return (totalTime > 0) + } else { + let totalNumberOfReps = self.repositoryExercise.sets.map({ $0.reps }).reduce(0, +) + + return (totalNumberOfReps > 0) + } + } + + return false + } + + func setSummaryLabel() -> String { + let defaultLabel = "Not Completed" + let totalNumberOfSets = self.repositoryExercise.sets.count + let setLabel = (totalNumberOfSets > 1) ? "Sets" : "Set" + + if let firstSet = self.repositoryExercise.sets.first as RepositorySet? { + if (firstSet.isTimed) { + let totalTime = self.repositoryExercise.sets.map({ $0.seconds }).reduce(0, +) + + let (_, minutes, seconds) = secondsToHoursMinutesSeconds(totalTime) + + let minutesLabel = (minutes == 1) ? "Minute" : "Minutes" + let secondsLabel = (seconds == 1) ? "Second" : "Seconds" + + if (totalTime == 0) { + return defaultLabel + } else if (minutes == 0) { + return "\(totalNumberOfSets) \(setLabel), \(seconds) \(secondsLabel)" + } else if (seconds == 0) { + return "\(totalNumberOfSets) \(setLabel), \(minutes) \(minutesLabel)" + } else { + return "\(totalNumberOfSets) \(setLabel), \(minutes) \(minutesLabel), \(seconds) \(secondsLabel)" + } + } else { + let totalNumberOfReps = self.repositoryExercise.sets.map({ $0.reps }).reduce(0, +) + + let repLabel = (totalNumberOfReps > 1) ? "Reps" : "Rep" + + if (totalNumberOfReps == 0) { + return defaultLabel + } else { + return "\(totalNumberOfSets) \(setLabel), \(totalNumberOfReps) \(repLabel)" + } + } + } + + return defaultLabel + } +} diff --git a/BodyweightFitness/Domain/RepositoryRoutine.swift b/BodyweightFitness/Domain/RepositoryRoutine.swift index aa3a9f0..ac0b581 100644 --- a/BodyweightFitness/Domain/RepositoryRoutine.swift +++ b/BodyweightFitness/Domain/RepositoryRoutine.swift @@ -1,140 +1,6 @@ import Foundation import RealmSwift -class RepositoryRoutineHelper { - let repositoryRoutine: RepositoryRoutine - - init(repositoryRoutine: RepositoryRoutine) { - self.repositoryRoutine = repositoryRoutine - } - - func getDate() -> String { - let formatter = DateFormatter() - formatter.dateFormat = "EEEE, d MMMM YYYY" - - return formatter.string(from: repositoryRoutine.startTime) - } - - func getStartTime(_ longFormat: Bool = false) -> String { - if longFormat { - let formatter = DateFormatter() - formatter.dateFormat = "EEEE, d MMMM YYYY - HH:mm" - - return formatter.string(from: repositoryRoutine.startTime) - } - - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm" - - return formatter.string(from: repositoryRoutine.startTime) - } - - func getLastUpdatedTime() -> String { - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm" - - return formatter.string(from: repositoryRoutine.lastUpdatedTime) - } - - func getWorkoutLength() -> String { - let interval = repositoryRoutine.lastUpdatedTime.timeIntervalSince(repositoryRoutine.startTime) - - let (hours, minutes) = stringFromTimeInterval(interval) - - if (hours > 0 || minutes > 10) { - if (hours > 0) { - return "\(hours)h \(minutes)m" - } else { - return "\(minutes) minutes" - } - } else { - return "--" - } - } - - class func getCompletionRate(_ repositoryRoutine: RepositoryRoutine) -> CompletionRate { - if (numberOfExercises(repositoryRoutine) == 0) { - return CompletionRate(percentage: 0, label: "0%") - } - - let percentage = numberOfCompletedExercises(repositoryRoutine) * 100 / numberOfExercises(repositoryRoutine) - - return CompletionRate(percentage: percentage, label: String(percentage) + "%") - } - - class func isCompleted(_ repositoryExercise: RepositoryExercise) -> Bool { - let size = repositoryExercise.sets.count - - if (size == 0) { - return false - } - - let firstSet = repositoryExercise.sets[0] as RepositorySet - - if (size == 1 && firstSet.seconds == 0 && firstSet.reps == 0) { - return false - } - - return true - } - - class func numberOfCompletedExercises(_ repositoryRoutine: RepositoryRoutine) -> Int { - return repositoryRoutine.exercises.filter({ - $0.visible && isCompleted($0) - }).count - } - - class func numberOfExercises(_ repositoryRoutine: RepositoryRoutine) -> Int { - return repositoryRoutine.exercises.filter({ - $0.visible - }).count - } - - func completedExercises() -> Int { - var completed = 0 - for exercise in repositoryRoutine.exercises { - if exercise.visible { - let firstSet = exercise.sets[0] - - if firstSet.isTimed { - if firstSet.seconds > 0 { - completed += 1 - } - } else { - if firstSet.reps > 0 { - completed += 1 - } - } - } - } - - return completed - } - - func totalExercises() -> Int { - return repositoryRoutine.exercises.filter({ - $0.visible == true - }).count - } - - func exercisesLeft() -> Int { - return totalExercises() - completedExercises() - } - - func isCompleted() -> Bool { - return exercisesLeft() == 0 - } - - fileprivate func stringFromTimeInterval(_ interval: TimeInterval) -> (Int, Int) { - let interval = Int(interval) - - let minutes = (interval / 60) % 60 - let hours = (interval / 3600) - - return (hours, minutes) - } -} - class RepositoryRoutine: Object { dynamic var id = "Routine-" + UUID().uuidString dynamic var routineId = "" diff --git a/BodyweightFitness/Domain/RepositoryRoutineCompanion.swift b/BodyweightFitness/Domain/RepositoryRoutineCompanion.swift new file mode 100644 index 0000000..391e0b8 --- /dev/null +++ b/BodyweightFitness/Domain/RepositoryRoutineCompanion.swift @@ -0,0 +1,78 @@ +import Foundation +import RealmSwift + +class RepositoryRoutineCompanion { + let repositoryRoutine: RepositoryRoutine + + init(_ repositoryRoutine: RepositoryRoutine) { + self.repositoryRoutine = repositoryRoutine + } + + func date() -> String { + return self.repositoryRoutine.startTime.description + } + + func dateWithTime() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "EEEE, d MMMM YYYY - HH:mm" + + return formatter.string(from: self.repositoryRoutine.startTime) + } + + func startTime() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm" + + return formatter.string(from: self.repositoryRoutine.startTime) + } + + func lastUpdatedTime() -> String { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm" + + return formatter.string(from: self.repositoryRoutine.lastUpdatedTime) + } + + func lastUpdatedTimeLabel() -> String { + let companion = ListOfRepositoryExercisesCompanion(self.repositoryRoutine.exercises) + + if (companion.allExercisesCompleted()) { + return "End Time" + } else { + return "Last Updated Time" + } + } + + func workoutLength() -> String { + let interval = self.repositoryRoutine.lastUpdatedTime.timeIntervalSince(repositoryRoutine.startTime) + + let (hours, minutes) = self.stringFromTimeInterval(interval) + + if (hours > 0 || minutes > 10) { + if (hours > 0 && minutes == 0) { + return "\(hours)h" + } else if (hours > 0) { + return "\(hours)h \(minutes)m" + } else { + return "\(minutes)m" + } + } else { + return "--" + } + } + + func workoutLengthInMinutes() -> Double { + let interval = self.repositoryRoutine.lastUpdatedTime.timeIntervalSince(repositoryRoutine.startTime) + + return Double(Int(interval) / 60) + } + + fileprivate func stringFromTimeInterval(_ interval: TimeInterval) -> (Int, Int) { + let interval = Int(interval) + + let minutes = (interval / 60) % 60 + let hours = (interval / 3600) + + return (hours, minutes) + } +} diff --git a/BodyweightFitness/Domain/RepositorySetCompanion.swift b/BodyweightFitness/Domain/RepositorySetCompanion.swift new file mode 100644 index 0000000..e8cc29a --- /dev/null +++ b/BodyweightFitness/Domain/RepositorySetCompanion.swift @@ -0,0 +1,40 @@ +import Foundation +import RealmSwift + +class RepositorySetCompanion { + let repositorySet: RepositorySet + + init(_ repositorySet: RepositorySet) { + self.repositorySet = repositorySet + } + + func setSummaryLabel() -> String { + let defaultLabel = "Not Completed" + + if (self.repositorySet.isTimed) { + let (_, minutes, seconds) = secondsToHoursMinutesSeconds(self.repositorySet.seconds) + + let minutesLabel = (minutes == 1) ? "Minute" : "Minutes" + let secondsLabel = (seconds == 1) ? "Second" : "Seconds" + + if (self.repositorySet.seconds == 0) { + return defaultLabel + } else if (minutes == 0) { + return "\(seconds) \(secondsLabel)" + } else if (seconds == 0) { + return "\(minutes) \(minutesLabel)" + } else { + return "\(minutes) \(minutesLabel), \(seconds) \(secondsLabel)" + } + } else { + let totalNumberOfReps = self.repositorySet.reps + let repLabel = (totalNumberOfReps > 1) ? "Reps" : "Rep" + + if (totalNumberOfReps == 0) { + return defaultLabel + } else { + return "\(totalNumberOfReps) \(repLabel)" + } + } + } +} diff --git a/BodyweightFitness/Domain/RepositoryStream.swift b/BodyweightFitness/Domain/RepositoryStream.swift index c08584e..7b4eb8d 100644 --- a/BodyweightFitness/Domain/RepositoryStream.swift +++ b/BodyweightFitness/Domain/RepositoryStream.swift @@ -140,7 +140,6 @@ final class RepositoryStream { } func getRepositoryRoutineForToday() -> RepositoryRoutine { - // If you want to get objects after a date, use greater-than (>), for dates before, use less-than (<). let startOfDay = Calendar.current.startOfDay(for: Date()) var components = DateComponents() diff --git a/BodyweightFitness/Domain/Routine.swift b/BodyweightFitness/Domain/Routine.swift index 4fdf4e5..7a9547a 100644 --- a/BodyweightFitness/Domain/Routine.swift +++ b/BodyweightFitness/Domain/Routine.swift @@ -110,6 +110,8 @@ class Routine { var linkedExercises: NSMutableArray = [] var linkedRoutine: NSMutableArray = [] + init() {} + init(fileName: String) { let json = JSON(data: loadRoutineFromFile(fileName)) diff --git a/BodyweightFitness/Domain/WorkoutChartType.swift b/BodyweightFitness/Domain/WorkoutChartType.swift new file mode 100644 index 0000000..f60a88b --- /dev/null +++ b/BodyweightFitness/Domain/WorkoutChartType.swift @@ -0,0 +1,4 @@ +enum WorkoutChartType { + case WorkoutLength + case CompletionRate +} diff --git a/BodyweightFitness/Domain/WorkoutDataEntry.swift b/BodyweightFitness/Domain/WorkoutDataEntry.swift new file mode 100644 index 0000000..530b740 --- /dev/null +++ b/BodyweightFitness/Domain/WorkoutDataEntry.swift @@ -0,0 +1,17 @@ +import Charts + +class WorkoutDataEntry: ChartDataEntry { + var title: String? + var label: String? + + public required init() { + super.init() + } + + init(x: Double, y: Double, title: String? = nil, label: String? = nil) { + super.init(x: x, y: y) + + self.title = title + self.label = label + } +} diff --git a/BodyweightFitness/Images.xcassets/tab_home.imageset/Contents.json b/BodyweightFitness/Images.xcassets/tab_home.imageset/Contents.json index 43b9af2..46c0393 100644 --- a/BodyweightFitness/Images.xcassets/tab_home.imageset/Contents.json +++ b/BodyweightFitness/Images.xcassets/tab_home.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "Home-30.png", + "filename" : "icons8-home-filled-30.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "Home-50.png", + "filename" : "icons8-home-filled-50.png", "scale" : "2x" }, { diff --git a/BodyweightFitness/Images.xcassets/tab_home.imageset/Home-30.png b/BodyweightFitness/Images.xcassets/tab_home.imageset/Home-30.png deleted file mode 100644 index 8059af7674ef611fa28fcc2a162fcb1c980a2bad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 518 zcmV+h0{Q)kP)k6bA6`1AE^iDc3@Y)Hi6wQ{1^0#>TCj)=94JV; z;9l?+n*4mU)r!vsRXmJGa+Q=YD;5u5s+Bz$u0`Ew72LwoC5VBmolnI%i&~+7|pT(YDNA;LUQl zCp60!%ehZxZQq9D;Z)#N!Q$LC=>~Qv@lINw7mFWh+h9KLgYk!m?A=RPIh-%3z61RH zmN1}Oi60Hla6HUFqn`08euyf{jW_WF8Fj=x6!y# z8`#E2rzcxo-2|8BX;z1mxY}2(9oU@XYnB|*Z=?k07*qo IM6N<$f_O*pO8@`> diff --git a/BodyweightFitness/Images.xcassets/tab_home.imageset/Home-50.png b/BodyweightFitness/Images.xcassets/tab_home.imageset/Home-50.png deleted file mode 100644 index 5b15960a94ac422a821b9f434f48b49448071479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 667 zcmV;M0%ZM(P)4t0RMKV*_6h z3A0dkF%0_ee|-soVDm2w$G*RQM#M4a-VBoD)7Efk>%kC)W5zab05B=WSlPr_gRtoO zk3Y8!Mk zo!G<}h14Qc`Uwn+u0>U)c>GSrN#3A41xSPj=Ijt{1c6ZyRe|IGo09tFiYSB}ghO@( zswb#YM$-ucU#@JR7629nO8~s<9l~ydAAtBEL)n11U&rZ^ivRHXC<^8t;3Bt(pZqm_ zlo%eTbNN#*6*&x|3qpaKo?{(i3PEA4LQEr?FvmS(tl4cLnlOG8Bw3tp-wupGY$Iwh zeDtF8)~vPsj&6eQo`=uHQu(A-6)HtU0WMBwgOc_!_0O*_psMW;eb+yx1EdYDS z>BoY>LW<;+K4#>Q5<>h1z;E*Bp;iQ77vm0t#|X|xA!D15AwxL#E*V=VMZ&ijRG)36 zQX=H*K%2KtlRZE^s}$FE7VR;Z@1QVIh_(kBYH*X+PZGStz^ZZ|=~9up4+j*aO&>PJ z}P)M~V!%9&tTFZOt90ikn()YPqS^oB`J(u18#t zxE`s^K(9yLK_09$+oSi}s*=@suk<*{{gum25w4S$`b$p002ovPDHLkV1m4$ BB(?wm diff --git a/BodyweightFitness/Images.xcassets/tab_home.imageset/icons8-home-filled-30.png b/BodyweightFitness/Images.xcassets/tab_home.imageset/icons8-home-filled-30.png new file mode 100644 index 0000000000000000000000000000000000000000..3f4498ce18008a1e7e0523d5a0976966d401ba40 GIT binary patch literal 614 zcmV-s0-61ZP);M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10pm$TK~zYI?UpfU6j2a{zu8@H-z{p0MQltX1W^>Ul5`d}LXul@ zZX$wM8SN~^E}gAdX{taPljA_lm4awvB>~R}Hg+OGu@P=J=iQElk{rkFvb)?hCV%^8 z{_pYL%)AE$SEsw30tJ{> zu|K$r{xwHS=Ydygo^~p6dD$`(V~-S`04Bp8%K|rJ>1w2ip$qR<{qs_5H6Ew^9QNv$$0^|hP;m}>R3+MT+<#gRb&NtG>$o$FD({zXbb<(wtF z22PMxY{O9p_!*0J#da>7J#N8wkZ-`H(Q;)~pn=fbyJNFB_Y9Rnb2mG8e~J})B!;1b z2hLZ;qAQXx{1vX|sgZo(*m@tOfA^1NAi^}e(VZDsK$rZZc zL(@9;I4v2iOq4#Si&qhb0Ka8)A~}U&3=B-?JzX3_ zG8*5`IPWczC~M=f&7u$EYe)EOD4y*h(=v6{a$Nzr0K@3b8pVQ;WL~6__y8r&o%pPKJR;f zhC!X>lkrZio^F;ZJ_eqB+w+(2{~hZ6QF~+FOfkW=%?3<|I0Vxar!%DX!Hg^Q;-I`^(w&)VG9VY#~d$B%Wn??v8z z;blr=@KAqk9dk`N_DK4S4?GR;qH=3P1k`rveVu;lzFGLGooAn(cyQo)^POXEv59`# za|9pdaQ@bJELm&0mM<}`$6@c@?Az9Uhh!x?)s7`intGk#oVn^bTYHg7!mT^cMD0DW ztAEwMjOLi`v?O1N5|NXztbFi{ac$+7CdS3NpQIWZzR0=1JGAXH z7ej_dZNdQsh5LDheu@g`8x{t>v<mswZH%$27 ac0XR|n)m&U0Vcro&*16m=d#Wzp$Pyb#(|~) literal 0 HcmV?d00001 diff --git a/BodyweightFitness/Images.xcassets/tab_settings.imageset/Contents.json b/BodyweightFitness/Images.xcassets/tab_settings.imageset/Contents.json index ab59e83..6b56b20 100644 --- a/BodyweightFitness/Images.xcassets/tab_settings.imageset/Contents.json +++ b/BodyweightFitness/Images.xcassets/tab_settings.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "universal", - "filename" : "Settings-30.png", + "filename" : "icons8-settings-filled-30.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "Settings-50.png", + "filename" : "icons8-settings-filled-50.png", "scale" : "2x" }, { diff --git a/BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-30.png b/BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-30.png deleted file mode 100644 index 5c2b8a07914566252c144177d0045c5e964eff64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmV+l1oiugP)%oz>CGLUn2G6TieLF6}pJI+Y+ZOhUf4etQH6G^6e z`FmK7an4Sjfn;1fw|^OqEsHlQSRGX3-uk3<3rMJfXj;c<0B4}^1PlpWieRRft84gv z&$g3u%fg*fe;pwFbCVu6I`&*R4@rVa99#FMN~Q`VJ__)O5L1bVI(P-R09DHNvjg7x zfVCj;T?9Q+ok*wt{a=GRlxrN&I8O%b+EeHmd5vcj+@LN z$)aBNM*mHsw8mOlv+{hHfiyQSF4XMtwrp|{{}wIwnbNM67e{MP8$v&HV4M=|54@=Yp}U6eS8ydO@r znO>76kX$r+Iy&|ov2RHF5P8Z4q%R_&X8fU^Krx(JTZ4M_nsiDf5ffF*vma^N$9u`_p4ZTuSddV)X#a z2gDSYU8wCT9{T;*gT`!}H&fx-3Uyqqw;?mGhh>cmPLfLfCiSJdg zI;;R>bP54^&{|EmRq>Aoj^o|}Jk-?wi|N$JJ`<2ldFO6p3^?pL(_L-oVGIa*Vz`a? z>R$muc}wK<-~;1i*8x?dzryz`0V^Ve!1h`tXfX z)~N|Fibe7^w>$$9j{xpeu=-TKb?&v_8_Pq=##kT`nWhSsUC1n#Zz_l;o%=X0+^sV_lbT zy3>KAfBqkae&+_tK4JPGG7lw~0sQ)UpD-Qhul3RPH!-lCHR#PoPXGV_07*qoM6N<$ Ef?=%czyJUM diff --git a/BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-50.png b/BodyweightFitness/Images.xcassets/tab_settings.imageset/Settings-50.png deleted file mode 100644 index 284fad9385382cfd1ea7c4202637cf581fdbfdb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1686 zcmV;H25I?;P)C}#K6SF>;|2{=meIx zs-klut3NcTyQ;A8#DrtCQUu*u^(r%8zRGOE5BQ6^Ew-3lsmc8Dp_ipY8$|nAJ&KOL z*zBsw{NU>z++f)GrA<`3Wbp5LvS=3ph27?iK)*7_CwltF?2w1}1Aw#!M0m@LcX~P> z6gL}nnZJP}{DM;Xhp3M6$Tj)M-Kpm|xo87pc zb$g`UK8~XJk{NX(4Af-)!DiGJKDMyiwHb9=ubCmX7_QHW=(Oh=?6f333T#A871BN2|p1{3#dKp&td0IXoj zf$V@#DrlS2Z9l7rQ8W_fk;R#L2=FI>-V#yHP-TpjZ#HGFf`?}SzXRw65t+O(?<>_a zAkALZGZ&bl%9yv*&`@9h%?19lsy_1_NV>=%(r9i?kUYP}K zG}(RLh!BjxIF1Q}^<*(}G0UCbO+Lipg+76A6s=FI4k79^`yhI6!dBPnyZKidTETD4 z(^`!_%EAizY2^tEEz6$=Jz7D!?3hCf{}vl@%rDoU7vo)fmC2sh14**U{OR7OeZUu@ zZc9Kk*iwJ&tQD@8AbMmoaKQbRn#%K$`C93qQRRJW9mxtei?*Ai~Q`UWioDq{sPYlix)@^)s2HNuOB3?BNwhvU|$z0tkRFlnVNlxs*tb zq1;Q1cyxgAy~89x5)JhTOl=~T^#0AhMr_c(}yy#GIt0{blG_IAI};wv>-Dpr-@EZn-za&b)uB@d_iEfi; zOZUNiofgDuS~H1kyCnk1dRYLE)MRny6B<#sc@9v|)~FCW#e$gCwXHIdwqETLrP~@3 z@Y;E?N+gO@+y@4X3f?#*%6kLC6w%v;P1$C3$|YL;L?hrH1r)#}eJ>*#KkbM{>3fN2 zG<_>gy6qtvP56~I{0gpj%lNbpYHWW_*F?GZjajsss{tnjfXL@8Wsi4c+It&CG?zRmHg!h^x<< zZ_3r`^xeJ7j@)3wqou7MeHTdK{%xb&RGOU2UgH>p_2>D8@4kx%5r*;Ww6@$Z$7fZH z7Id4v8tA<0o?kImu+JmHPE%SwyEDL*nP$=Y!qw(6^@aoKd&yW~OFzlDO;6|VwhqN( z)L|L)lz>TYbm9dgxkd2;@eD+RViDYD(35ms)F2Fx1k~FUz*0ezLJIj#KTNs_n48s)9imw~OPU~yVgj$Wabe0?x2jhYo7q6#<)n3OuhM_r_ z1cS8pRc+uLhL(NZuo;F{&KOODA&LhgRyX10F2+uYl8wEXsm{#3vLQ<`o)0yi7Wqy@ zBO+e;C<-?a*`;M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H11ByvRK~zYI#h1@-6lEC4KhHb+V*nxCNGP_|+NvqsJQx!sG#YJK zY+)U?3GvVv@Mydc6E*gtM*adrh`|H2WmDp|=*2`fa6k`gqG=>*p+zAW;zd|G?(V#g zhutN+J2P#!V)&kR-sgS4pJ(QM=9zclAxS!2kj@<#5VbRiZUhkJ4%I|v`P{ibI`K(# z3Pyt=kez5PX9Y-B_W-9mu`#ic;o&r%=Y$rBc`BTY84Qhip2P5Px+8Epo9TBqrxskw z-=uQ~23ioc4ixQ?oZkIr4y@ zs;c96^ZDDWf@`wh0d@sLi?D>Sw933{BEb()fk?25c_Th4PCJfn_ooZJ{&b<&(MlKS zWX#-T5XkFM0ReEwMtiY#kBSV6)t!j!Cj82PaK@i5g!T&%H#ahgl8cD_EpW-A^CB{j z$VGqZ^Tk>fTAc)WRuquf_HD%m$6{hC)*e$ahY0tEEeiVt*@ptEmx1SMk5kvwvwvI8 zpT7|oT+Yv32NqU!QIfDyul}D^0}GW^<^eph!OL{T1vhy6yMewn_>`g1Y1AG@v)RXS zsviJjF?aZmf41;p*d_{m@L&&#;41Mopa|FNzq;dmfQsYjqov8&KidP*#>wEt6`-|* zl^&Fy6`(aID=$SGw5fik`kwF}uU91-*k{T!b61)NTkmRO!icpeA0QN9lX1*vOiZ+} zTEUCO6o&2?gX$U}(Q|}tEEZGo;8R7PYGMfLg#&=zwqi$3)#*lgA1Fmw-vxcoadd|u zuhRTI7nZPgFQ13j zyW4VhWv!r_g`gl362XG9A|iq)=q4fv)Agk8o;8Fn(x4)&3pI1Kv@h2S^P)7|nKOIl zeY$Yn-1W?vIcH}#FZy53^MC)(^Jiw>kLQK|acMy&L$slz<#k{!P&Y2*i|TqeUwA9& zoF-|(5j@VtCIC_hPfSL_#Eo3hwR|RUO;}u!1$1}UC!IHOBmQt|K?Eh8`t#)cr1O@0 z{bi9^-7?o&+UgwbIlH-VGK{CjEy4z^ zS7z1noxs~RkRzGSRjb3es+x_`Q_7I?>PJiU$&_jus>Vqk3-;DvIg4!*&z zp}&79jNnun9Sr<NZSwN{zeEw^?X`@515A4FB!3m z>W0nix?wZhh+Ty6GU%x)bi|0n${qP>O>1krx>RpZ5O=*0KK4>>Hy?d=wro6?o7Q0b z_fa}y>A~NgYv)v&vCgF(k2OoDXZ(h&O3)-?vZ~w*dS|?xlJ~auc0$zt7fI(@KM~PK zQLJP8svToY^<&0i zbU{z-sOy)~KUX_uXo!kujA}4hU6d(DVn$MR#T%;~)7Z$or1D>Aq%nbDPHZj0B$!4e$y+M|JzOvmhVXz-Cd~1bkJ2JE*G7ZodEbFhcZ2Pj1yb5!nq~cfsQl!fW2i z(;L~dXY`Fs&*iT4#or0sQ(0BN@H|$IX&~fsPJ7ncV(Ze zmfgTjg6lY>z~!%4}R^&CKiRe*NYRq3Q4rdL|pE0D44Z%Wt`K$;^)cmWtAAgx{Ax zxU1X1^!gJ5#;fy~Ia10v(Xn0*$Dpa2FM|BU=Ai$`#C{sa(_O$-IRL)TG6Y30lP~=X z@O2W#qn+zLXGBbwYQESbl=)AR&Ymt}({myM9VW@2eMCQ`H2ILgMN4EuU7uT4_Dgs~ zk~%PV)jS^R(kww5nbBPr1ug^^vt#4W}oPjG9E_@wm!H177QRnW(z|{uA z@Fa|@D^iQS0{pU6*9>o|bVaHWyqH|lLPU7mgvqOEsL9o;omO@b31pDnwj0br*-mW) zL|Z`J1lP4|0cj(dYbk4;YNL=$u|;;J)#5l#tD8d?xO|s3l;1Wres)PfQedXy4x3$z zWj_jV6VWKM`s4=u|3;vSO029Rb@Xa3q_94B5OrI*54M@T<5vMML5cWw{jT0^Zvj6E z7gtTlwmxUY-d=3#_1%V^Fe!{Vf;8>i>WqSr)G)GRk;MEV!0=lc`ezac_#%(YJj@;4G-i{mK1@jXL=prwMEHYMd>Hz(Wz TQp7~W00000NkvXXu0mjf^2CrB diff --git a/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/Help-50.png b/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/Help-50.png deleted file mode 100644 index 546a38e47de35de7b89953f6f35bcd1e0d4b7ac7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1499 zcmV<11tj{3P)<;GL|J7qAIEYkEi?7*MHMJ2xUF~QO$AojuEL20ChyH0RaEl&xD9305B%P zaTv~?A^3~`F8pAj9sHx3>+YQ+!bOVGS+`~&T_I+VML^{P;vdyK&$|Xd2B01y_!ocz z02~8Efs}HxqL>2i)&MF#05gX^BK#C1rh6g8w>*Hh1LFHt&kJvD008t5MbHa>AN8_Q zV6St2X`EIna0-CuNkGmO1i>`xo|%ELKt#MlgsOh~(3P^%-A34(N@+7?hC9p>5lo5b zGV2S<1jKDMZV|DgO^6<)6rDWuXL`E~$n)+I@OUn-D+i;iP2YVRi0@ZjH~fx>KC?hb zov=T;-?V8NpF8cw1rl~OI}}LioV~^VdLYUlq9+ce3__yxq5tQ&3?(z~(f(X_$qnWK zVJt-DWF45bK-^a2iJ1VPzl9K|J2RzCsM??F9ys>^ct}J&IT)R#eI*r$+irFd;8(?j zoYg-Jgwfbyh5&w({h{(#`&j{^!y$hEFmqbRkOd}+=xjNfC4ngCj$Sy#MnISE<=C67 z%Y4oOpaOWup;^kxf$c8|AYQw{ws@{W>|k`7RY2-G->*7uc#MEtdjTR$1<2>30mE%K z0v(#^LG(w9$|44$%8tF4GW^1AHLvH1t-1_Sg;ET!as$$dbIg%Uh}nVh-vyAE4frMp z!<)QpcXPNtp)9le}QuR`UiCu5DH{kU1Ots}Pk>dCQJifL5M>o{^vzAxxEt z5nPxeG0o01zr2LLniT~k4PoX@%|a_sXb>r3GMV7G-nkNkQFm)u0AMW0?4f{o?dA)g zC(^=NRv{*AYbxkf3`Wa`D)o`cQF>Yapkm3Ji2m>Zjaext9}HKVGb<3c^~DFo8ziU^ zVlORe@m@AyHa*!N`B`NwuX9_?sd0LYV{`tuvyCENtI+|(TjN8;fHZoD_?2@WTIEQg zXq6RAT*E2b%IzbzZ?iw5Niamwi5&b9WK}57x&#}lj;qx2Mo10hi;q}&Qqj>kx}99| zD7o=FI+6Y0@@F0p3bCQ~P?fR!0{|urg!q4=Kg@Jm+^F!H1zx+!0#&(E)X8lTM*nvK zVkgQnN}vw`V(i!mFOsX$$bL6Qj1Ouhk`-%SGfiit)tCU{{w|z7k^D-CQY*ks%|-OO z)9{<{je=P`jlD$p-DN`DJrG^atAx1A5=HftH+0f|qUu?3~IgQil>elDX;x)UH)6`k4+QcfQ?=+-uiw=%cI zs0~13G|k)oNw6F@*Vi=Yc(v|?O!yr9e{7kWgPE?NlZUdmn%IS!Xj?ItWwCB0J*mF* z#wPZBL#u2l&0n(Ux^|5PBJ)_mY-Ty?ScutK8P1;zj6TlZ>B-spPS1wXTgY?s!=k;~ zTmbk>7881M=J;EW-jr~5elP5C8()o!k*>T+>5Nc>|d{C-b`L-VF2gg@M^aX@KH;Eo!8p#^FR4rCu0Vya0WL zqq}_FA|mL8gVAFiUvOHDuN72RI2O|Ps~@HX!z)#ibggKnn4i zda1_q_JP#c+gz%#8s`i^+~i?79}t6z0l6N}8u=KopNA11L8xm^QuOiwFfxk)u{Gz5 zXP)Ql;V0F3ys!d}=#>yAd=-j-#-R{F5#??1{{cuwyZyPZLiGRu002ovPDHLkV1l92 B$v^-A diff --git a/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-30.png b/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-30.png new file mode 100644 index 0000000000000000000000000000000000000000..fe85e53b52c83cb6d6f38528dcb134c36313ab5f GIT binary patch literal 945 zcmV;i15W&jP);M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H111?EKK~zYI&6mM%6jdC@KfkxLGqaLbqCt%`R8E%EAPL404=Qrv zP+CiO(SM-cjF@=P5Yd>RF&On`>d^z#F>IG6>;a2tO!Q`qu||y{7D5y~0K2d=yR-B2 z(C&73QaUr(h~Lw`-@Nbl^WK}}H*et%Nm9=&7HvnGdj_i5arL~ zdCd(0yOWc{SkddCQ@~(C*fkaXOhn#noUB~DJ#evTI}*Gl$cuoRw7{5rgb)14K6gDp4-lFN7 zGB$Qs*fwW25DKt8%tsaW?{|8Ox!!@ZYvgT#gNachzk*amK3=`i(zlJO~XN5oh zywHv4{BVn7+Y$2<;H+s~HN4VL`+YpG-KpgdJ^N@*qXppJb*w&NZp1%z>Ua89DdvqN zD8eU=Pbw3Y(Y9H;{08lIzTxtFVY+Bc<(LUds zKj)Ul7l9os)uyHPP`zHi?he>Dh`qj;YJRP`g=#f00FDXwB|-tjeCU+My&fol-7>8~ zs?{dYN=O)R2AyZ1bJQ9=l?YwdBLY*g+Bd=M+;4^F#vdn5qtWQ;E?Cg5DZz0Ck@CWF z9`B|x2(q;c1|GZ}s;V4rdX*mvhbCSEo=j(=7cA+&QmX+Afj9S&eQ<14RgY!0cXoU^ zGnnWYpvIUPppb!hxeHdflpV|krl<1=d$O2R^->p%m@hM9Mc#3*Uip~yTjd%)a~{9~ z2(J5Q-2uA>3}i?sfo5VVTn+BN?{ny^wptBMH;Zc#7g_L5+|!x{m4GjTl$||eSjM$PXB4Ey&GjA{w%xXtgZHA zaVXs4+Zik_Hp0=JCvy@yg6t;EarVncIPR-G9e7@S%_Suz{P!37xHkAN%pKD|{naCo TrkGG|00000NkvXXu0mjflE=7~ literal 0 HcmV?d00001 diff --git a/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-50.png b/BodyweightFitness/Images.xcassets/tab_support_developer.imageset/icons8-help-filled-50.png new file mode 100644 index 0000000000000000000000000000000000000000..ad69cc1a6678e94229c00dca688dcab887da4bec GIT binary patch literal 1575 zcmV+?2H5$DP)|}$7$)dJF`hG!=8=G425wBV^9Wl zY1RsIOoXF2y1!pEySH`gyT65@Uz!oq?3a*CQq@_4$XuYzCqoX{BcgAn$}1Y!xG@tn zlwd|oGm*evrXo)OR|ggEg3}`MV#%-O@D6V`SbC(R+Lc7YMr9(%3TU>e`BFU6hdl=DZ3=BF&22R9bNUpvYIzj8wMO z*PrRFZLdZW=KPUrl)aDYm~g`LjiX{PC$+8dbWe?XG?Fmqk5n-Z0@s8ZnqP>XG%}H? z?K^k=T&Rgru=A3caYxGP`KL_)sEl?ZP9v3-u|iD>8i}`;uM>1q-$WJ!dP`zZbZw#9 zZf~tvb=92$%_S3rA9cumzb$?=K`69e+MSQpY0X=<{)7caTB5|Um{a1&WoU1ZfZtnSVPfVHjh zK+19^10My{7#q7Hy0q)X&Lgs9W<1_rei9fFOhcZvY8zJOOR;;Cm7-b&Bvj-J+lg+< zrym2%c@;T{9tQ>l5cZ+BZx*K>5SS+gR;*$P? z1q%iOT&&yu3$Qt$)>Vmtu}&FA=UYp7Fd#9&J{lT+2Z+}sM*+`t(`I}kCd>bllMjN5 zDf)o7>>4S*@9rU>vw{+X#N#!|nS)Gos5rWa0x7+Ux(~Uk{2E-3Iox0Au6H8&lT0=` z1Jv(&!ULU!!>qKVdu%0QoOOGUE0^F)qG&7|pKilD;2Dt|&eWicKA@1>;+BNlOJmDT z%b&VN1es70X%P^S^{MT1S&&Cd;WIojdmr}eN+B%*r&Bgh=L=RgvJx0l3ZZ&p z_8KXPv=?MCxIaIuC2Fc}6IIC7U0C6+ z1UewloZeo42!LdAFrsThR&ZnX8fghv1RM`%Eb80_3=5NAJp8T^MZXVI1V98gI~7BM zCZh%6cw%;Ejxvmc4*F!6LaL05S6AHuAjl*|!=wi(JCHwhjYK1v{dP8z!~gzmVI5dV z$AN0dCLm)+EO&9{-dca|ioX}OT3_ex;<6fxF%KGxW+J?SHt2(IF%ZFgd|8 z-%>6~QBvA(J#gZ0i^@M*?6%B?t{3}qgAPQFp?a!ANQZ~8n_qB)uG^_L_ubJGWfQJ1 z7noe4@GxL>#~~FZa-qTt_V&h&-PIM$e(nCPD;4>wRPpd2pWEltPZz2!oW{So&+3yZHo1*Q|?j|K&h16ggIX(Tgy8g_s|)^T1hwLn?fu$|rU-eX!66q09KM Z;|~HZ^97KK8hii%002ovPDHLkV1lCJ)vo{m literal 0 HcmV?d00001 diff --git a/BodyweightFitness/Images.xcassets/tab_workout_log.imageset/Calendar 10-30.png b/BodyweightFitness/Images.xcassets/tab_workout_log.imageset/Calendar 10-30.png deleted file mode 100644 index 4f354c0928a1c0b7c09d04003825d4967faf2628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmV-s0-61ZP)&}H_yxgdD-~kpZu|ns56F2HCbbe) zgjgt+GAUQ0Bh$M*)6Dh^;rMEA8{Ht_XB!fcaT+HVF5I@?RJ$=I`Oe0dOS$;xxI7Zwh@P&rapX?)k;Ahy+!e7znJ4e%SVoRGm+5o{dX zr2vV($g%|l8jy2vwZ}!i(4AfFdzlaZdHl!KckEMvFV?@}&RJbaZIH%EYlAF{sca~aSQryr zV=`H|=fDmFaUaMhPSwT!n2kIDn2NAVVrERxa*^*>u|AEI3I`-m z&EE=yl9ymYcetyguPc+sjusHJ-DK%ZFc?Pii@$XpO_R%G=lK9x%*!O!79*0zaMt&$ z*irRLae2ASfmIA-1re9RX=_O^q?7M?ayQM1+4ouOD4qWch;W+sM0hdF9y1<7cNq1R zqMj^vR*J}UL+C3o5(E=>OKONK*;xrlQH+{>?7OYOdcP! z8e$#;W$QlQ8c_pb*$fuFeif0T8O^(16_I5O3dgzx(M>oQc2ill_Zvd&90Dw-=LSI6 z!1yY3zk8{=3j&2kL{7&B=re<_!hvrmIU}6r2@!frWUhn2Xu6JCM;K;6wC3vlqI4t9&4~U9Z)~2SjKPp#xw~8BGK7?|gvI zgMoiE=WP4+o(b_&{x=4{*ZtPX91$4-BI7cSu_PcW{iI$H7`t)lW45g`fDW1YYv}rt zq|_%f5KIbno=sw#KyQUb7<949DQ0#cF&qh}*(1V9G7!s)z%o9D z?l8XC?fr&n?%YnI_U9$MA9raX*-juc$Fa=gmd@WhBb1t5yK3doXTS$}G^ zHH!Tyq?XVCGTWoJxY(^PM0AkYG^c$e+J2{(N?W)6syQpE*{P-p=3jy9H_|}J_iL{a zIT5I42j=6z_4{*<+IC%t@$F>z#3!xKfsFtGAe?D>$y;GYcl2#|9)X#FypRKEbjlwZ z4c$?G{Sk>Aw(F)j@&I}jJ9IK0fN@8sqbvP_fkfXVHWS~aj^7fD7#%U8BWJIPLwp0^ zItYTE&U`gXLZLu(in1)33P@SZW?i?6NLJ>`(yJm;7PDE`eT+!l-FDVRH>D@10b0C6 g-;^_1?W#Nfza;M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10u@O_K~zYI?UlW2Q&AYkf6vJ^9|emF{Re^w-3l!(BGMuv6-`@A zRIr1Ko0IxaR1|R$TpYSJVrr!YK@i$OlxA}1qN5HDrP$KuqdBibBE6I(-arx&`Yf0C zdCz%%oO5696)lv=WnKxUfL$}YUCu8pwOSii{KnOcGdB+mfL@mt7IV$kxmE#!88e#! zmgTq-1zHc(B(PlHYuDT4!@$jJW8Ui?D0R@XtTT;y=kEd+F;|R4mLCP)JMUh2**Yj6 z%G`6NXS?~uVgTU1o&x8Ky-r-Ac-t~%K)!^;nwX($pfj4};* z`Qhf>`mF}-BQ&p0seoV_G~I4Z%i-#^8Yi}m#+>y*w$Ue0a|s}s%d7*VePR|%3vb5$ z**iW0x0T=v@coee4V1_D)C;fy?}Dlu2UbG|^JCT69N@bL_Ni3+)8K zmf%FAJlS||^&EUWC=Cj>8|5zm#9+_Cm6|&Xtkrp}4Q8{aFiuC=`FVMLLvPq#Enx)1CDKsIis>$BP1t73DMGrw1?kfetTA^@hn5)Vo% zw|x1FHUjJd@UuDj*_`rXG?ei9vgR+vNUW(18ujVcb`$r2#!?cbI$JI3qKeG_#mU{A z%M9h9U_>lG9~;YARKzrT<}Ex!g;Q($8WaX({Kk#MNZd$_#Eq&kQv3gqqA_F>m^J`gmx=ie`T#bC)PULksKlci5o5&94DOTc z#*+QwvzZ<8vjfNdZ0@X|893mfc3?UR`LH8mz$7s2v+ni^aX@*8yq{h5+sdU>m0O#rLT;}DcrQ)1441jbQ%WsPWo8~p&1oWEOu;5mhT0$8yxQ6mu zG?}{!ENKMi#_};7DcsOG>=@9CX)MUIS}L;q&dz(^0Sq=5-q+u2&vW0yYE`l5xh?sYek|&m0+84eR-+`*H!u#^v2!PB^hSjKu5zpKey1TuZ8FwFe4}k2^^z*@Y_387wO==3O5#?TMSqvRc zeH^{(4-IwuaCD*L(wUS^dp*G7MWAG&JhQ(BMg<2AT9yhh(+f{LGi(Y|gXFFoIvhqc zII~ANB27TkO5cWL9R?lw3kzQIkx(_~k;F*cNQ}gd#7NvojKqyXBa>@Z#p?MQGz$Lt p;B^7t)})-(F=^1X)? - let formatter = DateFormatter() - var testCalendar: Calendar! = Calendar.current - override func viewDidLoad() { - self.setNavigationBar() - - let border = CALayer() - let width = CGFloat(0.5) - - border.borderColor = UIColor( - red: 70.0/255.0, - green: 70.0/255.0, - blue: 80.0/255.0, - alpha: 1.0 - ).cgColor - - border.frame = CGRect( - x: 0, - y: self.backgroundView.frame.size.height - width, - width: self.backgroundView.frame.size.width, - height: self.backgroundView.frame.size.height) - - border.borderWidth = width - - self.backgroundView.layer.addSublayer(border) - self.backgroundView.layer.masksToBounds = true - - self.tableView.register( - UINib(nibName: "WorkoutLogSectionCell", bundle: nil), - forCellReuseIdentifier: "WorkoutLogSectionCell" - ) + super.viewDidLoad() - self.tableView.register( - UINib(nibName: "WorkoutLogCardCell", bundle: nil), - forCellReuseIdentifier: "WorkoutLogCardCell" - ) - - self.tableView.delegate = self - self.tableView.dataSource = self - - formatter.dateFormat = "yyyy MM dd" + self.setNavigationBar() self.calendarView.calendarDelegate = self self.calendarView.calendarDataSource = self @@ -62,125 +25,342 @@ class CalendarViewController: UIViewController { self.calendarView.scrollToDate(Date(), triggerScrollToDateDelegate: false, animateScroll: false) { self.calendarView.selectDates([Date()]) } + + _ = RoutineStream.sharedInstance.repositoryObservable().subscribe(onNext: { (it) in + self.initializeContent(contentForDate: self.date) + }) } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.showOrHideCardViewForDate(date) + + override func mainView() -> UIView { + return self.listView } - + + func initializeContent(contentForDate: Date) { + super.initializeContent() + + self.navigationItem.title = contentForDate.commonDescription + + let repositoryRoutines = RepositoryStream.sharedInstance.getRoutinesForDate(contentForDate) + + if repositoryRoutines.isEmpty { + self.addView(self.createEmptyStateCard()) + } else { + for repositoryRoutine in repositoryRoutines { + self.addView(self.createLogCard(repositoryRoutine: repositoryRoutine)) + } + } + } + + func createEmptyStateCard() -> CardView { + let card = CardView() + + let label = ValueLabel() + label.text = "No Logged Workouts" + label.numberOfLines = 2 + label.textAlignment = .center + card.addSubview(label) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(80) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-80) + } + + return card + } + + func createLogCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let label = TitleLabel() + label.text = repositoryRoutine.title + card.addSubview(label) + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 0 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + + let companion = ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises) + let completionRate = companion.completionRate() + + let homeBarView = HomeBarView() + + homeBarView.categoryTitle.text = repositoryRoutine.subtitle + homeBarView.progressView.setCompletionRate(completionRate) + homeBarView.progressRate.text = completionRate.label + + stackView.addArrangedSubview(homeBarView) + + let viewButton = CardButton() + viewButton.repositoryRoutine = repositoryRoutine + viewButton.setTitle("View", for: .normal) + viewButton.addTarget(self, action: #selector(viewWorkout), for: .touchUpInside) + card.addSubview(viewButton) + + let exportButton = CardButton() + exportButton.repositoryRoutine = repositoryRoutine + exportButton.setTitle("Export", for: .normal) + exportButton.addTarget(self, action: #selector(exportWorkout), for: .touchUpInside) + card.addSubview(exportButton) + + let deleteButton = CardButton() + deleteButton.repositoryRoutine = repositoryRoutine + deleteButton.setTitle("Delete", for: .normal) + deleteButton.setTitleColor(UIColor.red, for: .normal) + deleteButton.addTarget(self, action: #selector(removeLoggedWorkout), for: .touchUpInside) + card.addSubview(deleteButton) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + viewButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(stackView.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + exportButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(stackView.snp.bottom).offset(16) + make.left.equalTo(viewButton.snp.right).offset(16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + deleteButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(stackView.snp.bottom).offset(16) + make.left.equalTo(exportButton.snp.right).offset(16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + return card + } + @IBAction func toggleCurrentDayView(_ sender: UIBarButtonItem) { self.calendarView.scrollToDate(Date(), animateScroll: false) self.calendarView.selectDates([Date()]) } - func showOrHideCardViewForDate(_ date: Date) { - self.date = date + @IBAction func viewWorkout(_ sender: CardButton) { + if let repositoryRoutine = sender.repositoryRoutine { + let storyboard = UIStoryboard(name: "WorkoutLog", bundle: Bundle.main) - self.navigationItem.title = date.commonDescription - - let routines = RepositoryStream.sharedInstance.getRoutinesForDate(date) - if (routines.count > 0) { - self.routines = routines - self.tableView?.backgroundView = nil - } else { - let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: tableView.bounds.size.height)) + let p = storyboard.instantiateViewController(withIdentifier: "WorkoutLogViewController") as! WorkoutLogViewController - label.text = "When you log a workout, you'll see it here." - label.font = UIFont(name: "Helvetica Neue", size: 15) - label.textAlignment = .center - label.sizeToFit() + p.date = date + p.repositoryRoutine = repositoryRoutine + p.hidesBottomBarWhenPushed = true - self.routines = nil - self.tableView?.backgroundView = label + self.navigationController?.pushViewController(p, animated: true) } - - self.tableView.reloadData() } -} -extension CalendarViewController: UITableViewDataSource, UITableViewDelegate { - func numberOfSections(in tableView: UITableView) -> Int { - if let _ = self.routines { - return 1 - } else { - return 0 + @IBAction func exportWorkout(_ sender: CardButton) { + let mailString = NSMutableString() + + if let repositoryRoutine = sender.repositoryRoutine { + let companion = RepositoryRoutineCompanion(repositoryRoutine) + let exercisesCompanion = ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises) + + mailString.append("Date, Start Time, End Time, Workout Length, Routine, Exercise, Set Order, Weight, Weight Units, Reps, Minutes, Seconds\n") + + for exercise in exercisesCompanion.visibleExercises() { + let title = exercise.title + let weightValue = getWeightUnit() + var index = 1 + + for set in exercise.sets { + let (_, minutes, seconds) = secondsToHoursMinutesSeconds(set.seconds) + + mailString.append(String( + format: "%@,%@,%@,%@,%@,%@,%d,%f,%@,%d,%d,%d\n", + companion.date(), + companion.startTime(), + companion.lastUpdatedTime(), + companion.workoutLength(), + "\(repositoryRoutine.title) - \(repositoryRoutine.subtitle)", + title, + index, + set.weight, + weightValue, + set.reps, + minutes, + seconds + )) + + index += 1 + } + } + + let content = NSMutableString() + let emailTitle = "\(repositoryRoutine.title) workout for \(companion.dateWithTime())" + + content.append("Hello,\nThe following is your workout in Text/HTML format (CSV attached).") + + content.append("\n\nWorkout on \(companion.dateWithTime()).") + content.append("\nLast Updated at \(companion.lastUpdatedTime())") + content.append("\nWorkout length: \(companion.workoutLength())") + + content.append("\n\n\(repositoryRoutine.title)\n\(repositoryRoutine.subtitle)") + + let weightUnit = getWeightUnit() + + for exercise in exercisesCompanion.visibleExercises() { + content.append("\n\n\(exercise.title)") + + var index = 1 + for set in exercise.sets { + let (_, minutes, seconds) = secondsToHoursMinutesSeconds(set.seconds) + + content.append("\nSet \(index)") + + if (set.isTimed) { + if minutes > 0 { + content.append(", Minutes: \(minutes)") + } + + content.append(", Seconds: \(seconds)") + } else { + content.append(", Reps: \(set.reps)") + + if set.weight > 0 { + content.append(", Weight: \(set.weight) \(weightUnit)") + } + } + + index += 1 + } + } + + let data = mailString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false) + if let data = data { + if !MFMailComposeViewController.canSendMail() { + print("Mail services are not available") + return + } + + let emailViewController = configuredMailComposeViewController(data, subject: emailTitle, messageBody: content as String) + + if MFMailComposeViewController.canSendMail() { + self.present(emailViewController, animated: true, completion: nil) + } + } } } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let routines = self.routines { - return routines.count - } else { - return 0 + + @IBAction func removeLoggedWorkout(_ sender: CardButton) { + if let repositoryRoutine = sender.repositoryRoutine { + let alertController = UIAlertController( + title: "Remove Workout", + message: "Are you sure you want to remove this workout?", + preferredStyle: UIAlertControllerStyle.alert + ) + + alertController.addAction( + UIAlertAction( + title: "Cancel", + style: UIAlertActionStyle.cancel, + handler: nil + ) + ) + + alertController.addAction( + UIAlertAction( + title: "Remove", + style: UIAlertActionStyle.destructive, + handler: { (action: UIAlertAction!) in + let realm = try! Realm() + + try! realm.write { + realm.delete(repositoryRoutine) + } + + RoutineStream.sharedInstance.setRepository() + } + ) + ) + + self.present(alertController, animated: true, completion: nil) } } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 0 - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 200 + + func configuredMailComposeViewController(_ data: Data, subject: String, messageBody: String) -> MFMailComposeViewController { + let emailController = MFMailComposeViewController() + + emailController.mailComposeDelegate = self + emailController.setSubject(subject) + emailController.setMessageBody(messageBody, isHTML: false) + emailController.addAttachmentData(data, mimeType: "text/csv", fileName: "Workout.csv") + + return emailController } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return nil + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "WorkoutLogCardCell", for: indexPath) as! WorkoutLogCardCell - - if let routines = self.routines { - let repositoryRoutine = routines[indexPath.row] - let completionRate = RepositoryRoutineHelper.getCompletionRate(repositoryRoutine) - - cell.parentController = self - cell.date = date - - cell.title.text = repositoryRoutine.title - cell.subtitle.text = repositoryRoutine.subtitle - cell.progressView.setCompletionRate(completionRate) - cell.progressRate.text = completionRate.label - - cell.repositoryRoutine = repositoryRoutine + + func getWeightUnit() -> String { + if PersistenceManager.getWeightUnit() == "lbs" { + return "lbs" + } else { + return "kg" } - - return cell } } extension CalendarViewController: JTAppleCalendarViewDataSource, JTAppleCalendarViewDelegate { func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters { + let formatter = DateFormatter() + let testCalendar = Calendar.current + formatter.dateFormat = "yyyy MM dd" formatter.timeZone = testCalendar.timeZone formatter.locale = testCalendar.locale let startDate = formatter.date(from: "2015 01 01") let endDate = Date() - - let parameters = ConfigurationParameters(startDate: startDate!, - endDate: endDate, - numberOfRows: 1, - calendar: testCalendar, - generateInDates: .forFirstMonthOnly, - generateOutDates: .off, - firstDayOfWeek: .monday, - hasStrictBoundaries: false) - - return parameters + + return ConfigurationParameters( + startDate: startDate!, + endDate: endDate, + numberOfRows: 1, + calendar: testCalendar, + generateInDates: .forFirstMonthOnly, + generateOutDates: .off, + firstDayOfWeek: .monday, + hasStrictBoundaries: false + ) } public func calendar( _ calendar: JTAppleCalendar.JTAppleCalendarView, cellForItemAt date: Date, cellState: JTAppleCalendar.CellState, - indexPath: IndexPath) -> JTAppleCalendar.JTAppleCell { + indexPath: IndexPath + ) -> JTAppleCalendar.JTAppleCell { let cell = calendar.dequeueReusableJTAppleCell( withReuseIdentifier: "CellView", - for: indexPath) as! CellView + for: indexPath + ) as! CellView cell.setupCellBeforeDisplay(cellState, date: date) @@ -189,8 +369,9 @@ extension CalendarViewController: JTAppleCalendarViewDataSource, JTAppleCalendar func calendar(_ calendar: JTAppleCalendarView, didSelectDate date: Date, cell: JTAppleCell?, cellState: CellState) { (cell as? CellView)?.setupCellBeforeDisplay(cellState, date: date) - - self.showOrHideCardViewForDate(date) + + self.date = date + self.initializeContent(contentForDate: date) } func calendar(_ calendar: JTAppleCalendarView, didDeselectDate date: Date, cell: JTAppleCell?, cellState: CellState) { diff --git a/BodyweightFitness/Main/HomeViewController.swift b/BodyweightFitness/Main/HomeViewController.swift index 6df9e83..ea2e3f3 100644 --- a/BodyweightFitness/Main/HomeViewController.swift +++ b/BodyweightFitness/Main/HomeViewController.swift @@ -1,103 +1,324 @@ import UIKit import RxSwift import StoreKit +import SnapKit +import MessageUI -class HomeViewController: UIViewController { - @IBOutlet weak var stackView: UIStackView! - - @IBOutlet weak var totalWorkouts: UILabel! - @IBOutlet weak var lastWorkout: UILabel! - @IBOutlet weak var last7Days: UILabel! - @IBOutlet weak var last31Days: UILabel! +class HomeViewController: AbstractViewController, MFMailComposeViewControllerDelegate { + var routine: Routine? = nil - @IBOutlet weak var routineTitle: UILabel! - @IBOutlet weak var routineShortDescription: UITextView! - override func viewDidLoad() { super.viewDidLoad() self.setNavigationBar() - - self.stackView.axis = UILayoutConstraintAxis.vertical; - self.stackView.distribution = UIStackViewDistribution.equalSpacing; - self.stackView.alignment = UIStackViewAlignment.top; - self.stackView.spacing = 0; - + _ = RoutineStream.sharedInstance.repositoryObservable().subscribe(onNext: { (it) in - self.renderWorkoutProgressView() - self.renderStatisticsView() + self.initializeContent() }) _ = RoutineStream.sharedInstance.routineObservable().subscribe(onNext: { (it) in - self.renderWorkoutProgressView() - self.renderStatisticsView() - - self.tabBarController?.title = it.title - self.routineTitle.text = it.title - self.routineShortDescription.text = it.shortDescription + self.routine = it + self.initializeContent() }) self.requestReviewIfAllowed() } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + override func initializeContent() { + super.initializeContent() - self.tabBarController?.title = RoutineStream.sharedInstance.routine.title + self.navigationItem.title = self.routine?.title + + self.addView(self.createTodaysProgressCard()) + self.addView(self.createAboutRoutineCard()) + self.addView(self.createStatisticsCard()) + self.addView(self.createFeedbackCard()) } - - func renderWorkoutProgressView() { - let routine = RoutineStream.sharedInstance.routine - - self.stackView.removeAllSubviews() - self.navigationItem.title = routine.title - + + func createTodaysProgressCard() -> CardView { + let card = CardView() + + let label = TitleLabel() + label.text = "Today's Progress" + card.addSubview(label) + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 0 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + if (RepositoryStream.sharedInstance.repositoryRoutineForTodayExists()) { let repositoryRoutine = RepositoryStream.sharedInstance.getRepositoryRoutineForToday() + + for repositoryCategory in repositoryRoutine.categories { + let companion = ListOfRepositoryExercisesCompanion(repositoryCategory.exercises) + let completionRate = companion.completionRate() - for category in repositoryRoutine.categories { - let completionRate = RepositoryCategoryHelper.getCompletionRate(category) let homeBarView = HomeBarView() - - homeBarView.categoryTitle.text = category.title + + homeBarView.categoryTitle.text = repositoryCategory.title homeBarView.progressView.setCompletionRate(completionRate) homeBarView.progressRate.text = completionRate.label - - self.stackView.addArrangedSubview(homeBarView) + + stackView.addArrangedSubview(homeBarView) } } else { - for category in routine.categories { - if let c = category as? Category { - let completionRate = CompletionRate(percentage: 0, label: "0%") - let homeBarView = HomeBarView() - - homeBarView.categoryTitle.text = c.title - homeBarView.progressView.setCompletionRate(completionRate) - homeBarView.progressRate.text = completionRate.label - - self.stackView.addArrangedSubview(homeBarView) + if let routine = self.routine { + for category in routine.categories { + if let c = category as? Category { + let completionRate = CompletionRate(percentage: 0, label: "0%") + let homeBarView = HomeBarView() + + homeBarView.categoryTitle.text = c.title + homeBarView.progressView.setCompletionRate(completionRate) + homeBarView.progressRate.text = completionRate.label + + stackView.addArrangedSubview(homeBarView) + } } } } + + let cardButton = CardButton() + cardButton.setTitle("Start Workout", for: .normal) + cardButton.addTarget(self, action: #selector(startWorkout), for: .touchUpInside) + card.addSubview(cardButton) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + cardButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(stackView.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + return card } - - func renderStatisticsView() { + + func createAboutRoutineCard() -> CardView { + let card = CardView() + + let routineTitleLabel = TitleLabel() + routineTitleLabel.text = routine?.title + card.addSubview(routineTitleLabel) + + let routineShortDescriptionLabel = DescriptionTextView() + routineShortDescriptionLabel.text = routine?.shortDescription + card.addSubview(routineShortDescriptionLabel) + + let cardButton = CardButton() + cardButton.setTitle("Read More", for: .normal) + cardButton.addTarget(self, action: #selector(readMore), for: .touchUpInside) + card.addSubview(cardButton) + + routineTitleLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + + make.height.equalTo(24) + } + + routineShortDescriptionLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(routineTitleLabel.snp.bottom) //.offset(8) + make.left.equalTo(card).offset(12) + make.right.equalTo(card).offset(-12) + make.bottom.equalTo(cardButton.snp.top).offset(-12) + } + + cardButton.snp.makeConstraints { (make) -> Void in + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + return card + } + + func createStatisticsCard() -> CardView { + let card = CardView() + let numberOfWorkouts = RepositoryStream.sharedInstance.getNumberOfWorkouts() let lastWorkout = RepositoryStream.sharedInstance.getLastWorkout() - + let numberOfWorkoutsLast7Days = RepositoryStream.sharedInstance.getNumberOfWorkouts(-7) let numberOfWorkoutsLast31Days = RepositoryStream.sharedInstance.getNumberOfWorkouts(-31) + + let topLeftLabel = TitleLabel() + topLeftLabel.textAlignment = .left + topLeftLabel.text = String(numberOfWorkouts) + getNumberOfWorkoutsPostfix(numberOfWorkouts) + card.addSubview(topLeftLabel) + + let topLeftValue = ValueLabel() + topLeftValue.textAlignment = .left + topLeftValue.text = "Total Workouts" + card.addSubview(topLeftValue) + + let topRightLabel = TitleLabel() + topRightLabel.textAlignment = .right + + if let workout = lastWorkout { + topRightLabel.text = String(Date.timeAgoSince(workout.startTime)) + } else { + topRightLabel.text = String("Never") + } + + card.addSubview(topRightLabel) + + let topRightValue = ValueLabel() + topRightValue.textAlignment = .right + topRightValue.text = "Last Workout" + card.addSubview(topRightValue) + + let bottomLeftLabel = TitleLabel() + bottomLeftLabel.textAlignment = .left + bottomLeftLabel.text = String(numberOfWorkoutsLast7Days) + getNumberOfWorkoutsPostfix(numberOfWorkoutsLast7Days) + card.addSubview(bottomLeftLabel) + + let bottomLeftValue = ValueLabel() + bottomLeftValue.textAlignment = .left + bottomLeftValue.text = "Last 7 Days" + card.addSubview(bottomLeftValue) + + let bottomRightLabel = TitleLabel() + bottomRightLabel.textAlignment = .right + bottomRightLabel.text = String(numberOfWorkoutsLast31Days) + getNumberOfWorkoutsPostfix(numberOfWorkoutsLast31Days) + card.addSubview(bottomRightLabel) + + let bottomRightValue = ValueLabel() + bottomRightValue.textAlignment = .right + bottomRightValue.text = "Last 31 Days" + card.addSubview(bottomRightValue) + + topLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightLabel.snp.left) + + make.height.equalTo(24) + } + + topRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftLabel.snp.right) + + make.height.equalTo(24) + } + + topLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightValue.snp.left) + } + + topRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftValue.snp.right) + } + + bottomLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftValue.snp.bottom).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(bottomRightLabel.snp.left) + + make.height.equalTo(24) + } + + bottomRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightValue.snp.bottom).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(bottomLeftLabel.snp.right) + + make.height.equalTo(24) + } + + bottomLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(bottomLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(bottomRightValue.snp.left) + + make.bottom.equalTo(card).offset(-20) + } + + bottomRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(bottomRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(bottomLeftValue.snp.right) + + make.bottom.equalTo(card).offset(-20) + } + + return card + } - self.totalWorkouts.text = String(numberOfWorkouts) + getNumberOfWorkoutsPostfix(numberOfWorkouts) + func createFeedbackCard() -> CardView { + let card = CardView() - if let w = lastWorkout { - self.lastWorkout.text = String(Date.timeAgoSince(w.startTime)) - } else { - self.lastWorkout.text = String("Never") + let label = TitleLabel() + label.text = "Feedback" + card.addSubview(label) + + let description = DescriptionTextView() + description.text = "Found a bug? Would like to request a new feature? Let me know!" + card.addSubview(description) + + let cardButton = CardButton() + cardButton.setTitle("Send Email", for: .normal) + cardButton.addTarget(self, action: #selector(sendFeedback), for: .touchUpInside) + card.addSubview(cardButton) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + + make.height.equalTo(24) + } + + description.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom) + make.left.equalTo(card).offset(12) + make.right.equalTo(card).offset(-12) + make.bottom.equalTo(cardButton.snp.top).offset(-12) } - self.last7Days.text = String(numberOfWorkoutsLast7Days) + getNumberOfWorkoutsPostfix(numberOfWorkoutsLast7Days) - self.last31Days.text = String(numberOfWorkoutsLast31Days) + getNumberOfWorkoutsPostfix(numberOfWorkoutsLast31Days) + cardButton.snp.makeConstraints { (make) -> Void in + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + return card } func getNumberOfWorkoutsPostfix(_ count: Int) -> String { @@ -172,7 +393,24 @@ class HomeViewController: UIViewController { @IBAction func readMore(_ sender: AnyObject) { if let requestUrl = URL(string: RoutineStream.sharedInstance.routine.url) { - UIApplication.shared.openURL(requestUrl) + UIApplication.shared.open(requestUrl, options: [:], completionHandler: nil) } } + + @IBAction func sendFeedback(_ sender: AnyObject) { + if MFMailComposeViewController.canSendMail() { + let emailController = MFMailComposeViewController() + + emailController.mailComposeDelegate = self + emailController.setToRecipients(["damian@mazur.io"]) + emailController.setSubject("Bodyweight Fitness App for iOS - Feedback") + emailController.setMessageBody("Hi Damian,\n", isHTML: false) + + self.present(emailController, animated: true, completion: nil) + } + } + + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true, completion: nil) + } } diff --git a/BodyweightFitness/Main/Main.storyboard b/BodyweightFitness/Main/Main.storyboard index 511b33c..8d4ccea 100644 --- a/BodyweightFitness/Main/Main.storyboard +++ b/BodyweightFitness/Main/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -159,23 +159,20 @@ - - - - - - - + + + + - + + + - + - - @@ -192,7 +189,7 @@ - + @@ -331,302 +328,18 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Many people want to improve overall flexibility, but do not know where to begin. This routine should serve as a general jumping-off point for beginners. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -634,15 +347,6 @@ - - - - - - - - - diff --git a/BodyweightFitness/Main/SupportDeveloperViewController.swift b/BodyweightFitness/Main/SupportDeveloperViewController.swift index 4d211ff..20c7aaf 100644 --- a/BodyweightFitness/Main/SupportDeveloperViewController.swift +++ b/BodyweightFitness/Main/SupportDeveloperViewController.swift @@ -111,7 +111,8 @@ class SupportDeveloperViewController: UIViewController, SKPaymentTransactionObse let alertController = UIAlertController( title: "IAP Disabled", message: "Please enable In App Purchases in Settings", - preferredStyle: .alert) + preferredStyle: .alert + ) alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default, handler: nil)) diff --git a/BodyweightFitness/Main/View/CalendarCell.swift b/BodyweightFitness/Main/View/CalendarCell.swift deleted file mode 100644 index 3efeb4e..0000000 --- a/BodyweightFitness/Main/View/CalendarCell.swift +++ /dev/null @@ -1,198 +0,0 @@ -import UIKit -import MessageUI - -func getWeightUnit() -> String { - if PersistenceManager.getWeightUnit() == "lbs" { - return "lbs" - } else { - return "kg" - } -} - -class WorkoutLogCardCell: UITableViewCell, MFMailComposeViewControllerDelegate { - @IBOutlet weak var title: UILabel! - @IBOutlet weak var subtitle: UILabel! - @IBOutlet weak var progressView: ProgressView! - @IBOutlet weak var progressRate: UILabel! - - var parentController: UIViewController? - var date: Date? - var repositoryRoutine: RepositoryRoutine? - - override func awakeFromNib() { - super.awakeFromNib() - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - } - - @IBAction func onClickView(_ sender: AnyObject) { - - let storyboard = UIStoryboard(name: "WorkoutLog", bundle: Bundle.main) - - let p = storyboard.instantiateViewController( - withIdentifier: "WorkoutLogViewController" - ) as! WorkoutLogViewController - - p.date = date - p.repositoryRoutine = repositoryRoutine - p.hidesBottomBarWhenPushed = true - - self.parentController?.navigationController?.pushViewController(p, animated: true) - } - - @IBAction func onClickExport(_ sender: AnyObject) { - let mailString = NSMutableString() - - if let routine = repositoryRoutine { - let helper = RepositoryRoutineHelper(repositoryRoutine: routine) - - let date = helper.getDate() - let startTime = helper.getStartTime() - let lastUpdatedTime = helper.getLastUpdatedTime() - - let exercises = repositoryRoutine?.exercises.filter { (exercise) in - exercise.visible == true - } - - mailString.append("Date, Start Time, End Time, Workout Length, Routine, Exercise, Set Order, Weight, Weight Units, Reps, Minutes, Seconds\n") - - for exercise in exercises! { - let title = exercise.title - let weightValue = getWeightUnit() - var index = 1 - - for set in exercise.sets { - let (_, minutes, seconds) = secondsToHoursMinutesSeconds(set.seconds) - - mailString.append(String( - format: "%@,%@,%@,%@,%@,%@,%d,%f,%@,%d,%d,%d\n", - date, - startTime, - lastUpdatedTime, - "1h 10m", - "\(routine.title) - \(routine.subtitle)", - title, - index, - set.weight, - weightValue, - set.reps, - minutes, - seconds)) - - index += 1 - } - } - - let content = NSMutableString() - let emailTitle = "\(routine.title) workout for \(helper.getStartTime(true))" - - content.append("Hello,\nThe following is your workout in Text/HTML format (CSV attached).") - - content.append("\n\nWorkout on \(helper.getStartTime(true)).") - content.append("\nLast Updated at \(helper.getLastUpdatedTime())") - content.append("\nWorkout length: \(helper.getWorkoutLength())") - - content.append("\n\n\(routine.title)\n\(routine.subtitle)") - - let weightUnit = getWeightUnit() - - if let exercises = exercises { - for exercise in exercises { - content.append("\n\n\(exercise.title)") - - var index = 1 - for set in exercise.sets { - let (_, minutes, seconds) = secondsToHoursMinutesSeconds(set.seconds) - - content.append("\nSet \(index)") - - if (set.isTimed) { - if minutes > 0 { - content.append(", Minutes: \(minutes)") - } - - content.append(", Seconds: \(seconds)") - } else { - content.append(", Reps: \(set.reps)") - - if set.weight > 0 { - content.append(", Weight: \(set.weight) \(weightUnit)") - } - } - - index += 1 - } - } - - } - - let data = mailString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: false) - if let data = data { - if !MFMailComposeViewController.canSendMail() { - print("Mail services are not available") - return - } - - let emailViewController = configuredMailComposeViewController(data, subject: emailTitle, messageBody: content as String) - - if MFMailComposeViewController.canSendMail() { - self.parentController?.present(emailViewController, animated: true, completion: nil) - } - } - } - } - - @IBAction func onClickRemove(_ sender: AnyObject) { - let alertController = UIAlertController( - title: "Remove Workout", - message: "Are you sure you want to remove this workout?", - preferredStyle: UIAlertControllerStyle.alert) - - alertController.addAction(UIAlertAction( - title: "Cancel", - style: UIAlertActionStyle.cancel, - handler: nil)) - - alertController.addAction(UIAlertAction( - title: "Remove", - style: UIAlertActionStyle.destructive, - handler: { (action: UIAlertAction!) in - let realm = RepositoryStream.sharedInstance.getRealm() - - try! realm.write { - realm.delete(self.repositoryRoutine!) - } - - if let parent = self.parentController as? CalendarViewController { - if let date = self.date { - parent.showOrHideCardViewForDate(date) - } - } - - RoutineStream.sharedInstance.setRepository() - })) - - self.parentController?.present(alertController, animated: true, completion: nil) - } - - func configuredMailComposeViewController(_ data: Data, subject: String, messageBody: String) -> MFMailComposeViewController { - let emailController = MFMailComposeViewController() - - emailController.mailComposeDelegate = self - emailController.setSubject(subject) - emailController.setMessageBody(messageBody, isHTML: false) - emailController.addAttachmentData(data, mimeType: "text/csv", fileName: "Workout.csv") - - return emailController - } - - func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { - controller.dismiss(animated: true, completion: nil) - } - - func secondsToHoursMinutesSeconds (_ seconds : Int) -> (Int, Int, Int) { - return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) - } -} diff --git a/BodyweightFitness/Resources/TestRoutine.json b/BodyweightFitness/Resources/bodyweight_fitness_recommended_routine_unit_test.json similarity index 100% rename from BodyweightFitness/Resources/TestRoutine.json rename to BodyweightFitness/Resources/bodyweight_fitness_recommended_routine_unit_test.json diff --git a/BodyweightFitness/View/CardView.swift b/BodyweightFitness/View/CardView.swift index da70314..16ea397 100644 --- a/BodyweightFitness/View/CardView.swift +++ b/BodyweightFitness/View/CardView.swift @@ -2,14 +2,16 @@ import UIKit @IBDesignable class CardView: UIView { - @IBInspectable var cornerRadius: CGFloat = 2 + @IBInspectable var cornerRadius: CGFloat = 3 @IBInspectable var shadowOffsetWidth: Int = 0 - @IBInspectable var shadowOffsetHeight: Int = 2 - @IBInspectable var shadowColor: UIColor? = UIColor.black + @IBInspectable var shadowOffsetHeight: Int = 3 + @IBInspectable var shadowColor: UIColor? = UIColor(red: 0.82, green: 0.82, blue: 0.83, alpha: 1.00) @IBInspectable var shadowOpacity: Float = 0.2 override func layoutSubviews() { + self.backgroundColor = UIColor.white + layer.cornerRadius = cornerRadius let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) diff --git a/BodyweightFitness/View/Views.swift b/BodyweightFitness/View/Views.swift new file mode 100644 index 0000000..1db3861 --- /dev/null +++ b/BodyweightFitness/View/Views.swift @@ -0,0 +1,95 @@ +import SnapKit + +class TitleLabel: UILabel { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.commonInit() + } + + func commonInit() { + self.textAlignment = .left + self.font = UIFont.systemFont(ofSize: 20) + self.textColor = UIColor.black + } +} + +class ValueLabel: UILabel { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.commonInit() + } + + func commonInit() { + self.textAlignment = .left + self.font = UIFont.systemFont(ofSize: 17) + self.textColor = UIColor(red: 0.47, green: 0.47, blue: 0.47, alpha: 1.00) + } + + class func create(text: String) -> UILabel { + let label = ValueLabel() + label.text = text + return label + } +} + +class DescriptionTextView: UITextView { + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + + self.commonInit() + } + + func commonInit() { + self.textAlignment = .left + self.font = UIFont.systemFont(ofSize: 17) + self.textColor = UIColor(red: 0.47, green: 0.47, blue: 0.47, alpha: 1.00) + self.isEditable = false + self.isSelectable = false + self.isScrollEnabled = false + } +} + +class CardButton: UIButton { + // This is here only because we need to pass it to a selector method, maybe there is a better way? + var repositoryRoutine: RepositoryRoutine? + var repositoryExercise: RepositoryExercise? + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.commonInit() + } + + func commonInit() { + self.contentHorizontalAlignment = .left + self.titleLabel?.font = UIFont.systemFont(ofSize: 20) + self.setTitleColor(UIColor.primaryDark(), for: .normal) + } +} + diff --git a/BodyweightFitness/View/WorkoutChartView.swift b/BodyweightFitness/View/WorkoutChartView.swift new file mode 100644 index 0000000..5f3403b --- /dev/null +++ b/BodyweightFitness/View/WorkoutChartView.swift @@ -0,0 +1,116 @@ +import UIKit +import Charts + +class WorkoutChartView: LineChartView, ChartViewDelegate { + var workoutChartType: WorkoutChartType = .WorkoutLength + var workoutChartLength: Int = 30 + + var values: [RepositoryRoutine] = [] + + var titleLabel: UILabel? + var valueLabel: UILabel? + + var buttonBar: UIView? + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.commonInit() + } + + override init(frame: CGRect) { + super.init(frame: frame) + + self.commonInit() + } + + func commonInit() { + self.delegate = self + self.backgroundColor = UIColor.white + self.chartDescription?.enabled = false + self.dragEnabled = true + self.drawGridBackgroundEnabled = false + self.drawMarkers = false + self.legend.enabled = false + self.animate( + xAxisDuration: 1.0, + yAxisDuration: 1.0, + easingOption: .easeInSine + ) + + self.xAxis.enabled = false + self.xAxis.drawLabelsEnabled = false + self.xAxis.drawGridLinesEnabled = false + + self.leftAxis.enabled = false + self.leftAxis.drawLabelsEnabled = false + self.leftAxis.drawGridLinesEnabled = false + + self.rightAxis.enabled = false + self.rightAxis.drawLabelsEnabled = false + self.rightAxis.drawGridLinesEnabled = false + } + + func setValues() { + let dataEntriesCompanion = DataEntriesCompanion() + let dataEntries = dataEntriesCompanion.getDataEntries( + fromDate: Date(), + numberOfDays: self.workoutChartLength, + repositoryRoutines: self.values, + workoutChartType: self.workoutChartType + ) + + self.data = self.createDataSetFromDataEntries(values: dataEntries) + } + + func createDataSetFromDataEntries(values: [WorkoutDataEntry]) -> LineChartData { + let lineChartData = LineChartData() + let lineChartDataSet = LineChartDataSet(values: values, label: nil) + + lineChartDataSet.lineWidth = 1.8 + lineChartDataSet.mode = .cubicBezier + lineChartDataSet.cubicIntensity = 0 + lineChartDataSet.drawCirclesEnabled = false + lineChartDataSet.drawValuesEnabled = false + lineChartDataSet.drawHorizontalHighlightIndicatorEnabled = false + lineChartDataSet.colors = [UIColor.primary()] + + lineChartData.addDataSet(lineChartDataSet) + + return lineChartData + } + + func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) { + if let data = entry as? WorkoutDataEntry { + titleLabel?.text = data.title + valueLabel?.text = data.label + } + } + + func segmentedControlValueChanged(_ sender: UISegmentedControl) { + UIView.animate(withDuration: 0.2) { + let offset = ((sender.frame.width) / CGFloat(sender.numberOfSegments)) * CGFloat(sender.selectedSegmentIndex) + + self.buttonBar?.frame.origin.x = offset + 16 + } + + switch sender.selectedSegmentIndex { + case 0: + self.workoutChartLength = 7 + case 1: + self.workoutChartLength = 31 + case 2: + self.workoutChartLength = 93 + case 3: + self.workoutChartLength = 186 + case 4: + self.workoutChartLength = 365 + case 5: + self.workoutChartLength = 1095 + default: + self.workoutChartLength = 7 + } + + self.setValues() + } +} diff --git a/BodyweightFitness/Controller/RestTimerViewController.swift b/BodyweightFitness/Workout/RestTimerViewController.swift similarity index 100% rename from BodyweightFitness/Controller/RestTimerViewController.swift rename to BodyweightFitness/Workout/RestTimerViewController.swift diff --git a/BodyweightFitness/Controller/TimedViewController.swift b/BodyweightFitness/Workout/TimedViewController.swift similarity index 99% rename from BodyweightFitness/Controller/TimedViewController.swift rename to BodyweightFitness/Workout/TimedViewController.swift index a6f6ed6..431d53b 100644 --- a/BodyweightFitness/Controller/TimedViewController.swift +++ b/BodyweightFitness/Workout/TimedViewController.swift @@ -168,7 +168,7 @@ class TimedViewController: UIViewController, AVAudioPlayerDelegate { sets.append(repositorySet) - repositoryRoutine.lastUpdatedTime = NSDate() as Date + repositoryRoutine.lastUpdatedTime = Date() self.showNotification(loggedSeconds) self.showRestTimer() diff --git a/BodyweightFitness/Controller/WeightedViewController.swift b/BodyweightFitness/Workout/WeightedViewController.swift similarity index 99% rename from BodyweightFitness/Controller/WeightedViewController.swift rename to BodyweightFitness/Workout/WeightedViewController.swift index 7b24a78..3d7b5a2 100644 --- a/BodyweightFitness/Controller/WeightedViewController.swift +++ b/BodyweightFitness/Workout/WeightedViewController.swift @@ -176,7 +176,7 @@ class WeightedViewController: UIViewController { sets.append(repositorySet) - repositoryRoutine.lastUpdatedTime = NSDate() as Date + repositoryRoutine.lastUpdatedTime = Date() } realm.add(repositoryRoutine, update: true) diff --git a/BodyweightFitness/WorkoutLog/ProgressGeneralViewController.swift b/BodyweightFitness/WorkoutLog/ProgressGeneralViewController.swift deleted file mode 100644 index b5bc7af..0000000 --- a/BodyweightFitness/WorkoutLog/ProgressGeneralViewController.swift +++ /dev/null @@ -1,110 +0,0 @@ -import Foundation -import UIKit - -import UIKit - -class CircleGraphView: UIView { - // - // Range of 0.0 to 1.0 - // - var endArc: CGFloat = 0.0 { - didSet { - setNeedsDisplay() - } - } - - var arcWidth: CGFloat = 18.0 - var arcColor = UIColor(red:0.04, green:0.58, blue:0.58, alpha:1.00) - var arcBackgroundColor = UIColor(red:0.80, green:0.80, blue:0.80, alpha:1.00) - - override func draw(_ rect: CGRect) { - let fullCircle = 2.0 * CGFloat(Double.pi) - let start: CGFloat = -0.25 * fullCircle - let end: CGFloat = endArc * fullCircle + start - - let centerPoint = CGPoint(x: rect.midX, y: rect.midY) - - var radius: CGFloat = 0.0 - - if rect.width > rect.height { - radius = (rect.width - arcWidth) / 2.0 - } else { - radius = (rect.height - arcWidth) / 2.0 - } - - let context = UIGraphicsGetCurrentContext() - - context!.setLineWidth(arcWidth) - context!.setLineCap(CGLineCap.round) - context!.setStrokeColor(arcBackgroundColor.cgColor) - context!.addArc(center: centerPoint, radius: radius, startAngle: 0, endAngle: fullCircle, clockwise: false) - context!.strokePath() - context!.setStrokeColor(arcColor.cgColor) - context!.setLineWidth(arcWidth * 0.8 ) - context!.addArc(center: centerPoint, radius: radius, startAngle: start, endAngle: end, clockwise: false) - context!.strokePath() - } -} - -class ProgressGeneralViewController: UIViewController { - @IBOutlet var startTime: UILabel! - - @IBOutlet var endTime: UILabel! - @IBOutlet var endTimeLabel: UILabel! - - @IBOutlet var workoutLength: UILabel! - @IBOutlet var circleGraphView: CircleGraphView! - - @IBOutlet var progressValueLabel: UILabel! - @IBOutlet var progressTextLabel: UILabel! - - var parentController: UIViewController? - - var date: Date? - var repositoryRoutine: RepositoryRoutine? - - override func viewDidLoad() { - super.viewDidLoad() - - if let routine = self.repositoryRoutine { - let helper = RepositoryRoutineHelper(repositoryRoutine: routine) - - self.startTime.text = helper.getStartTime() - self.endTime.text = helper.getLastUpdatedTime() - self.workoutLength.text = helper.getWorkoutLength() - - if helper.isCompleted() { - self.endTimeLabel.text = "End Time" - self.progressTextLabel.text = "Congratulations!" - self.progressValueLabel.text = "100%" - } else { - self.endTimeLabel.text = "Last Updated Time" - - if helper.exercisesLeft() == 1 { - self.progressTextLabel.text = "1 exercise to go" - } else { - self.progressTextLabel.text = "\(helper.exercisesLeft()) exercises to go" - } - - let onePercent: Double = 1 / Double(helper.totalExercises()) * 100 - let progress: Int = Int(Double(helper.completedExercises()) * onePercent) - - self.progressValueLabel.text = "\(progress)%" - } - - self.circleGraphView.endArc = 0.0476 * CGFloat(helper.completedExercises()) - } else { - self.startTime.text = "--" - self.endTime.text = "--" - self.workoutLength.text = "--" - } - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - func secondsToHoursMinutesSeconds(_ seconds: Int) -> (Int, Int, Int) { - return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60) - } -} diff --git a/BodyweightFitness/WorkoutLog/ProgressPageViewController.swift b/BodyweightFitness/WorkoutLog/ProgressPageViewController.swift deleted file mode 100644 index 84e9c04..0000000 --- a/BodyweightFitness/WorkoutLog/ProgressPageViewController.swift +++ /dev/null @@ -1,110 +0,0 @@ -import Foundation -import UIKit - -class ProgressPageViewController: UITableViewController { - var parentController: UIViewController? - - var category: RepositoryCategory? - - override func viewDidLoad() { - super.viewDidLoad() - - self.tableView.register( - UINib(nibName: "ProgressSectionCell", bundle: nil), - forCellReuseIdentifier: "ProgressSectionCell") - - self.tableView.register( - UINib(nibName: "ProgressCardCell", bundle: nil), - forCellReuseIdentifier: "ProgressCardCell") - - self.tableView.delegate = self - self.tableView.dataSource = self - } - - override func numberOfSections(in tableView: UITableView) -> Int { - if let category = self.category { - return category.sections.count - } - - return 0 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if let category = self.category { - let exercises = category.sections[section].exercises.filter { (exercise) in - self.isVisible(exercise) - } - - return exercises.count - } - - return 0 - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 45 - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return 166 - } - - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let cell = tableView.dequeueReusableCell(withIdentifier: "ProgressSectionCell") as! ProgressSectionCell - - cell.parentController = parentController - - cell.backgroundColor = UIColor(red:0.88, green:0.88, blue:0.88, alpha:1) - cell.contentView.backgroundColor = UIColor(red:0.88, green:0.88, blue:0.88, alpha:1) - - if let category = self.category { - let section = category.sections[section] - - cell.title.text = section.title - } - - return cell - - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "ProgressCardCell", for: indexPath) as! ProgressCardCell - - cell.parentController = parentController - - if let category = self.category { - - - let exercises = category.sections[indexPath.section].exercises - - - - let filteredExercises = Array(exercises).filter { (exercise) in - self.isVisible(exercise) - } - - let exercise = filteredExercises[indexPath.row] - - cell.current = exercise - - cell.title.text = exercise.title - cell.subtitle.text = exercise.desc - } - - return cell - } - - func isVisible(_ exercise: RepositoryExercise) -> Bool { - let firstSet = exercise.sets[0] - - if (exercise.sets.count > 1) { - return true - } else { - if (firstSet.seconds > 0 || firstSet.reps > 0) { - return true - } - } - - return exercise.visible - } -} diff --git a/BodyweightFitness/WorkoutLog/WorkoutLog.storyboard b/BodyweightFitness/WorkoutLog/WorkoutLog.storyboard index a7675cf..f0bc78a 100644 --- a/BodyweightFitness/WorkoutLog/WorkoutLog.storyboard +++ b/BodyweightFitness/WorkoutLog/WorkoutLog.storyboard @@ -26,7 +26,7 @@ - + diff --git a/BodyweightFitness/WorkoutLog/WorkoutLogCategoryViewController.swift b/BodyweightFitness/WorkoutLog/WorkoutLogCategoryViewController.swift new file mode 100644 index 0000000..95c9378 --- /dev/null +++ b/BodyweightFitness/WorkoutLog/WorkoutLogCategoryViewController.swift @@ -0,0 +1,230 @@ +import SnapKit + +class WorkoutLogCategoryViewController: AbstractViewController { + var repositoryRoutine: RepositoryRoutine? + var repositoryCategory: RepositoryCategory? + + override func viewDidLoad() { + super.viewDidLoad() + + _ = RoutineStream.sharedInstance.repositoryObservable().subscribe(onNext: { (it) in + self.initializeContent() + }) + + self.initializeContent() + } + + override func initializeContent() { + super.initializeContent() + + if let repositoryCategory = self.repositoryCategory { + self.addView(self.createProgressCard(repositoryCategory: repositoryCategory)) + + // TODO: Finish Graph for Category Completion Rate + // self.addView(ValueLabel.create(text: "Category Completion Rate")) + // self.addView(self.createCompletionRateHistoryCard()) + + for repositorySection in repositoryCategory.sections { + self.addView(ValueLabel.create(text: repositorySection.title)) + + let companion = ListOfRepositoryExercisesCompanion(repositorySection.exercises) + + for repositoryExercise in companion.visibleOrCompletedExercises() { + self.addView( + createExerciseCard(repositoryExercise: repositoryExercise) + ) + } + } + } + } + + func createProgressCard(repositoryCategory: RepositoryCategory) -> CardView { + let card = CardView() + + let companion = ListOfRepositoryExercisesCompanion(repositoryCategory.exercises) + let completionRate = companion.completionRate() + + let topLeftLabel = TitleLabel() + topLeftLabel.textAlignment = .left + topLeftLabel.text = "\(companion.numberOfCompletedExercises()) out of \(companion.numberOfExercises())" + card.addSubview(topLeftLabel) + + let topLeftValue = ValueLabel() + topLeftValue.textAlignment = .left + topLeftValue.text = "Completed Exercises" + card.addSubview(topLeftValue) + + let topRightLabel = TitleLabel() + topRightLabel.textAlignment = .right + topRightLabel.text = completionRate.label + + card.addSubview(topRightLabel) + + let topRightValue = ValueLabel() + topRightValue.textAlignment = .right + topRightValue.text = "Completion Rate" + card.addSubview(topRightValue) + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 0 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + + for repositorySection in repositoryCategory.sections { + let companion = ListOfRepositoryExercisesCompanion(repositorySection.exercises) + let completionRate = companion.completionRate() + + let homeBarView = HomeBarView() + homeBarView.categoryTitle.text = repositorySection.title + homeBarView.progressView.setCompletionRate(completionRate) + homeBarView.progressRate.text = completionRate.label + + stackView.addArrangedSubview(homeBarView) + } + + topLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightLabel.snp.left) + + make.height.equalTo(24) + } + + topRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftLabel.snp.right) + + make.height.equalTo(24) + } + + topLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightValue.snp.left) + } + + topRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftValue.snp.right) + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftValue.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + } + + return card + } + + func createCompletionRateHistoryCard() -> CardView { + let card = CardView() + + let label = TitleLabel() + label.text = "12 October 2017" + card.addSubview(label) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-20) + } + + return card + } + + func createExerciseCard(repositoryExercise: RepositoryExercise) -> CardView { + let card = CardView() + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + let label = TitleLabel() + label.text = repositoryExercise.title + card.addSubview(label) + + let value = ValueLabel() + value.text = companion.setSummaryLabel() + card.addSubview(value) + + let editButton = CardButton() + editButton.repositoryExercise = repositoryExercise + editButton.setTitle("Edit", for: .normal) + editButton.addTarget(self, action: #selector(edit(_:)), for: .touchUpInside) + card.addSubview(editButton) + + let fullReportButton = CardButton() + fullReportButton.repositoryExercise = repositoryExercise + fullReportButton.setTitle("Full Report", for: .normal) + fullReportButton.addTarget(self, action: #selector(fullReport(_:)), for: .touchUpInside) + card.addSubview(fullReportButton) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + value.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(8) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + editButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(value.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + fullReportButton.snp.makeConstraints { (make) -> Void in + make.top.equalTo(value.snp.bottom).offset(16) + make.left.equalTo(editButton.snp.right).offset(16) + make.bottom.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + return card + } + + @IBAction func edit(_ sender: CardButton) { + let controller = self.parent! + + if let repositoryExercise = sender.repositoryExercise { + let logWorkoutController = LogWorkoutController() + + logWorkoutController.parentController = controller + logWorkoutController.exercise = repositoryExercise + logWorkoutController.routine = repositoryRoutine + + logWorkoutController.modalTransitionStyle = .coverVertical + logWorkoutController.modalPresentationStyle = .custom + + controller.dim(.in, alpha: 0.5, speed: 0.5) + controller.present(logWorkoutController, animated: true, completion: nil) + } + } + + @IBAction func fullReport(_ sender: CardButton) { + if let repositoryExercise = sender.repositoryExercise { + let workoutLogFullReportViewController = WorkoutLogFullReportViewController() + workoutLogFullReportViewController.repositoryExercise = repositoryExercise + + self.parent?.navigationController?.pushViewController(workoutLogFullReportViewController, animated: true) + } + } +} + diff --git a/BodyweightFitness/WorkoutLog/WorkoutLogFullReportViewController.swift b/BodyweightFitness/WorkoutLog/WorkoutLogFullReportViewController.swift new file mode 100644 index 0000000..c64250f --- /dev/null +++ b/BodyweightFitness/WorkoutLog/WorkoutLogFullReportViewController.swift @@ -0,0 +1,102 @@ +import SnapKit +import Charts +import RealmSwift + +class WorkoutLogFullReportViewController: AbstractViewController { + var repositoryExercise: RepositoryExercise? + + override func viewDidLoad() { + super.viewDidLoad() + + self.initializeContent() + } + + override func initializeContent() { + super.initializeContent() + + self.navigationItem.title = self.repositoryExercise?.title + + if let repositoryExercise = self.repositoryExercise { + self.addView(self.createFullReportCard(repositoryExercise: repositoryExercise)) + } + } + + func createLabelsView(titleText: String, subtitleText: String, valueText: String) -> UIView { + let view = UIView() + + let title = TitleLabel() + title.text = titleText + view.addSubview(title) + + let subtitle = ValueLabel() + subtitle.text = subtitleText + view.addSubview(subtitle) + + let value = ValueLabel() + value.text = valueText + view.addSubview(value) + + title.snp.makeConstraints { (make) -> Void in + make.top.equalTo(view) + make.left.equalTo(view) + make.right.equalTo(view) + } + + subtitle.snp.makeConstraints { (make) -> Void in + make.top.equalTo(title.snp.bottom).offset(8) + make.left.equalTo(view) + make.right.equalTo(view) + } + + value.snp.makeConstraints { (make) -> Void in + make.top.equalTo(subtitle.snp.bottom).offset(8) + make.left.equalTo(view) + make.right.equalTo(view) + make.bottom.equalTo(view) + } + + return view + } + + func createFullReportCard(repositoryExercise: RepositoryExercise) -> CardView { + let card = CardView() + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 16 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + + let realm = try! Realm() + let exercises = realm.objects(RepositoryExercise.self).filter("exerciseId == '\(repositoryExercise.exerciseId)'") + + let repositorySets = Array(exercises).map { $0.sets }.flatMap { $0 } + + for repositorySet in repositorySets { + if let desc = repositorySet.exercise?.routine?.startTime.description { + let companion = RepositorySetCompanion(repositorySet) + + if let index = repositorySet.exercise?.sets.index(of: repositorySet) { + stackView.addArrangedSubview( + createLabelsView( + titleText: "Set \(index + 1)", + subtitleText: "\(companion.setSummaryLabel())", + valueText: "\(desc)" + ) + ) + } + } + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + } + + return card + } +} diff --git a/BodyweightFitness/WorkoutLog/WorkoutLogGeneralViewController.swift b/BodyweightFitness/WorkoutLog/WorkoutLogGeneralViewController.swift new file mode 100644 index 0000000..70827c5 --- /dev/null +++ b/BodyweightFitness/WorkoutLog/WorkoutLogGeneralViewController.swift @@ -0,0 +1,484 @@ +import SnapKit +import Charts +import RealmSwift + +class WorkoutLogGeneralViewController: AbstractViewController { + var repositoryRoutine: RepositoryRoutine? + + override func viewDidLoad() { + super.viewDidLoad() + + _ = RoutineStream.sharedInstance.repositoryObservable().subscribe(onNext: { (it) in + self.initializeContent() + }) + + self.initializeContent() + } + + override func initializeContent() { + super.initializeContent() + + if let repositoryRoutine = self.repositoryRoutine { + self.addView(self.createStatisticsCard(repositoryRoutine: repositoryRoutine)) + self.addView(ValueLabel.create(text: "Workout Progress")) + self.addView(self.createProgressCard(repositoryRoutine: repositoryRoutine)) + self.addView(ValueLabel.create(text: "Workout Length History")) + self.addView(self.createWorkoutLengthHistoryCard(repositoryRoutine: repositoryRoutine)) + self.addView(ValueLabel.create(text: "Completion Rate History")) + self.addView(self.createCompletionRateHistoryCard(repositoryRoutine: repositoryRoutine)) + self.addView(ValueLabel.create(text: "Not Completed Exercises")) + self.addView(self.createNotCompletedExercisesCard(repositoryRoutine: repositoryRoutine)) + } + } + + func createTitleValueView(labelText: String, valueText: String) -> UIView { + let view = UIView() + + let label = TitleLabel() + label.text = labelText + view.addSubview(label) + + let value = ValueLabel() + value.text = valueText + view.addSubview(value) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(view) + make.left.equalTo(view) + make.right.equalTo(view) + } + + value.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(8) + make.left.equalTo(view) + make.right.equalTo(view) + make.bottom.equalTo(view) + } + + return view + } + + func createStatisticsCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + let topLeftLabel = TitleLabel() + topLeftLabel.textAlignment = .left + topLeftLabel.text = companion.startTime() + card.addSubview(topLeftLabel) + + let topLeftValue = ValueLabel() + topLeftValue.textAlignment = .left + topLeftValue.text = "Start Time" + card.addSubview(topLeftValue) + + let topRightLabel = TitleLabel() + topRightLabel.textAlignment = .right + topRightLabel.text = companion.lastUpdatedTime() + + card.addSubview(topRightLabel) + + let topRightValue = ValueLabel() + topRightValue.textAlignment = .right + topRightValue.text = companion.lastUpdatedTimeLabel() + card.addSubview(topRightValue) + + let bottomLeftLabel = TitleLabel() + bottomLeftLabel.textAlignment = .left + bottomLeftLabel.text = companion.workoutLength() + card.addSubview(bottomLeftLabel) + + let bottomLeftValue = ValueLabel() + bottomLeftValue.textAlignment = .left + bottomLeftValue.text = "Workout Length" + card.addSubview(bottomLeftValue) + + let bottomRightLabel = TitleLabel() + bottomRightLabel.textAlignment = .right + bottomRightLabel.text = " " + card.addSubview(bottomRightLabel) + + let bottomRightValue = ValueLabel() + bottomRightValue.textAlignment = .right + bottomRightValue.text = " " + card.addSubview(bottomRightValue) + + topLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightLabel.snp.left) + + make.height.equalTo(24) + } + + topRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftLabel.snp.right) + + make.height.equalTo(24) + } + + topLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightValue.snp.left) + } + + topRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftValue.snp.right) + } + + bottomLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftValue.snp.bottom).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(bottomRightLabel.snp.left) + + make.height.equalTo(24) + } + + bottomRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightValue.snp.bottom).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(bottomLeftLabel.snp.right) + + make.height.equalTo(24) + } + + bottomLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(bottomLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(bottomRightValue.snp.left) + + make.bottom.equalTo(card).offset(-20) + } + + bottomRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(bottomRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(bottomLeftValue.snp.right) + + make.bottom.equalTo(card).offset(-20) + } + + return card + } + + func createProgressCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let companion = ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises) + let completionRate = companion.completionRate() + + let topLeftLabel = TitleLabel() + topLeftLabel.textAlignment = .left + topLeftLabel.text = "\(companion.numberOfCompletedExercises()) out of \(companion.numberOfExercises())" + card.addSubview(topLeftLabel) + + let topLeftValue = ValueLabel() + topLeftValue.textAlignment = .left + topLeftValue.text = "Completed Exercises" + card.addSubview(topLeftValue) + + let topRightLabel = TitleLabel() + topRightLabel.textAlignment = .right + topRightLabel.text = completionRate.label + + card.addSubview(topRightLabel) + + let topRightValue = ValueLabel() + topRightValue.textAlignment = .right + topRightValue.text = "Completion Rate" + card.addSubview(topRightValue) + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 0 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + + for repositoryCategory in repositoryRoutine.categories { + let companion = ListOfRepositoryExercisesCompanion(repositoryCategory.exercises) + let completionRate = companion.completionRate() + + let homeBarView = HomeBarView() + homeBarView.categoryTitle.text = repositoryCategory.title + homeBarView.progressView.setCompletionRate(completionRate) + homeBarView.progressRate.text = completionRate.label + + stackView.addArrangedSubview(homeBarView) + } + + topLeftLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightLabel.snp.left) + + make.height.equalTo(24) + } + + topRightLabel.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftLabel.snp.right) + + make.height.equalTo(24) + } + + topLeftValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftLabel.snp.bottom).offset(8) + make.leading.equalTo(card).offset(16) + + make.right.equalTo(topRightValue.snp.left) + } + + topRightValue.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topRightLabel.snp.bottom).offset(8) + make.trailing.equalTo(card).offset(-16) + + make.left.equalTo(topLeftValue.snp.right) + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(topLeftValue.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + } + + return card + } + + func createWorkoutLengthHistoryCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let realm = try! Realm() + let allWorkouts = realm.objects(RepositoryRoutine.self) + .filter("routineId == '\(repositoryRoutine.routineId)'") + + let label = TitleLabel() + label.text = RepositoryRoutineCompanion(repositoryRoutine).date() + + let value = ValueLabel() + value.text = RepositoryRoutineCompanion(repositoryRoutine).workoutLength() + + let buttonBar = UIView() + buttonBar.backgroundColor = UIColor.primary() + + let graph = WorkoutChartView() + graph.workoutChartType = .WorkoutLength + graph.workoutChartLength = 7 + graph.values = Array(allWorkouts) + graph.titleLabel = label + graph.valueLabel = value + graph.buttonBar = buttonBar + graph.setValues() + + let segmentedControl = UISegmentedControl() + segmentedControl.addTarget(graph, action: #selector(graph.segmentedControlValueChanged(_:)), for: UIControlEvents.valueChanged) + segmentedControl.insertSegment(withTitle: "1W", at: 0, animated: true) + segmentedControl.insertSegment(withTitle: "1M", at: 1, animated: true) + segmentedControl.insertSegment(withTitle: "3M", at: 2, animated: true) + segmentedControl.insertSegment(withTitle: "6M", at: 3, animated: true) + segmentedControl.insertSegment(withTitle: "1Y", at: 4, animated: true) + segmentedControl.insertSegment(withTitle: "ALL", at: 5, animated: true) + segmentedControl.selectedSegmentIndex = 0 + segmentedControl.backgroundColor = .clear + segmentedControl.tintColor = .clear + segmentedControl.setTitleTextAttributes([ + NSFontAttributeName: UIFont.systemFont(ofSize: 16), + NSForegroundColorAttributeName: UIColor.lightGray + ], for: .normal) + segmentedControl.setTitleTextAttributes([ + NSFontAttributeName: UIFont.systemFont(ofSize: 16), + NSForegroundColorAttributeName: UIColor.primary() + ], for: .selected) + + card.addSubview(label) + card.addSubview(value) + card.addSubview(graph) + card.addSubview(segmentedControl) + card.addSubview(buttonBar) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + value.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(8) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + graph.snp.makeConstraints { (make) -> Void in + make.top.equalTo(value.snp.bottom).offset(8) + make.left.equalTo(card).offset(0) + make.right.equalTo(card).offset(0) + + make.height.equalTo(200) + } + + segmentedControl.snp.makeConstraints { (make) -> Void in + make.top.equalTo(graph.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + buttonBar.snp.makeConstraints { (make) -> Void in + make.top.equalTo(segmentedControl.snp.bottom) + make.left.equalTo(segmentedControl.snp.left) + make.bottom.equalTo(card).offset(-16) + + make.width.equalTo(segmentedControl.snp.width).multipliedBy(1 / CGFloat(segmentedControl.numberOfSegments)) + + make.height.equalTo(2) + } + + return card + } + + func createCompletionRateHistoryCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let realm = try! Realm() + let allWorkouts = realm.objects(RepositoryRoutine.self) + .filter("routineId == '\(repositoryRoutine.routineId)'") + + let label = TitleLabel() + label.text = RepositoryRoutineCompanion(repositoryRoutine).date() + + let value = ValueLabel() + value.text = ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises).completionRate().label + + let buttonBar = UIView() + buttonBar.backgroundColor = UIColor.primary() + + let graph = WorkoutChartView() + graph.workoutChartType = .CompletionRate + graph.workoutChartLength = 7 + graph.values = Array(allWorkouts) + graph.titleLabel = label + graph.valueLabel = value + graph.buttonBar = buttonBar + graph.setValues() + + let segmentedControl = UISegmentedControl() + segmentedControl.addTarget(graph, action: #selector(graph.segmentedControlValueChanged(_:)), for: UIControlEvents.valueChanged) + segmentedControl.insertSegment(withTitle: "1W", at: 0, animated: true) + segmentedControl.insertSegment(withTitle: "1M", at: 1, animated: true) + segmentedControl.insertSegment(withTitle: "3M", at: 2, animated: true) + segmentedControl.insertSegment(withTitle: "6M", at: 3, animated: true) + segmentedControl.insertSegment(withTitle: "1Y", at: 4, animated: true) + segmentedControl.insertSegment(withTitle: "ALL", at: 5, animated: true) + segmentedControl.selectedSegmentIndex = 0 + segmentedControl.backgroundColor = .clear + segmentedControl.tintColor = .clear + segmentedControl.setTitleTextAttributes([ + NSFontAttributeName: UIFont.systemFont(ofSize: 16), + NSForegroundColorAttributeName: UIColor.lightGray + ], for: .normal) + segmentedControl.setTitleTextAttributes([ + NSFontAttributeName: UIFont.systemFont(ofSize: 16), + NSForegroundColorAttributeName: UIColor.primary() + ], for: .selected) + + card.addSubview(label) + card.addSubview(value) + card.addSubview(graph) + card.addSubview(segmentedControl) + card.addSubview(buttonBar) + + label.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(20) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + value.snp.makeConstraints { (make) -> Void in + make.top.equalTo(label.snp.bottom).offset(8) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + } + + graph.snp.makeConstraints { (make) -> Void in + make.top.equalTo(value.snp.bottom).offset(8) + make.left.equalTo(card).offset(0) + make.right.equalTo(card).offset(0) + + make.height.equalTo(200) + } + + segmentedControl.snp.makeConstraints { (make) -> Void in + make.top.equalTo(graph.snp.bottom).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + + make.height.equalTo(36) + } + + buttonBar.snp.makeConstraints { (make) -> Void in + make.top.equalTo(segmentedControl.snp.bottom) + make.left.equalTo(segmentedControl.snp.left) + make.bottom.equalTo(card).offset(-16) + + make.width.equalTo(segmentedControl.snp.width).multipliedBy(1 / CGFloat(segmentedControl.numberOfSegments)).constraint + + make.height.equalTo(2) + } + + return card + } + + func createNotCompletedExercisesCard(repositoryRoutine: RepositoryRoutine) -> CardView { + let card = CardView() + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + stackView.spacing = 16 + stackView.translatesAutoresizingMaskIntoConstraints = false + card.addSubview(stackView) + + let companion = ListOfRepositoryExercisesCompanion(repositoryRoutine.exercises) + + for repositoryExercise in companion.notCompletedExercises() { + if let category = repositoryExercise.category, let section = repositoryExercise.section { + stackView.addArrangedSubview( + createTitleValueView( + labelText: "\(repositoryExercise.title)", + valueText: "\(category.title) - \(section.title)" + ) + ) + } + } + + stackView.snp.makeConstraints { (make) -> Void in + make.top.equalTo(card).offset(16) + make.left.equalTo(card).offset(16) + make.right.equalTo(card).offset(-16) + make.bottom.equalTo(card).offset(-16) + } + + return card + } +} diff --git a/BodyweightFitness/WorkoutLog/WorkoutLogViewController.swift b/BodyweightFitness/WorkoutLog/WorkoutLogViewController.swift index a806cfc..de26b93 100644 --- a/BodyweightFitness/WorkoutLog/WorkoutLogViewController.swift +++ b/BodyweightFitness/WorkoutLog/WorkoutLogViewController.swift @@ -17,28 +17,18 @@ class WorkoutLogViewController: UIViewController { self.navigationItem.title = routine.startTime.commonDescription } - let generalViewController: ProgressGeneralViewController = ProgressGeneralViewController( - nibName: "ProgressGeneralViewController", - bundle: nil) - - generalViewController.parentController = self.navigationController + let generalViewController = WorkoutLogGeneralViewController() generalViewController.title = "General" - generalViewController.date = date - generalViewController.repositoryRoutine = self.repositoryRoutine - + generalViewController.repositoryRoutine = repositoryRoutine controllerArray.append(generalViewController) - if let routine = repositoryRoutine { - for category in routine.categories { - let viewController: ProgressPageViewController = ProgressPageViewController( - nibName: "ProgressPageViewController", - bundle: nil) - - viewController.parentController = self.navigationController - viewController.title = category.title - viewController.category = category - - controllerArray.append(viewController) + if let repositoryRoutine = repositoryRoutine { + for repositoryCategory in repositoryRoutine.categories { + let categoryViewController = WorkoutLogCategoryViewController() + categoryViewController.title = repositoryCategory.title + categoryViewController.repositoryRoutine = repositoryRoutine + categoryViewController.repositoryCategory = repositoryCategory + controllerArray.append(categoryViewController) } } @@ -53,7 +43,7 @@ class WorkoutLogViewController: UIViewController { UIColor(red:0.02, green:0.21, blue:0.18, alpha:1) ), .bottomMenuHairlineColor( - UIColor(red: 70.0/255.0, green: 70.0/255.0, blue: 80.0/255.0, alpha: 1.0) + UIColor.primary() ), .selectedMenuItemLabelColor( UIColor(red:0, green:0.33, blue:0.29, alpha:1) @@ -73,8 +63,11 @@ class WorkoutLogViewController: UIViewController { pageMenu = CAPSPageMenu( viewControllers: controllerArray, frame: CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height), - pageMenuOptions: parameters) - + pageMenuOptions: parameters + ) + + self.addChildViewController(pageMenu!) self.view.addSubview(pageMenu!.view) + self.pageMenu!.didMove(toParentViewController: self) } } diff --git a/BodyweightFitnessTests/BodyweightFitnessTests.swift b/BodyweightFitnessTests/BodyweightFitnessTests.swift index e3018fa..c711767 100644 --- a/BodyweightFitnessTests/BodyweightFitnessTests.swift +++ b/BodyweightFitnessTests/BodyweightFitnessTests.swift @@ -67,9 +67,9 @@ class BodyweightFitnessTests: XCTestCase { override func setUp() { super.setUp() - - oldSchema = Routine(fileName: "TestRoutine") - newSchema = Routine(fileName: "Routine") + + oldSchema = Routine(fileName: "bodyweight_fitness_recommended_routine_unit_test") + newSchema = Routine(fileName: "bodyweight_fitness_recommended_routine") } override func tearDown() { @@ -89,8 +89,8 @@ class BodyweightFitnessTests: XCTestCase { } func testDifferentSchemaMigrationShouldSucceed() { - let newRoutine = Routine(fileName: "Routine") - let currentRoutine = Routine(fileName: "TestRoutine") + let newRoutine = Routine(fileName: "bodyweight_fitness_recommended_routine") + let currentRoutine = Routine(fileName: "bodyweight_fitness_recommended_routine_unit_test") let currentSchema = buildRoutine(currentRoutine) @@ -111,8 +111,8 @@ class BodyweightFitnessTests: XCTestCase { XCTAssert(migratedSchema.startTime == fakeSchema.startTime) XCTAssert(migratedSchema.lastUpdatedTime == fakeSchema.lastUpdatedTime) - for (_, exercise) in migratedSchema.exercises.enumerate() { - for (_, set) in exercise.sets.enumerate() { + for (_, exercise) in migratedSchema.exercises.enumerated() { + for (_, set) in exercise.sets.enumerated() { if let oldExercise = currentSchema.exercises.filter({ $0.exerciseId == exercise.exerciseId }).first { @@ -147,7 +147,7 @@ class BodyweightFitnessTests: XCTestCase { func isValidSchema(_ routine: Routine, currentSchema: TestRoutine) -> Bool { for exercise in routine.exercises { if let exercise = exercise as? Exercise { - let containsExercise = currentSchema.exercises.contains({ + let containsExercise = currentSchema.exercises.contains(where: { $0.exerciseId == exercise.exerciseId }) @@ -207,9 +207,9 @@ class BodyweightFitnessTests: XCTestCase { testSection?.sectionId = section.sectionId testSection?.title = section.title - if (section.mode == SectionMode.All) { + if (section.mode == SectionMode.all) { testSection?.mode = "all" - } else if (section.mode == SectionMode.Pick) { + } else if (section.mode == SectionMode.pick) { testSection?.mode = "pick" } else { testSection?.mode = "levels" @@ -226,7 +226,7 @@ class BodyweightFitnessTests: XCTestCase { testExercise.category = testCategory! testExercise.section = testSection! - if(exercise.section?.mode == SectionMode.All) { + if(exercise.section?.mode == SectionMode.all) { testExercise.visible = true } else { if let currentExercise = exercise.section?.currentExercise { @@ -249,3 +249,4 @@ class BodyweightFitnessTests: XCTestCase { return testRoutine } } + diff --git a/BodyweightFitnessTests/Domain/DataEntriesCompanionSpec.swift b/BodyweightFitnessTests/Domain/DataEntriesCompanionSpec.swift new file mode 100644 index 0000000..71ff464 --- /dev/null +++ b/BodyweightFitnessTests/Domain/DataEntriesCompanionSpec.swift @@ -0,0 +1,100 @@ +import Quick +import Nimble +import RealmSwift + +@testable import Bodyweight_Fitness + +class DataEntriesCompanionSpec: QuickSpec { + func mockDate(from: String) -> Date { + let dateFormatter = DateFormatter() + + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" + dateFormatter.timeZone = TimeZone(abbreviation: "GMT+0:00") + + return dateFormatter.date(from: from)! + } + + override func spec() { + describe("DataEntriesCompanion") { + context("getDataEntries()") { + it("returns data entries for last 7 days") { + let fromDate = self.mockDate(from: "2017-12-10T13:00:00Z") + + let dataEntriesCompanion = DataEntriesCompanion() + let dataEntries = dataEntriesCompanion.getDataEntries( + fromDate: fromDate, + numberOfDays: 7, + repositoryRoutines: [], + workoutChartType: .WorkoutLength + ) + + expect(dataEntries.count).to(equal(8)) + + expect(dataEntries[0].x).to(equal(0)) + expect(dataEntries[0].y).to(equal(0)) + + expect(dataEntries[1].x).to(equal(1)) + expect(dataEntries[1].y).to(equal(0)) + + expect(dataEntries[6].x).to(equal(6)) + expect(dataEntries[6].y).to(equal(0)) + } + + it("returns data entries for last 30 days") { + let fromDate = self.mockDate(from: "2017-12-10T13:00:00Z") + + let dataEntriesCompanion = DataEntriesCompanion() + let dataEntries = dataEntriesCompanion.getDataEntries( + fromDate: fromDate, + numberOfDays: 30, + repositoryRoutines: [], + workoutChartType: .WorkoutLength + ) + + expect(dataEntries.count).to(equal(31)) + + expect(dataEntries[0].x).to(equal(0)) + expect(dataEntries[0].y).to(equal(0)) + + expect(dataEntries[1].x).to(equal(1)) + expect(dataEntries[1].y).to(equal(0)) + + expect(dataEntries[29].x).to(equal(29)) + expect(dataEntries[29].y).to(equal(0)) + } + + it("returns data entries for last 7 days with logged workouts") { + let fromDate = self.mockDate(from: "2017-12-10T13:00:00Z") + + let firstRepositoryRoutine = RepositoryRoutine() + firstRepositoryRoutine.startTime = self.mockDate(from: "2017-12-09T12:00:00") + firstRepositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-12-09T12:50:00") + + let dataEntriesCompanion = DataEntriesCompanion() + let dataEntries = dataEntriesCompanion.getDataEntries( + fromDate: fromDate, + numberOfDays: 7, + repositoryRoutines: [ + firstRepositoryRoutine + ], + workoutChartType: .WorkoutLength + ) + + expect(dataEntries.count).to(equal(8)) + + expect(dataEntries[0].x).to(equal(0)) + expect(dataEntries[0].y).to(equal(0)) + + expect(dataEntries[5].x).to(equal(5)) + expect(dataEntries[5].y).to(equal(0)) + + expect(dataEntries[6].x).to(equal(6)) + expect(dataEntries[6].y).to(equal(50)) + + expect(dataEntries[7].x).to(equal(7)) + expect(dataEntries[7].y).to(equal(0)) + } + } + } + } +} diff --git a/BodyweightFitnessTests/Domain/ListOfRepositoryExercisesCompanionSpec.swift b/BodyweightFitnessTests/Domain/ListOfRepositoryExercisesCompanionSpec.swift new file mode 100644 index 0000000..95a0072 --- /dev/null +++ b/BodyweightFitnessTests/Domain/ListOfRepositoryExercisesCompanionSpec.swift @@ -0,0 +1,504 @@ +import Quick +import Nimble +import RealmSwift + +@testable import Bodyweight_Fitness + +class ListOfRepositoryExercisesCompanionSpec: QuickSpec { + override func spec() { + describe("ListOfRepositoryExercisesCompanion") { + context("numberOfExercises()") { + it("does not count invisible exercises") { + let repositoryExercise = RepositoryExercise() + repositoryExercise.visible = false + + let repositoryExercises = List() + repositoryExercises.append(repositoryExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfExercises()).to(equal(0)) + } + + it("counts visible exercises") { + let repositoryExercise = RepositoryExercise() + repositoryExercise.visible = true + + let repositoryExercises = List() + repositoryExercises.append(repositoryExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfExercises()).to(equal(1)) + } + + it("counts multiple visible exercises") { + let firstExercise = RepositoryExercise() + firstExercise.visible = true + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = false + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfExercises()).to(equal(2)) + } + } + + context("numberOfCompletedExercises()") { + it("does not count invisible exercises") { + let repositorySet = RepositorySet() + repositorySet.isTimed = true + repositorySet.seconds = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.visible = false + repositoryExercise.sets.append(repositorySet) + + let repositoryExercises = List() + repositoryExercises.append(repositoryExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfCompletedExercises()).to(equal(0)) + } + + it("does not count incomplete exercises") { + let repositorySet = RepositorySet() + repositorySet.isTimed = true + repositorySet.seconds = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.visible = true + repositoryExercise.sets.append(repositorySet) + + let repositoryExercises = List() + repositoryExercises.append(repositoryExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfCompletedExercises()).to(equal(0)) + } + + it("counts visible and completed exercises") { + let repositorySet = RepositorySet() + repositorySet.isTimed = true + repositorySet.seconds = 10 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.visible = true + repositoryExercise.sets.append(repositorySet) + + let repositoryExercises = List() + repositoryExercises.append(repositoryExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfCompletedExercises()).to(equal(1)) + } + + it("counts multiple visible and completed exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 10 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + expect(companion.numberOfCompletedExercises()).to(equal(2)) + } + } + + context("allExercisesCompleted()") { + it("should return false if number of visible exercises is bigger than number of completed exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 10 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + expect(companion.allExercisesCompleted()).to(equal(false)) + } + + it("should return true if number of visible exercises is equal to number of completed exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 10 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + expect(companion.allExercisesCompleted()).to(equal(true)) + } + } + + context("getCompletionRate()") { + it("should return 0% if number of exercises is 0") { + let repositoryExercises = List() + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(0)) + expect(completionRate.label).to(equal("0%")) + } + + it("should return 0% if number of completed exercises is 0") { + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(notCompletedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(0)) + expect(completionRate.label).to(equal("0%")) + } + + it("should return 50% if number of completed exercises is 1 out of 2") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(notCompletedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(50)) + expect(completionRate.label).to(equal("50%")) + } + + it("should return 100% if number of completed exercises is 2 out of 2") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(100)) + expect(completionRate.label).to(equal("100%")) + } + + it("should return 33% if number of completed exercises is 1 out of 3") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(notCompletedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(33)) + expect(completionRate.label).to(equal("33%")) + } + + it("should return 66% if number of completed exercises is 2 out of 3") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + + let completionRate = companion.completionRate() + + expect(completionRate.percentage).to(equal(66)) + expect(completionRate.label).to(equal("66%")) + } + } + + context("notCompletedExercises()") { + it("should return not completed and visible exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let fourthExercise = RepositoryExercise() + fourthExercise.visible = false + fourthExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + repositoryExercises.append(fourthExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let notCompletedExercises = companion.notCompletedExercises() + + expect(notCompletedExercises.count).to(equal(1)) + } + } + + context("visibleOrCompletedExercises()") { + it("should return visible or completed exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let fourthExercise = RepositoryExercise() + fourthExercise.visible = false + fourthExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + repositoryExercises.append(fourthExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let visibleOrCompletedExercises = companion.visibleOrCompletedExercises() + + expect(visibleOrCompletedExercises.count).to(equal(4)) + } + + it("should return visible exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(notCompletedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(notCompletedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let fourthExercise = RepositoryExercise() + fourthExercise.visible = false + fourthExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + repositoryExercises.append(fourthExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let visibleOrCompletedExercises = companion.visibleOrCompletedExercises() + + expect(visibleOrCompletedExercises.count).to(equal(4)) + } + + it("should return completed exercises") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 30 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = false + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = false + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let fourthExercise = RepositoryExercise() + fourthExercise.visible = false + fourthExercise.sets.append(completedSet) + + let repositoryExercises = List() + repositoryExercises.append(firstExercise) + repositoryExercises.append(secondExercise) + repositoryExercises.append(thirdExercise) + repositoryExercises.append(fourthExercise) + + let companion = ListOfRepositoryExercisesCompanion(repositoryExercises) + let visibleOrCompletedExercises = companion.visibleOrCompletedExercises() + + expect(visibleOrCompletedExercises.count).to(equal(4)) + } + } + } + } +} diff --git a/BodyweightFitnessTests/Domain/RepositoryExerciseCompanionSpec.swift b/BodyweightFitnessTests/Domain/RepositoryExerciseCompanionSpec.swift new file mode 100644 index 0000000..6a9f9bf --- /dev/null +++ b/BodyweightFitnessTests/Domain/RepositoryExerciseCompanionSpec.swift @@ -0,0 +1,295 @@ +import Quick +import Nimble +import RealmSwift + +@testable import Bodyweight_Fitness + +class RepositoryExerciseCompanionSpec: QuickSpec { + override func spec() { + describe("RepositoryExerciseCompanion") { + context("isCompleted()") { + it("is not completed when number of sets is 0") { + let repositoryExercise = RepositoryExercise() + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(false)) + } + + it("is not completed when first set is timed and time is set to 0") { + let repositorySet = RepositorySet() + repositorySet.isTimed = true + repositorySet.seconds = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(repositorySet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(false)) + } + + it("is not completed when multiple sets are timed and time is set to 0") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 0 + + let secondSet = RepositorySet() + secondSet.isTimed = true + secondSet.seconds = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + repositoryExercise.sets.append(secondSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(false)) + } + + it("is not completed when first set is weighted and repetitions are set to 0") { + let repositorySet = RepositorySet() + repositorySet.isTimed = false + repositorySet.reps = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(repositorySet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(false)) + } + + it("is not completed when multiple sets are weighted and repetitions are set to 0") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 0 + + let secondSet = RepositorySet() + secondSet.isTimed = false + secondSet.reps = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + repositoryExercise.sets.append(secondSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(false)) + } + + it("is completed when first set is timed and time is bigger than 0") { + let repositorySet = RepositorySet() + repositorySet.isTimed = true + repositorySet.seconds = 10 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(repositorySet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(true)) + } + + it("is completed when first set is weighted and repetitions are bigger than 0") { + let repositorySet = RepositorySet() + repositorySet.isTimed = false + repositorySet.reps = 1 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(repositorySet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.isCompleted()).to(equal(true)) + } + } + + context("setSummaryLabel()") { + context("timed set") { + it("returns --") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("Not Completed")) + } + + it("returns 1 Set, 1 Second") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 1 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Second")) + } + + it("returns 1 Set, 59 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 59 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 59 Seconds")) + } + + it("returns 1 Set, 1 Minute") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 60 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Minute")) + } + + it("returns 1 Set, 1 Minute, 1 Second") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 61 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Minute, 1 Second")) + } + + it("returns 1 Set, 1 Minute, 2 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 62 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Minute, 2 Seconds")) + } + + it("returns 1 Set, 2 Minutes, 40 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 160 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 2 Minutes, 40 Seconds")) + } + } + + context("weighted set") { + it("returns --") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 0 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("Not Completed")) + } + + it("returns 1 Set, 1 Rep") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 1 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Rep")) + } + + it("returns 2 Sets, 3 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 1 + + let secondSet = RepositorySet() + secondSet.isTimed = false + secondSet.reps = 2 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + repositoryExercise.sets.append(secondSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("2 Sets, 3 Reps")) + } + + it("returns 3 Sets, 5 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 1 + + let secondSet = RepositorySet() + secondSet.isTimed = false + secondSet.reps = 2 + + let thirdSet = RepositorySet() + thirdSet.isTimed = false + thirdSet.reps = 2 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + repositoryExercise.sets.append(secondSet) + repositoryExercise.sets.append(thirdSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("3 Sets, 5 Reps")) + } + + it("returns 3 Sets, 16 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 3 + + let secondSet = RepositorySet() + secondSet.isTimed = false + secondSet.reps = 3 + + let thirdSet = RepositorySet() + thirdSet.isTimed = false + thirdSet.reps = 10 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + repositoryExercise.sets.append(secondSet) + repositoryExercise.sets.append(thirdSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("3 Sets, 16 Reps")) + } + } + } + } + } +} diff --git a/BodyweightFitnessTests/Domain/RepositoryRoutineCompanionSpec.swift b/BodyweightFitnessTests/Domain/RepositoryRoutineCompanionSpec.swift new file mode 100644 index 0000000..8a9f873 --- /dev/null +++ b/BodyweightFitnessTests/Domain/RepositoryRoutineCompanionSpec.swift @@ -0,0 +1,218 @@ +import Quick +import Nimble +import RealmSwift + +@testable import Bodyweight_Fitness + +class RepositoryRoutineCompanionSpec: QuickSpec { + func mockDate(from: String) -> Date { + let dateFormatter = DateFormatter() + + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" + dateFormatter.timeZone = TimeZone(abbreviation: "GMT+0:00") + + return dateFormatter.date(from: from)! + } + + override func spec() { + describe("RepositoryRoutineCompanion") { + context("date()") { + it("should return start date in EEE, d MMMM YYYY format") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T00:00:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.date()).to(equal("Monday, 7 August 2017")) + } + } + + context("dateWithTime()") { + it("should return start date and time in EEE, d MMMM YYYY - HH:mm format") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T12:13:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.dateWithTime()).to(equal("Monday, 7 August 2017 - 13:13")) + } + } + + context("startTime()") { + it("should return start time in HH:mm format") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T12:13:21Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.startTime()).to(equal("13:13")) + } + } + + context("lastUpdatedTime()") { + it("should return last updated time in HH:mm format") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T13:13:21Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.lastUpdatedTime()).to(equal("14:13")) + } + } + + context("lastUpdatedTimeLabel()") { + it("should return 'End Time' if all exercises are completed") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 10 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(completedSet) + + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.exercises.append(firstExercise) + repositoryRoutine.exercises.append(secondExercise) + repositoryRoutine.exercises.append(thirdExercise) + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.lastUpdatedTimeLabel()).to(equal("End Time")) + } + + it("should return 'Last Updated Time' if not all exercises are completed") { + let completedSet = RepositorySet() + completedSet.isTimed = true + completedSet.seconds = 10 + + let notCompletedSet = RepositorySet() + notCompletedSet.isTimed = true + notCompletedSet.seconds = 0 + + let firstExercise = RepositoryExercise() + firstExercise.visible = true + firstExercise.sets.append(completedSet) + + let secondExercise = RepositoryExercise() + secondExercise.visible = true + secondExercise.sets.append(completedSet) + + let thirdExercise = RepositoryExercise() + thirdExercise.visible = true + thirdExercise.sets.append(notCompletedSet) + + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.exercises.append(firstExercise) + repositoryRoutine.exercises.append(secondExercise) + repositoryRoutine.exercises.append(thirdExercise) + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.lastUpdatedTimeLabel()).to(equal("Last Updated Time")) + } + } + + context("workoutLength()") { + it("should return 50m") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T13:50:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLength()).to(equal("50m")) + } + + it("should return 1h") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T14:00:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLength()).to(equal("1h")) + } + + it("should return 1h 10m") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T14:10:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLength()).to(equal("1h 10m")) + } + + it("should return 3h 35m") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T16:35:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLength()).to(equal("3h 35m")) + } + + it("should return -- for workout length less than 10m") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T13:09:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLength()).to(equal("--")) + } + } + + context("workoutLengthInMinutes()") { + it("should return 50") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T13:50:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLengthInMinutes()).to(equal(50)) + } + + it("should return 60") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T14:00:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLengthInMinutes()).to(equal(60)) + } + + it("should return 70") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T14:10:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLengthInMinutes()).to(equal(70)) + } + + it("should return 215") { + let repositoryRoutine = RepositoryRoutine() + repositoryRoutine.startTime = self.mockDate(from: "2017-08-07T13:00:00Z") + repositoryRoutine.lastUpdatedTime = self.mockDate(from: "2017-08-07T16:35:00Z") + + let companion = RepositoryRoutineCompanion(repositoryRoutine) + + expect(companion.workoutLengthInMinutes()).to(equal(215)) + } + } + } + } +} diff --git a/BodyweightFitnessTests/Domain/RepositorySetCompanionSpec.swift b/BodyweightFitnessTests/Domain/RepositorySetCompanionSpec.swift new file mode 100644 index 0000000..7d60439 --- /dev/null +++ b/BodyweightFitnessTests/Domain/RepositorySetCompanionSpec.swift @@ -0,0 +1,140 @@ +import Quick +import Nimble +import RealmSwift + +@testable import Bodyweight_Fitness + +class RepositorySetCompanionSpec: QuickSpec { + override func spec() { + describe("RepositorySetCompanion") { + context("setSummaryLabel()") { + context("timed set") { + it("returns Not Completed") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 0 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("Not Completed")) + } + + it("returns 1 Second") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 1 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("1 Second")) + } + + it("returns 59 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 59 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("59 Seconds")) + } + + it("returns 1 Minute") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 60 + + let repositoryExercise = RepositoryExercise() + repositoryExercise.sets.append(firstSet) + + let companion = RepositoryExerciseCompanion(repositoryExercise) + + expect(companion.setSummaryLabel()).to(equal("1 Set, 1 Minute")) + } + + it("returns 1 Minute, 1 Second") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 61 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("1 Minute, 1 Second")) + } + + it("returns 1 Minute, 2 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 62 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("1 Minute, 2 Seconds")) + } + + it("returns 2 Minutes, 40 Seconds") { + let firstSet = RepositorySet() + firstSet.isTimed = true + firstSet.seconds = 160 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("2 Minutes, 40 Seconds")) + } + } + + context("weighted set") { + it("returns Not Completed") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 0 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("Not Completed")) + } + + it("returns 1 Rep") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 1 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("1 Rep")) + } + + it("returns 3 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 3 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("3 Reps")) + } + + it("returns 5 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 5 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("5 Reps")) + } + + it("returns 3 Sets, 16 Reps") { + let firstSet = RepositorySet() + firstSet.isTimed = false + firstSet.reps = 16 + + let companion = RepositorySetCompanion(firstSet) + + expect(companion.setSummaryLabel()).to(equal("16 Reps")) + } + } + } + } + } +} diff --git a/BodyweightFitnessTests/Info.plist b/BodyweightFitnessTests/Info.plist index ba72822..6c40a6c 100644 --- a/BodyweightFitnessTests/Info.plist +++ b/BodyweightFitnessTests/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -16,8 +16,6 @@ BNDL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleVersion 1 diff --git a/Podfile b/Podfile index 3f81f19..2e1e2c9 100644 --- a/Podfile +++ b/Podfile @@ -1,18 +1,40 @@ # Uncomment the next line to define a global platform for your project -platform :ios, '9.0' +platform :ios, '10.0' -target 'Bodyweight Fitness' do +target 'BodyweightFitness' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for Bodyweight Fitness - pod 'RealmSwift' + pod 'RealmSwift', '~> 3.0.2' pod 'SwiftyJSON' - pod 'RxSwift', '~> 3.0' - pod 'RxCocoa', '~> 3.0' + pod 'RxSwift', '~> 3.0' + pod 'RxCocoa', '~> 3.0' pod 'JTAppleCalendar', '~> 7.0' - pod 'Eureka', '~> 4.0.1' - + pod 'Eureka', '~> 4.0.1' + pod 'Charts', '~> 3.0.4' + pod 'SnapKit', '~> 3.2.0' pod 'Fabric' pod 'Crashlytics' + + target 'BodyweightFitnessTests' do + pod 'Quick' + pod 'Nimble' + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + if ['Eureka'].include? target.name + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '4.0' + end + end + + if ['Charts'].include? target.name + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = '4.0' + end + end + end end