├── .gitignore ├── HealthApp.config ├── HealthApp.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ ├── mi23738.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── moisescordova.xcuserdatad │ │ ├── Bookmarks │ │ └── bookmarks.plist │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── mi23738.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── moisescordova.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── HealthApp ├── CollectionViewCells │ ├── DataCellCollectionViewCell.swift │ ├── DataCollectionViewCell.swift │ ├── DetailCollectionViewCell.swift │ ├── DoctorCollectionViewCell.swift │ ├── PictureNameCollectionViewCell.swift │ ├── ProfileCollectionViewCell.swift │ └── SingleLabelCollectionViewCell.swift ├── Delegates │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Models │ ├── Patient │ │ ├── Config.swift │ │ ├── HealthKitManager.swift │ │ └── Person.swift │ └── Shared │ │ ├── Appointment.swift │ │ ├── CacheImageView.swift │ │ ├── Doctor.swift │ │ ├── KeychainManager.swift │ │ ├── Patient.swift │ │ ├── RequestManager.swift │ │ ├── Schedule.swift │ │ └── SessionManager.swift ├── Storyboards │ ├── Base.lproj │ │ ├── Dashboard~.storyboard │ │ └── TabBar~.storyboard │ ├── Doctor │ │ ├── DoctorDashboard.storyboard │ │ ├── DoctorSettings.storyboard │ │ ├── PatientProfile.storyboard │ │ └── Patients.storyboard │ ├── HealthApp │ │ └── Storyboards │ │ │ └── Base.lproj │ │ │ ├── History~.storyboard │ │ │ ├── Profile~.storyboard │ │ │ └── Recommendation~.storyboard │ ├── Patient │ │ ├── Appointments.storyboard │ │ ├── Base.lproj │ │ │ └── LaunchScreen.storyboard │ │ ├── Doctors.storyboard │ │ └── HealthApp │ │ │ └── Storyboards │ │ │ ├── Base.lproj │ │ │ ├── Dashboard.storyboard │ │ │ └── TabBar.storyboard │ │ │ └── HealthApp │ │ │ └── Storyboards │ │ │ └── Base.lproj │ │ │ ├── History.storyboard │ │ │ ├── Profile.storyboard │ │ │ └── Recommendation.storyboard │ └── Shared │ │ ├── Base.lproj │ │ └── Main.storyboard │ │ └── Login.storyboard ├── TableViewCells │ ├── AppointmentTableViewCell.swift │ ├── ButtonCellTableViewCell.swift │ ├── CollectionViewTableViewCell.swift │ ├── ColorTableViewCell.swift │ ├── DataTableViewCell.swift │ ├── DatePickerTableViewCell.swift │ ├── DateTimeTableViewCell.swift │ ├── DualSelectionTableViewCell.swift │ ├── HistoryRecordsTableViewCell.swift │ ├── IconLabelTableViewCell.swift │ ├── ImageTableViewCell.swift │ ├── InlineDatePickerTableViewCell.swift │ ├── LabelButtonTableViewCell.swift │ ├── ProfileHorizontalTableViewCell.swift │ ├── ProfileImageTableViewCell.swift │ ├── ScheduleTableViewCell.swift │ ├── TextFieldTableViewCell.swift │ └── TextViewTableViewCell.swift ├── Utilities │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ └── icon.png │ │ ├── BlurBackground.imageset │ │ │ ├── BlurBackground 1.png │ │ │ ├── BlurBackground.png │ │ │ ├── BlurBackground@2x 1.png │ │ │ ├── BlurBackground@2x.png │ │ │ ├── BlurBackground@3x 1.png │ │ │ ├── BlurBackground@3x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Dark Blue.colorset │ │ │ └── Contents.json │ │ ├── Dashboard Green.colorset │ │ │ └── Contents.json │ │ ├── Dashboard Pink.colorset │ │ │ └── Contents.json │ │ ├── Dashboard fruits │ │ │ ├── Contents.json │ │ │ ├── Eat.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Eat.png │ │ │ │ ├── Eat@2x.png │ │ │ │ └── Eat@3x.png │ │ │ ├── Exercise.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Exercise.png │ │ │ │ ├── Exercise@2x.png │ │ │ │ └── Exercise@3x.png │ │ │ ├── Hearth.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Hearth.png │ │ │ │ ├── Hearth@2x.png │ │ │ │ └── Hearth@3x.png │ │ │ ├── Nevus.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Nevus.png │ │ │ │ ├── Nevus@2x.png │ │ │ │ └── Nevus@3x.png │ │ │ ├── Sleep.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── Sleep.png │ │ │ │ ├── Sleep@2x.png │ │ │ │ └── Sleep@3x.png │ │ │ └── weight.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── weight.png │ │ │ │ ├── weight@2x.png │ │ │ │ └── weight@3x.png │ │ ├── Favourite Colors │ │ │ ├── Contents.json │ │ │ ├── Favourite 1.colorset │ │ │ │ └── Contents.json │ │ │ ├── Favourite 2.colorset │ │ │ │ └── Contents.json │ │ │ └── Favourite 3.colorset │ │ │ │ └── Contents.json │ │ ├── Helpers │ │ │ ├── Contents.json │ │ │ ├── doctor.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── doctor.png │ │ │ ├── doctor2.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── doctor2.png │ │ │ └── profile.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── profile.jpg │ │ ├── hearth_icon.imageset │ │ │ ├── Contents.json │ │ │ ├── hearth_icon.png │ │ │ ├── hearth_icon@2x.png │ │ │ └── hearth_icon@3x.png │ │ ├── placeholder.imageset │ │ │ ├── Contents.json │ │ │ └── placeholder.png │ │ └── star-backround.imageset │ │ │ ├── Contents.json │ │ │ ├── star_backround.png │ │ │ ├── star_backround@2x.png │ │ │ └── star_backround@3x.png │ ├── Constants.swift │ ├── FitzpatrickScale.mlmodel │ ├── HealthApp.entitlements │ ├── Info.plist │ ├── Mocks │ │ ├── Appointments.json │ │ ├── Doctors.json │ │ ├── Original Mocks │ │ │ ├── Appointments.json │ │ │ ├── Doctors.json │ │ │ └── Users.json │ │ └── Users.json │ ├── Profile Pictures │ │ ├── Alexander │ │ │ ├── Alerxander-sqaure.png │ │ │ └── Alexander-vertical.png │ │ ├── Allison │ │ │ ├── Allison-square.png │ │ │ └── Allison-vertical.png │ │ ├── Carlos │ │ │ ├── Carlos-square.png │ │ │ └── Carlos-vertical.png │ │ ├── Emily │ │ │ ├── Emily-square.png │ │ │ └── Emily-vertical.png │ │ ├── Juan Gabriel │ │ │ ├── Juan Gabriel-square.png │ │ │ └── Juan Gabriel-vertical.png │ │ └── Sophia │ │ │ ├── Sophia-square.png │ │ │ └── Sophia-vertical.png │ └── Util.swift └── ViewControllers │ ├── AppointmentConflictViewController.swift │ ├── Doctor │ ├── DoctorDashboardViewController.swift │ ├── DoctorSettingsViewController.swift │ ├── PatientProfileViewController.swift │ ├── PatientsViewController.swift │ └── SchedulesViewController.swift │ ├── Initializers │ ├── Patient │ │ ├── AddDoctorViewController.swift │ │ ├── PrimaryTableViewController.swift │ │ ├── SplitViewController.swift │ │ └── TabBarViewController.swift │ └── Shared │ │ ├── AutenticationViewController.swift │ │ ├── InitialViewController.swift │ │ └── LoginViewController.swift │ ├── Patient │ ├── Appointments │ │ ├── AppointmentViewController.swift │ │ └── AppointmentsViewController.swift │ ├── Dashboard │ │ ├── DashboardViewController.swift │ │ ├── FilterViewController.swift │ │ ├── HistoryViewController.swift │ │ ├── PermissionsViewController.swift │ │ └── SleepHistoryViewController.swift │ ├── Doctors │ │ ├── BookAppointmentViewController.swift │ │ ├── DoctorProfileViewController.swift │ │ └── DoctorsViewController.swift │ ├── Ingested Food │ │ ├── CaloriesSummaryViewController.swift │ │ └── RecommendationViewController.swift │ └── Profile │ │ ├── FitzpatrickExplanationViewController.swift │ │ ├── FitzpatrickViewController.swift │ │ └── ProfileViewController.swift │ └── Shared │ ├── Camera Scanning │ ├── QRView.swift │ └── ScannerView.swift │ ├── SwiftUI │ └── LineChart.swift │ └── Utilities │ └── FloatingAlertView.swift ├── README Files ├── Appointment.png ├── Appointments.png ├── Created Appointment.png ├── Dashboard.png ├── Doctor Profile.png ├── Fitzpatrick Explanation.png ├── Fitzpatrick Scan.png ├── Fitzpatrick.png ├── History.png ├── Logo.png ├── My Doctors.png ├── Nevus.png ├── Profile.png ├── QR.png └── Sleep.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | HealthApp/HealthApp.entitlements 2 | HealthApp/Utilities/keys.plist 3 | HealthApp.xcodeproj/project.xcworkspace/xcuserdata/moisescordova.xcuserdatad/UserInterfaceState.xcuserstate 4 | Raw data training/ 5 | HealthApp.xcodeproj/project.xcworkspace/xcuserdata/moisescordova.xcuserdatad/UserInterfaceState.xcuserstate 6 | -------------------------------------------------------------------------------- /HealthApp.config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp.config -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "8f41067740b6399ffc93220974bde95c34fe4fe411e2dc043beb699efc0e7d84", 3 | "pins" : [ 4 | { 5 | "identity" : "iqkeyboardmanager", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/hackiftekhar/IQKeyboardManager.git", 8 | "state" : { 9 | "revision" : "aa656e1f0d95f0154622f453599318602848be3b", 10 | "version" : "7.0.1" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/xcuserdata/mi23738.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp.xcodeproj/project.xcworkspace/xcuserdata/mi23738.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/xcuserdata/moisescordova.xcuserdatad/Bookmarks/bookmarks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | top-level-items 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/project.xcworkspace/xcuserdata/moisescordova.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp.xcodeproj/project.xcworkspace/xcuserdata/moisescordova.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /HealthApp.xcodeproj/xcuserdata/mi23738.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/xcuserdata/mi23738.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HealthApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/xcuserdata/moisescordova.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /HealthApp.xcodeproj/xcuserdata/moisescordova.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HealthApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/DataCellCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataCellCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DataCellCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var imageView: UIImageView! 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var valueLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | contentView.layer.cornerRadius = 10.0 17 | } 18 | 19 | func customizeCell(image: UIImage, title: String, value: String, color: UIColor) { 20 | self.imageView.image = image 21 | self.titleLabel.text = title 22 | self.valueLabel.text = value 23 | self.contentView.backgroundColor = color 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/DataCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DataCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var customBackground: UIView! 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var customImageView: CacheImageView! 14 | @IBOutlet weak var valueLabel: UILabel! 15 | 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | 20 | self.customBackground.layer.cornerRadius = 8.0 21 | } 22 | 23 | func customizeCell(title: String, value: String, image: UIImage) { 24 | self.titleLabel.text = title 25 | self.customImageView.image = image 26 | self.valueLabel.text = value 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/DetailCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-12. 6 | // 7 | 8 | import UIKit 9 | 10 | class DetailCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var titleLabel: UILabel! 12 | @IBOutlet weak var detailLabel: UILabel! 13 | 14 | func customizeCell(title: String, detail: String) { 15 | self.titleLabel.text = title 16 | self.detailLabel.text = detail 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/DoctorCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoctorCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DoctorCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var profileImageView: CacheImageView! 12 | @IBOutlet weak var specializationLabel: UILabel! 13 | @IBOutlet weak var clockIcon: UIImageView! 14 | @IBOutlet weak var dateLabel: UILabel! 15 | @IBOutlet weak var nameLabel: UILabel! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | self.contentView.layer.cornerRadius = 8.0 20 | guard let profileImageView else { return } 21 | profileImageView.layer.cornerRadius = 8.0 22 | } 23 | 24 | func cutomizeCell(doctor: Doctor) { 25 | self.profileImageView.loadImageFrom(url: doctor.profilePicture) 26 | self.specializationLabel.text = doctor.specialization 27 | self.nameLabel.text = doctor.firstName 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/PictureNameCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PictureNameCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-12. 6 | // 7 | 8 | import UIKit 9 | 10 | class PictureNameCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var pictureImageView: CacheImageView! 12 | @IBOutlet weak var nameLabel: UILabel! 13 | 14 | func customizeCell(pictureURL: URL?, name: String) { 15 | self.pictureImageView.loadImageFrom(url: pictureURL) 16 | self.nameLabel.text = name 17 | self.pictureImageView.setCircularImage() 18 | self.pictureImageView.layer.borderWidth = 4 19 | self.pictureImageView.layer.borderColor = UIColor.white.cgColor 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/ProfileCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class ProfileCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var profileImageView: CacheImageView! 12 | @IBOutlet weak var nameLabel: UILabel! 13 | @IBOutlet weak var biologicalSexLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | guard let profileImageView else { return } 18 | profileImageView.layer.cornerRadius = profileImageView.frame.width / 2 19 | profileImageView.clipsToBounds = true 20 | } 21 | 22 | func customizeCell(image: UIImage, name: String, biologicalSex: String) { 23 | self.profileImageView.image = image 24 | self.nameLabel.text = name 25 | self.biologicalSexLabel.text = biologicalSex 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /HealthApp/CollectionViewCells/SingleLabelCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SingleLabelCollectionViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class SingleLabelCollectionViewCell: UICollectionViewCell { 11 | @IBOutlet weak var label: UILabel! 12 | 13 | func customizeCell(text: String, selected: Bool) { 14 | self.label.text = text 15 | self.contentView.backgroundColor = selected ? .systemRed : .secondaryLabel 16 | self.layoutSubviews() 17 | } 18 | 19 | func updateSelected() { 20 | self.contentView.backgroundColor = self.contentView.backgroundColor == .secondaryLabel ? .red : .secondaryLabel 21 | } 22 | 23 | override func layoutSubviews() { 24 | super.layoutSubviews() 25 | self.contentView.layer.cornerRadius = contentView.frame.size.width / 2 26 | self.contentView.layer.masksToBounds = true 27 | let enabled = Bool.random() 28 | 29 | if enabled { 30 | self.contentView.backgroundColor = .red 31 | self.label.textColor = .white 32 | } else { 33 | self.contentView.backgroundColor = .lightGray 34 | self.label.textColor = .black 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HealthApp/Delegates/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | import IQKeyboardManagerSwift 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | self.checkForFreshInstallation() 16 | IQKeyboardManager.shared.enable = true 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | private func checkForFreshInstallation() { 35 | let defaults = UserDefaults.standard 36 | guard !defaults.bool(forKey: "hasLaunchedBefore") else { return } 37 | _ = KeychainManager.shared.delete(identifier: "currentUserSession") 38 | defaults.set(true, forKey: "hasLaunchedBefore") 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /HealthApp/Delegates/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | private var savedURL: URL? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | let storyboard = UIStoryboard(name: Constants.Storyboard.main, bundle: nil) 18 | 19 | if let initialViewController = storyboard.instantiateInitialViewController() { 20 | window = UIWindow(windowScene: windowScene) 21 | window?.rootViewController = initialViewController 22 | window?.makeKeyAndVisible() 23 | } 24 | 25 | guard let urlContext = connectionOptions.urlContexts.first else { return } 26 | self.savedURL = urlContext.url 27 | 28 | } 29 | 30 | func sceneDidDisconnect(_ scene: UIScene) { 31 | // Called as the scene is being released by the system. 32 | // This occurs shortly after the scene enters the background, or when its session is discarded. 33 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 34 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 35 | } 36 | 37 | func sceneDidBecomeActive(_ scene: UIScene) { 38 | guard let url = self.savedURL else { return } 39 | self.handleURL(url) 40 | self.savedURL = nil 41 | } 42 | 43 | func sceneWillResignActive(_ scene: UIScene) { 44 | // Called when the scene will move from an active state to an inactive state. 45 | // This may occur due to temporary interruptions (ex. an incoming phone call). 46 | } 47 | 48 | func sceneWillEnterForeground(_ scene: UIScene) { 49 | // Called as the scene transitions from the background to the foreground. 50 | // Use this method to undo the changes made on entering the background. 51 | } 52 | 53 | func sceneDidEnterBackground(_ scene: UIScene) { 54 | // Called as the scene transitions from the foreground to the background. 55 | // Use this method to save data, release shared resources, and store enough scene-specific state information 56 | // to restore the scene back to its current state. 57 | } 58 | 59 | func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { 60 | guard let url = URLContexts.first?.url else { return } 61 | self.handleURL(url) 62 | } 63 | 64 | private func handleURL(_ url: URL) { 65 | guard var topController = window?.rootViewController else { return } 66 | let storyboard = UIStoryboard(name: Constants.Storyboard.main, bundle: nil) 67 | let addDoctorVC = storyboard.instantiateViewController(identifier: Constants.ViewIdentifiers.addDoctorVC) 68 | while let presentedViewController = topController.presentedViewController { 69 | topController = presentedViewController 70 | } 71 | topController.present(addDoctorVC, animated: true, completion: nil) 72 | } 73 | 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /HealthApp/Models/Patient/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 03/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | class Config { 11 | static func getPropertieListValue(forKey key: String, in prpertyList: String) -> String? { 12 | guard let filePath = Bundle.main.path(forResource: prpertyList, ofType: "plist"), 13 | let plist = NSDictionary(contentsOfFile: filePath), 14 | let value = plist.object(forKey: key) as? String else { 15 | return nil 16 | } 17 | return value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /HealthApp/Models/Patient/Person.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Person.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 30/12/23. 6 | // 7 | 8 | import Foundation 9 | import HealthKit 10 | import UIKit 11 | 12 | struct Person { 13 | var name: String 14 | var age: Int 15 | var biologicalSex: String 16 | var wheelCharUse: Bool 17 | var fitzpatrickSkinType: (type: String, color: UIColor) 18 | var bloodYpe: String 19 | } 20 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/Appointment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Appointment.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-11. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Appointment: Codable { 11 | let id: Int 12 | let doctor: Doctor 13 | let patient: Patient 14 | let date: Date 15 | let notes: String 16 | } 17 | 18 | struct AppointmentsContainer: Codable { 19 | let appointments: [Appointment] 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/CacheImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CacheImageView.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-11. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | let imageCache = NSCache() 12 | 13 | class CacheImageView: UIImageView { 14 | 15 | var imageUrlString: String? 16 | 17 | func loadImageFrom(url: URL?) { 18 | guard let url else { return } 19 | self.image = nil 20 | 21 | if let imageFromCache = imageCache.object(forKey: url.absoluteString as AnyObject) as? UIImage { 22 | self.image = imageFromCache 23 | return 24 | } 25 | 26 | URLSession.shared.dataTask(with: url) { (data, response, error) in 27 | guard error == nil else { return } 28 | DispatchQueue.main.async { 29 | guard let data, 30 | let imageToCache = UIImage(data: data) else { return } 31 | imageCache.setObject(imageToCache, forKey: url.absoluteString as AnyObject) 32 | self.image = imageToCache 33 | } 34 | }.resume() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/Doctor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Doctor.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-10. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Doctor: Codable { 11 | enum CodingKeys: String, CodingKey { 12 | case id, firstName, lastName, profilePicture, backgroundImage, description, specialization, schedules 13 | } 14 | let id: Int 15 | let firstName: String 16 | let lastName: String 17 | let profilePicture: URL? 18 | let backgroundImage: URL? 19 | let description: String? 20 | let specialization: String 21 | let schedules: [Schedule]? 22 | 23 | var fullName: String { 24 | "\(self.firstName) \(self.lastName)" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/KeychainManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainManager.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-10. 6 | // 7 | 8 | import Foundation 9 | import Security 10 | 11 | class KeychainManager { 12 | static let shared = KeychainManager() 13 | 14 | private init() {} 15 | 16 | func save(data: Data, identifier: String) -> HTTPStatusCode { 17 | let query = [ 18 | kSecClass as String: kSecClassGenericPassword as String, 19 | kSecAttrAccount as String: identifier, 20 | kSecValueData as String: data] as [String: Any] 21 | 22 | SecItemDelete(query as CFDictionary) 23 | let status = SecItemAdd(query as CFDictionary, nil) 24 | 25 | return status != errSecSuccess ? .localError : .success 26 | } 27 | 28 | func save(dictionary: Dictionary, identifier: String) -> HTTPStatusCode { 29 | guard let data = try? JSONSerialization.data(withJSONObject: dictionary, options: []) else { return .localError } 30 | return self.save(data: data, identifier: identifier) 31 | } 32 | 33 | func retrieve(identifier: String) -> [String: Any]? { 34 | let query = [ 35 | kSecClass as String: kSecClassGenericPassword as String, 36 | kSecAttrAccount as String: identifier, 37 | kSecReturnData as String: kCFBooleanTrue!, 38 | kSecMatchLimit as String: kSecMatchLimitOne] as [String: Any] 39 | 40 | var item: AnyObject? 41 | let status = SecItemCopyMatching(query as CFDictionary, &item) 42 | 43 | guard status == noErr, 44 | let data = item as? Data, 45 | let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? Dictionary else { return nil } 46 | return dictionary 47 | 48 | } 49 | 50 | func delete(identifier: String) -> HTTPStatusCode { 51 | let query = [ 52 | kSecClass as String: kSecClassGenericPassword as String, 53 | kSecAttrAccount as String: identifier] as [String: Any] 54 | 55 | let status = SecItemDelete(query as CFDictionary) 56 | return status != errSecSuccess && status != errSecItemNotFound ? .localError : .success 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/Patient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Patient.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-03-17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CaloriesBurned: Codable { 11 | let date: Date 12 | let calories: Int 13 | } 14 | 15 | struct HeartRate: Codable { 16 | let BPM: Int 17 | let date: Date 18 | } 19 | 20 | struct Weight: Codable { 21 | let date: Date 22 | let kilograms: Int 23 | } 24 | 25 | struct IngestedFood: Codable { 26 | let date: Date 27 | let foodName: String 28 | let calories: Int 29 | } 30 | 31 | struct Sleep: Codable { 32 | let endDate: Date 33 | let startDate: Date 34 | } 35 | 36 | struct HealthDataStructure: Codable { 37 | let caloriesBurned: [CaloriesBurned] 38 | let hearthBPM: [HeartRate] 39 | let weight: [Weight] 40 | let ingestedFood: [IngestedFood] 41 | let sleep: [Sleep] 42 | } 43 | 44 | struct Patient: Codable { 45 | let id: Int 46 | let firstName: String 47 | let lastName: String 48 | let healthData: HealthDataStructure? 49 | let profilePicture: URL? 50 | let age: Int 51 | let weight: Int 52 | let height: Int 53 | let biologicalSex: String 54 | 55 | var fullName: String { 56 | "\(self.firstName) \(self.lastName)" 57 | } 58 | 59 | enum CodingKeys: String, CodingKey { 60 | case id, firstName, lastName, healthData, profilePicture, age, weight, height, biologicalSex 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/RequestManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestManager.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 03/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum EndPoint: String { 11 | case login = "login" 12 | case doctors = "doctors" 13 | case patients = "patients" 14 | case appointment = "appointment" 15 | case appointments = "appointments" 16 | case bookApointment = "bookAppointment" 17 | } 18 | 19 | enum User: String { 20 | case none 21 | case doctor 22 | case patient 23 | } 24 | 25 | enum HTTPStatusCode: Int { 26 | case success = 200 27 | case created = 201 28 | case empty = 204 29 | case unauthorized = 401 30 | case conflict = 409 31 | case localError = 900 32 | } 33 | 34 | fileprivate struct RequestManagerIdentifier { 35 | let baseURL = "https://api.healthApp.local/" 36 | let sessionToken = "sessionToken" 37 | let patient = "patient" 38 | let userTyoe = "userType" 39 | let doctor = "doctor" 40 | } 41 | 42 | class RequestManager { 43 | static let shared = RequestManager() 44 | private let identifiers = RequestManagerIdentifier() 45 | let baseURL: String 46 | 47 | enum HTTPMethod: String { 48 | case get = "GET" 49 | case post = "POST" 50 | case patch = "PATCH" 51 | case delete = "DELETE" 52 | } 53 | 54 | private init() { 55 | self.baseURL = self.identifiers.baseURL 56 | } 57 | 58 | private func getURLWithIdFor(user: User, endpoint: EndPoint) -> URL? { 59 | guard var components = URLComponents(string: baseURL), 60 | let userId = user == .patient ? SessionManager.shared.getPatientId() : SessionManager.shared.getDoctorId() else { return nil } 61 | let endpointPath = endpoint.rawValue 62 | let fullPath = "\(userId)/\(endpointPath)" 63 | components.path = (components.path) + fullPath 64 | return components.url 65 | } 66 | 67 | private func getHTTPStatusCodeFrom(statusCode: Int) -> HTTPStatusCode { 68 | guard let responseCode = HTTPStatusCode(rawValue: statusCode) else { return .localError } 69 | return responseCode 70 | } 71 | 72 | private func makeRequest(url: URL? = nil, endPoint: EndPoint? = nil, method: HTTPMethod, body: [String: Any]? = nil, headers: [String: String]? = nil) async -> (httpStatusCode: HTTPStatusCode, body: Dictionary?, rawData: Data?) { 73 | guard let url = url ?? URL(string: self.baseURL + (endPoint?.rawValue ?? .empty)) else { return (.localError, nil, nil) } 74 | var request = URLRequest(url: url) 75 | var headers = headers 76 | 77 | request.httpMethod = method.rawValue 78 | headers = headers == nil ? [:] : headers 79 | 80 | if SessionManager.shared.isLoggedIn { 81 | guard let sessionToken = SessionManager.shared.getSessionToken() else { 82 | SessionManager.shared.logOut() 83 | return (.localError, nil, nil) 84 | } 85 | headers?[self.identifiers.sessionToken] = sessionToken 86 | headers?[self.identifiers.userTyoe] = SessionManager.shared.getPatientId() != nil ? self.identifiers.patient : self.identifiers.doctor 87 | } 88 | 89 | headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } 90 | 91 | if let body { 92 | request.httpBody = try? JSONSerialization.data(withJSONObject: body) 93 | } 94 | 95 | do { 96 | let (data, response) = try await URLSession.shared.data(for: request) 97 | guard let httpResponse = response as? HTTPURLResponse else { 98 | return (.localError, nil, nil) 99 | } 100 | 101 | let statusCode = httpResponse.statusCode 102 | guard self.getHTTPStatusCodeFrom(statusCode: statusCode) != .unauthorized else { 103 | SessionManager.shared.logOut() 104 | return (.unauthorized, nil, nil) 105 | } 106 | 107 | let responseBody = try? JSONSerialization.jsonObject(with: data) as? Dictionary 108 | 109 | return (self.getHTTPStatusCodeFrom(statusCode: statusCode), responseBody, data) 110 | } catch { 111 | return (.localError, nil, nil) 112 | } 113 | } 114 | 115 | func request(url: URL, method: HTTPMethod, body: [String: Any]? = nil, headers: [String: String]? = nil) async -> (httpStatusCode: HTTPStatusCode, body: Dictionary?, rawData: Data?) { 116 | return await makeRequest(url: url, method: method, body: body, headers: headers) 117 | } 118 | 119 | func request(endPoint: EndPoint, method: HTTPMethod, body: [String: Any]? = nil, headers: [String: String]? = nil) async -> (httpStatusCode: HTTPStatusCode, body: Dictionary?, rawData: Data?) { 120 | return await makeRequest(endPoint: endPoint, method: method, body: body, headers: headers) 121 | } 122 | 123 | func getURLWithPatientFor(endpoint: EndPoint) -> URL? { 124 | self.getURLWithIdFor(user: .patient, endpoint: endpoint) 125 | } 126 | 127 | func getURLWithDoctorFor(endpoint: EndPoint) -> URL? { 128 | self.getURLWithIdFor(user: .doctor, endpoint: endpoint) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/Schedule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Schedule.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-03-17. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Schedule: Codable { 11 | let day: String 12 | let times: [String] 13 | } 14 | -------------------------------------------------------------------------------- /HealthApp/Models/Shared/SessionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionManager.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-03-17. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class SessionManager: Codable { 12 | static let shared = SessionManager() 13 | 14 | private var currentUserSession = "currentUserSession" 15 | private var patientId: Int? 16 | private var doctorId: Int? 17 | private var sessionToken: String? 18 | 19 | init(json: Dictionary) { 20 | guard let token = json["sessionToken"] as? String, 21 | let id = json["id"] as? Int, 22 | let rawUserType = json["userType"] as? String else { return } 23 | let userType = User(rawValue: rawUserType) 24 | 25 | self.sessionToken = token 26 | if userType == .patient { 27 | patientId = id 28 | } else { 29 | doctorId = id 30 | } 31 | self.secureSave() 32 | } 33 | 34 | private init() { 35 | guard let savedSession = self.loadSecureSavedSession() else { return } 36 | self.patientId = savedSession.patientId 37 | self.doctorId = savedSession.doctorId 38 | self.sessionToken = savedSession.sessionToken 39 | } 40 | 41 | var isLoggedIn: Bool { 42 | return self.sessionToken != nil 43 | } 44 | 45 | func secureSave() { 46 | let encoder = JSONEncoder() 47 | guard let encodedData = try? encoder.encode(self) else { return } 48 | let _ = KeychainManager.shared.save(data: encodedData, identifier: self.currentUserSession) 49 | } 50 | 51 | func loadSecureSavedSession() -> SessionManager? { 52 | guard let retrievedData = KeychainManager.shared.retrieve(identifier: "currentUserSession"), 53 | let jsonData = try? JSONSerialization.data(withJSONObject: retrievedData, options: []) else { 54 | self.clearSession() 55 | return nil 56 | } 57 | 58 | let decoder = JSONDecoder() 59 | guard let session = try? decoder.decode(SessionManager.self, from: jsonData) else { return nil } 60 | return session 61 | } 62 | 63 | func clearSession() { 64 | patientId = nil 65 | doctorId = nil 66 | sessionToken = nil 67 | let _ = KeychainManager.shared.delete(identifier: self.currentUserSession) 68 | } 69 | 70 | func getPatientId() -> Int? { 71 | guard self.isLoggedIn else { 72 | self.logOut() 73 | return nil 74 | } 75 | return self.patientId 76 | } 77 | 78 | func getDoctorId() -> Int? { 79 | guard self.isLoggedIn else { 80 | self.logOut() 81 | return nil 82 | } 83 | return self.doctorId 84 | } 85 | 86 | func getSessionToken() -> String? { 87 | guard let sessionToken else { 88 | self.logOut() 89 | return nil 90 | } 91 | return sessionToken 92 | } 93 | 94 | func logOut() { 95 | self.clearSession() 96 | DispatchQueue.main.async { 97 | let initialViewController = UIStoryboard(name: Constants.Storyboard.main, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.initialViewController) 98 | initialViewController.modalPresentationStyle = .fullScreen 99 | guard let topViewController = UIViewController.topMostViewController() else { return } 100 | topViewController.present(initialViewController, animated: false) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /HealthApp/Storyboards/Base.lproj/Dashboard~.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /HealthApp/Storyboards/Patient/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /HealthApp/Storyboards/Patient/HealthApp/Storyboards/Base.lproj/TabBar.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/AppointmentTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppointmentTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppointmentTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var nameLabel: UILabel! 13 | @IBOutlet weak var dateLabel: UILabel! 14 | @IBOutlet weak var customImageView: CacheImageView! 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | guard let customImageView else { return } 18 | customImageView.setCircularImage() 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | func customizeCell(appointment: Appointment) { 28 | self.customImageView.loadImageFrom(url: appointment.doctor.profilePicture) 29 | self.nameLabel.text = appointment.doctor.fullName 30 | self.dateLabel.text = appointment.date.longDate 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ButtonCellTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonCellTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class ButtonCellTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var button: UIButton! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/CollectionViewTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class CollectionViewTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var collectionView: UICollectionView! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | } 16 | 17 | override func setSelected(_ selected: Bool, animated: Bool) { 18 | super.setSelected(selected, animated: animated) 19 | 20 | // Configure the view for the selected state 21 | } 22 | 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ColorTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 30/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class ColorTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var valueLabel: UILabel! 14 | @IBOutlet weak var colorView: UIView! 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | self.colorView.layer.cornerRadius = 8.0 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | func customizeCell(title: String, value: String, color: UIColor) { 27 | self.titleLabel.text = title 28 | self.valueLabel.text = value 29 | self.colorView.backgroundColor = color 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/DataTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DataTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var customImageView: CacheImageView! 14 | @IBOutlet weak var valueLabel: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | func customizeCell(dataOption: DataOption, roundedImage: Bool = false) { 28 | self.titleLabel.text = dataOption.title 29 | self.valueLabel.text = dataOption.value 30 | self.customImageView.image = dataOption.image 31 | if roundedImage { self.customImageView.setCircularImage() } 32 | } 33 | 34 | func customizeCell(title: String, value: String, image: UIImage? = nil, imageURL: URL? = nil, roundedImage: Bool = false) { 35 | self.titleLabel.text = title 36 | self.valueLabel.text = value 37 | if let image { 38 | self.customImageView.image = image 39 | } 40 | if let imageURL { 41 | self.customImageView.loadImageFrom(url: imageURL) 42 | } 43 | if roundedImage { self.customImageView.setCircularImage() } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/DatePickerTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatePickerTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DatePickerTableViewCell: UITableViewCell { 11 | @IBOutlet weak var monthLabel: UILabel! 12 | @IBOutlet weak var yearLabel: UILabel! 13 | @IBOutlet weak var datePicker: UIDatePicker! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | } 18 | 19 | override func setSelected(_ selected: Bool, animated: Bool) { 20 | super.setSelected(selected, animated: animated) 21 | 22 | // Configure the view for the selected state 23 | } 24 | 25 | func updateDate() { 26 | self.monthLabel.text = "\(self.datePicker.date.abbreviatedMonth) \(self.datePicker.date.day)" 27 | self.yearLabel.text = "\(self.datePicker.date.year)" 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/DateTimeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTimeTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DateTimeTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var calendarImageView: UIImageView! 13 | @IBOutlet weak var dateLabel: UILabel! 14 | @IBOutlet weak var timeImageView: UIImageView! 15 | @IBOutlet weak var timeLabel: UILabel! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | // Initialization code 20 | } 21 | 22 | override func setSelected(_ selected: Bool, animated: Bool) { 23 | super.setSelected(selected, animated: animated) 24 | 25 | // Configure the view for the selected state 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/DualSelectionTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DualSelectionTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class DualSelectionTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var availableButton: UIButton! 13 | @IBOutlet weak var unavailableButton: UIButton! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/HistoryRecordsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryRecordsTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class HistoryRecordsTableViewCell: UITableViewCell { 11 | @IBOutlet weak var dateLabel: UILabel! 12 | @IBOutlet weak var valueLabel: UILabel! 13 | @IBOutlet weak var timeLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | func customizeCell(date: String, value: String, time: String) { 27 | self.dateLabel.text = date 28 | self.valueLabel.text = value 29 | self.timeLabel.text = time 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/IconLabelTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IconLabelTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class IconLabelTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var iconImageView: UIImageView! 13 | @IBOutlet weak var mainLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | } 24 | 25 | func configure(title: String, icon: UIImage) { 26 | self.mainLabel.text = title 27 | self.iconImageView.image = icon 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class ImageTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var customImageView: CacheImageView! 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | func customizeCell(image: UIImage? = nil, url: URL? = nil, showLines: Bool = false, circular: Bool = true) { 25 | if circular { self.customImageView.setCircularImage() } 26 | if let image { 27 | self.customImageView.image = image 28 | } else if let url { 29 | self.customImageView.loadImageFrom(url: url) 30 | } 31 | 32 | self.separatorInset.removeSeparator() 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/InlineDatePickerTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InlineDatePickerTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class InlineDatePickerTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var datePicker: UIDatePicker! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/LabelButtonTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabelButtonTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class LabelButtonTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var button: UIButton! 13 | @IBOutlet weak var customLabel: UILabel! 14 | var selectedValueCallback: ((Int) -> Void)? 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | self.setMenu() 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | @objc func setMenu() { 27 | guard let button else { return } 28 | let maleAction = UIAction(title: "Male", image: nil, identifier: nil, discoverabilityTitle: nil, state: .off) { action in 29 | self.selectedValueCallback?(0) 30 | } 31 | let femaleAction = UIAction(title: "Female", image: nil, identifier: nil, discoverabilityTitle: nil, state: .off) { action in 32 | self.selectedValueCallback?(1) 33 | } 34 | let otherAction = UIAction(title: "Not Set", image: nil, identifier: nil, discoverabilityTitle: nil, state: .off) { action in 35 | self.selectedValueCallback?(2) 36 | } 37 | 38 | let menu = UIMenu(title: "", children: [maleAction, femaleAction, otherAction]) 39 | button.menu = menu 40 | button.showsMenuAsPrimaryAction = true 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ProfileHorizontalTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileHorizontalTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class ProfileHorizontalTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var profileImaeView: UIImageView! 13 | @IBOutlet weak var nameLabel: UILabel! 14 | @IBOutlet weak var specilizationLabel: UILabel! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | self.profileImaeView.setCircularImage() 19 | } 20 | 21 | func customizeCell(picture: UIImage, name: String, specilization: String) { 22 | self.profileImaeView.image = picture 23 | self.nameLabel.text = name 24 | self.specilizationLabel.text = specilization 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ProfileImageTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileImageTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | import CoreImage 10 | 11 | class ProfileImageTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var customImageView: CacheImageView! 14 | @IBOutlet weak var customTitleLabel: UILabel! 15 | @IBOutlet weak var customDetailLabel: UILabel! 16 | @IBOutlet weak var visualEffectView: UIVisualEffectView! 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | } 20 | 21 | override func setSelected(_ selected: Bool, animated: Bool) { 22 | super.setSelected(selected, animated: animated) 23 | 24 | // Configure the view for the selected state 25 | } 26 | 27 | override func layoutSubviews() { 28 | super.layoutSubviews() 29 | self.configureGradientMask() 30 | } 31 | 32 | 33 | func customizeCell(imageURL: URL?, title: String?, detail: String?) { 34 | self.customImageView.loadImageFrom(url: imageURL) 35 | self.customTitleLabel.text = title 36 | self.customDetailLabel.text = detail 37 | self.configureGradientMask() 38 | 39 | 40 | } 41 | 42 | func configureGradientMask() { 43 | let gradientLayer = CAGradientLayer() 44 | gradientLayer.frame = visualEffectView.bounds 45 | gradientLayer.colors = [UIColor.clear.cgColor, UIColor.white.cgColor] 46 | gradientLayer.locations = [0.0, 0.7] // Adjust these values to control the gradient effect 47 | visualEffectView.layer.mask = gradientLayer 48 | } 49 | 50 | 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/ScheduleTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScheduleCellTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class ScheduleTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var daysLabel: UILabel! 13 | @IBOutlet weak var startTitle: UILabel! 14 | @IBOutlet weak var startHourLabel: UILabel! 15 | @IBOutlet weak var endLabel: UILabel! 16 | @IBOutlet weak var endHourLabel: UILabel! 17 | 18 | override func awakeFromNib() { 19 | super.awakeFromNib() 20 | } 21 | 22 | override func setSelected(_ selected: Bool, animated: Bool) { 23 | super.setSelected(selected, animated: animated) 24 | 25 | // Configure the view for the selected state 26 | } 27 | 28 | func customizeCell(days: String, startHour: String, endHour: String) { 29 | self.daysLabel.text = days 30 | self.startHourLabel.text = startHour 31 | self.endHourLabel.text = endHour 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/TextFieldTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class TextFieldTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var textField: UITextField! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HealthApp/TableViewCells/TextViewTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewTableViewCell.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class TextViewTableViewCell: UITableViewCell { 11 | 12 | @IBOutlet weak var textView: UITextView! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | // Initialization code 16 | } 17 | 18 | override func setSelected(_ selected: Bool, animated: Bool) { 19 | super.setSelected(selected, animated: animated) 20 | 21 | // Configure the view for the selected state 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "watchos", 6 | "reference" : "systemRedColor" 7 | }, 8 | "idiom" : "universal" 9 | } 10 | ], 11 | "info" : { 12 | "author" : "xcode", 13 | "version" : 1 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground 1.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@2x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@2x 1.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@3x 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@3x 1.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/BlurBackground@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/BlurBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BlurBackground.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "BlurBackground 1.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "BlurBackground@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "BlurBackground@2x 1.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "BlurBackground@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "BlurBackground@3x 1.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dark Blue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.435", 9 | "green" : "0.297", 10 | "red" : "0.137" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard Green.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.505", 9 | "green" : "0.546", 10 | "red" : "0.388" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard Pink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.671", 9 | "green" : "0.704", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Eat.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Eat@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Eat@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Eat.imageset/Eat@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Exercise.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Exercise@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Exercise@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Exercise.imageset/Exercise@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Hearth.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Hearth@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Hearth@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Hearth.imageset/Hearth@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Nevus.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Nevus@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Nevus@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Nevus.imageset/Nevus@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Sleep.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "Sleep@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "Sleep@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/Sleep.imageset/Sleep@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "weight.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "weight@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "weight@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Dashboard fruits/weight.imageset/weight@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Favourite Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Favourite Colors/Favourite 1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "236", 9 | "green" : "234", 10 | "red" : "208" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Favourite Colors/Favourite 2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "251", 9 | "green" : "209", 10 | "red" : "208" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Favourite Colors/Favourite 3.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "238", 9 | "green" : "236", 10 | "red" : "245" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/doctor.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "doctor.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/doctor.imageset/doctor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Helpers/doctor.imageset/doctor.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/doctor2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "doctor2.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/doctor2.imageset/doctor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Helpers/doctor2.imageset/doctor2.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/profile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "profile.jpg", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/Helpers/profile.imageset/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/Helpers/profile.imageset/profile.jpg -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "hearth_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "hearth_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "hearth_icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/hearth_icon.imageset/hearth_icon@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "placeholder.png", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/placeholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/placeholder.imageset/placeholder.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/star-backround.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "star_backround.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "star_backround@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "star_backround@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround@2x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Assets.xcassets/star-backround.imageset/star_backround@3x.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Constants { 11 | struct Segues { 12 | static let showLoginViewController = "ShowLoginVC" 13 | static let showSplitViewController = "ShowSplitViewController" 14 | static let showEditProfileViewController = "ShowEditProfileVC" 15 | static let showDoctorProfileVC = "ShowDoctorProfileVC" 16 | static let showAppointmentVC = "ShowAppointmentVC" 17 | static let showBookAppointmentVC = "ShowBookAppointmentVC" 18 | static let showHistoryVC = "ShowHistoryVC" 19 | static let showFilterVC = "ShowFilterVC" 20 | static let showCaloriesSummaryViewController = "ShowCaloriesSummaryVC" 21 | static let showRecommendationViewController = "ShowRecommendationVC" 22 | static let showFitzpatrickView = "ShowFitzpatrickView" 23 | static let showPermissionsViewController = "ShowPermissionsVC" 24 | static let showSleepHistoryVC = "ShowSleepHistoryVC" 25 | static let showPatientProfileVC = "ShowPatientProfileVC" 26 | static let showPatientsVC = "ShowPatientsVC" 27 | static let showSchedulesVC = "ShowSchedulesVC" 28 | static let showEditProfileVC = "ShowEditProfileVC" 29 | static let showAppointmentConflictVC = "ShowAppointmentConflictVC" 30 | } 31 | 32 | struct Cells { 33 | static let labelIconCell = "LabelIconCell" 34 | static let textFieldCell = "TextFieldCell" 35 | static let labelButtonCell = "LabelButtonCell" 36 | static let imageCell = "ImageCell" 37 | static let appointmentCell = "AppointmentCell" 38 | static let basicCell = "BasicCell" 39 | static let collectionViewTableViewCell = "CollectionViewTableViewCell" 40 | static let dataTableViewCell = "DataTableViewCell" 41 | static let profileImageCell = "ProfileImageCell" 42 | static let buttonCell = "ButtonCell" 43 | static let datePickerCell = "DatePickerCell" 44 | static let dateTimeCell = "DateTimeCell" 45 | static let inlineDatePicker = "InlineDatePicker" 46 | static let textViewCell = "TextViewCell" 47 | static let historyRecordCell = "HistioryRecordVC" 48 | static let colorCell = "ColorCell" 49 | static let profileHorizontalCell = "ProfileHorizontalCell" 50 | static let statusCell = "StatusCell" 51 | static let dualSelectionCell = "DualSelectionCell" 52 | static let scheduleCell = "ScheduleCell" 53 | //CollectionView 54 | static let profileCell = "ProfileCell" 55 | static let dashboardDataCell = "DashboarDataCell" 56 | static let doctorCell = "DoctorCell" 57 | static let dataCell = "DataCell" 58 | static let pictureNameCell = "PictureNameCell" 59 | static let detailCell = "DetailCell" 60 | static let singleLabelCell = "SingleLabelCell" 61 | } 62 | 63 | struct SFSymbols { 64 | static let person = "person" 65 | static let personFill = "person.fill" 66 | static let heart = "heart" 67 | static let heartFill = "heart.fill" 68 | static let calendar = "calendar" 69 | static let trashCircleFill = "trash.circle.fill" 70 | } 71 | 72 | struct Storyboard { 73 | static let main = "Main" 74 | static let tabBar = "TabBar" 75 | static let dashboard = "Dashboard" 76 | static let doctors = "Doctors" 77 | static let appointments = "Appointments" 78 | static let doctorDashboard = "DoctorDashboard" 79 | static let doctorSettings = "DoctorSettings" 80 | static let patientProfile = "PatientProfile" 81 | } 82 | 83 | struct ViewIdentifiers { 84 | static let initialViewController = "InitialViewController" 85 | static let loginViewController = "LoginViewController" 86 | static let primaryTableViewController = "PrimaryTableViewController" 87 | static let tabBarViewController = "TabBarController" 88 | static let dashboardNavigationController = "DashboardNC" 89 | static let doctorsNC = "DoctorsNC" 90 | static let appointmentsNC = "AppointmentsNC" 91 | static let addDoctorVC = "AddDoctorVC" 92 | static let authenticationVC = "AuthenticationVC" 93 | static let doctorDashboardNC = "DoctorDashboardNC" 94 | static let doctorsSettingsVC = "DoctorSettingsVC" 95 | static let patientProfileVC = "PatientProfileVC" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /HealthApp/Utilities/FitzpatrickScale.mlmodel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/FitzpatrickScale.mlmodel -------------------------------------------------------------------------------- /HealthApp/Utilities/HealthApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.healthkit 6 | 7 | com.apple.developer.healthkit.access 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleURLSchemes 9 | 10 | healthApp 11 | 12 | CFBundleURLName 13 | com.MoisesCordova.HealthAppKit 14 | 15 | 16 | NSPhotoLibraryUsageDescription 17 | We need access to create recommendations based on you ingredients 18 | NSFaceIDUsageDescription 19 | We need your biometric to authenticate you and see you records 20 | NSCameraUsageDescription 21 | We need access to your camera for scanning QR codes 22 | NSHealthUpdateUsageDescription 23 | We need access to share information with your doctor 24 | UIApplicationSceneManifest 25 | 26 | UIApplicationSupportsMultipleScenes 27 | 28 | UISceneConfigurations 29 | 30 | UIWindowSceneSessionRoleApplication 31 | 32 | 33 | UISceneConfigurationName 34 | Default Configuration 35 | UISceneDelegateClassName 36 | $(PRODUCT_MODULE_NAME).SceneDelegate 37 | UISceneStoryboardFile 38 | Main 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Mocks/Original Mocks/Appointments.json: -------------------------------------------------------------------------------- 1 | { 2 | "appointments": [ 3 | { 4 | "id": 0, 5 | "doctor": { 6 | "id": 0, 7 | "firstName": "Juan Gabriel", 8 | "lastName": "Gomila Salas", 9 | "specialization": "Cardiologist", 10 | "profilePicture": "https://github.com/ColeMacGrath/HealthApp/blob/master/HealthApp/Utilities/Profile%20Pictures/Juan%20Gabriel/Juan%20Gabriel-square.png?raw=true" 11 | }, 12 | "date": "2024-02-11T03:30:00.000Z", 13 | "notes": "Reason for Appointment:\n- Recent development of skin rash on arms and legs, noticed over the past two weeks.\n- Persistent itchiness and occasional redness around the rash areas.\n- Previous use of over-the-counter creams has provided minimal relief.\nAdditional Notes:\n- Concern about potential allergic reaction or eczema, as there is a family history of eczema.\n- Interested in discussing preventive skincare routines for sensitive skin.\n- Questions about potential dietary influences on skin health and if a referral to a nutritionist is advisable.\nMedications Currently Taking:\n- Over-the-counter hydrocortisone cream, applied twice daily.\n- Multivitamins.\nAllergies:\n- No known drug allergies.\n- Mild allergy to pollen (seasonal).\nPrevious Dermatological Treatments:\n- None for the current condition.\n- Treated for acne with topical retinoids two years ago with good results.\nExpectations from the Appointment:\n- Diagnosis of the current skin condition.\n- Recommendations for treatment and relief from itchiness.\n- Advice on skincare products suitable for sensitive skin." 14 | }, 15 | { 16 | "id": 1, 17 | "doctor": { 18 | "id": 2, 19 | "firstName": "Carlos", 20 | "lastName": "Rivera", 21 | "specialization": "Traumatologist", 22 | "profilePicture": "https://github.com/ColeMacGrath/HealthApp/blob/master/HealthApp/Utilities/Profile%20Pictures/Carlos/Carlos-square.png?raw=true" 23 | }, 24 | "date": "2024-02-11T03:30:00.000Z", 25 | "notes": "Reason for Appointment:\n- Experiencing persistent knee pain and swelling after a recent fall during a soccer game.\n- Limited mobility and discomfort when walking or standing for long periods.\nAdditional Notes:\n- Pain relief methods such as ice and over-the-counter painkillers have provided only temporary relief.\n- Concerns about potential ligament damage or a meniscus tear.\n- History of a similar injury on the other knee treated with physical therapy two years ago.\nMedications Currently Taking:\n- Ibuprofen, as needed for pain relief.\nAllergies:\n- No known allergies to medications.\nPrevious Orthopedic Treatments:\n- Physical therapy for the other knee due to a sports injury, with good recovery.\nExpectations from the Appointment:\n- Assessment of the knee for any serious injuries such as ligament or meniscal damage.\n- Advice on immediate care and whether imaging tests are needed.\n- Discussion on treatment options, including the possibility of physical therapy or surgery." 26 | }, 27 | { 28 | "id": 2, 29 | "doctor": { 30 | "id": 5, 31 | "firstName": "Emily", 32 | "lastName": "Patel", 33 | "specialization": "Neurologist", 34 | "profilePicture": "https://github.com/ColeMacGrath/HealthApp/blob/master/HealthApp/Utilities/Profile%20Pictures/Emily/Emily-square.png?raw=true" 35 | }, 36 | "date": "2024-02-12T03:30:00.000Z", 37 | "notes": "Reason for Appointment:\n- Experiencing frequent headaches over the past month, increasing in intensity.\n- Occasional dizziness and blurred vision associated with the headaches.\nAdditional Notes:\n- Headaches tend to worsen in the late afternoon and after prolonged screen time.\n- Over-the-counter pain medication provides limited relief.\n- Concerns about the possibility of migraines or vision-related issues.\nMedications Currently Taking:\n- Acetaminophen, as needed for headache relief.\nAllergies:\n- No known allergies to medications.\nPrevious Neurological Treatments:\n- None.\nExpectations from the Appointment:\n- Assessment to determine the cause of the headaches and associated symptoms.\n- Discussion on diagnostic tests, such as imaging or vision tests, if necessary.\n- Advice on treatment options, including medication, lifestyle adjustments, or referral to a specialist if needed." 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /HealthApp/Utilities/Mocks/Original Mocks/Users.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "users": [ 4 | { 5 | "userId": 0, 6 | "firstName": "John", 7 | "lastName": "Doe", 8 | "doctors": [0, 1, 2, 3], 9 | "healthData": { 10 | "caloriesBurned": [ 11 | { 12 | "calories": 100, 13 | "date": "2024-02-11T03:30:00.000Z" 14 | }, 15 | { 16 | "calories": 100, 17 | "date": "2024-02-11T03:30:00.000Z" 18 | } 19 | ], 20 | "sleep": [ 21 | { 22 | "startDate": "2024-02-11T03:30:00.000Z", 23 | "endDate": "2024-02-11T03:30:00.000Z" 24 | }, 25 | { 26 | "startDate": "2024-02-11T03:30:00.000Z", 27 | "endDate": "2024-02-11T03:30:00.000Z" 28 | } 29 | ], 30 | "weight": [ 31 | { 32 | "kilograms": 70, 33 | "date": "2024-02-11T03:30:00.000Z" 34 | }, 35 | { 36 | "kilograms": 70, 37 | "date": "2024-02-11T03:30:00.000Z" 38 | } 39 | ], 40 | "hearthBPM": [ 41 | { 42 | "BPM": 70, 43 | "date": "2024-02-11T03:30:00.000Z" 44 | }, 45 | { 46 | "BPM": 70, 47 | "date": "2024-02-11T03:30:00.000Z" 48 | } 49 | ], 50 | "ingestedFood": [ 51 | { 52 | "foodName": "Apple", 53 | "calories": 150, 54 | "date": "2024-02-11T03:30:00.000Z" 55 | } 56 | ] 57 | } 58 | }, 59 | { 60 | "userId": 1, 61 | "firstName": "Allison", 62 | "lastName": "Doe", 63 | "doctors": [3, 4, 5, 6], 64 | "healthData": { 65 | "caloriesBurned": [ 66 | { 67 | "calories": 100, 68 | "date": "2024-02-11T03:30:00.000Z" 69 | }, 70 | { 71 | "calories": 100, 72 | "date": "2024-02-11T03:30:00.000Z" 73 | } 74 | ], 75 | "sleep": [ 76 | { 77 | "startDate": "2024-02-11T03:30:00.000Z", 78 | "endDate": "2024-02-11T03:30:00.000Z" 79 | }, 80 | { 81 | "startDate": "2024-02-11T03:30:00.000Z", 82 | "endDate": "2024-02-11T03:30:00.000Z" 83 | } 84 | ], 85 | "weight": [ 86 | { 87 | "kilograms": 70, 88 | "date": "2024-02-11T03:30:00.000Z" 89 | }, 90 | { 91 | "kilograms": 70, 92 | "date": "2024-02-11T03:30:00.000Z" 93 | } 94 | ], 95 | "hearthBPM": [ 96 | { 97 | "BPM": 70, 98 | "date": "2024-02-11T03:30:00.000Z" 99 | }, 100 | { 101 | "BPM": 70, 102 | "date": "2024-02-11T03:30:00.000Z" 103 | } 104 | ], 105 | "ingestedFood": [ 106 | { 107 | "foodName": "Apple", 108 | "calories": 150, 109 | "date": "2024-02-11T03:30:00.000Z" 110 | } 111 | ] 112 | } 113 | } 114 | 115 | ] 116 | } 117 | -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Alexander/Alerxander-sqaure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Alexander/Alerxander-sqaure.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Alexander/Alexander-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Alexander/Alexander-vertical.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Allison/Allison-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Allison/Allison-square.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Allison/Allison-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Allison/Allison-vertical.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Carlos/Carlos-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Carlos/Carlos-square.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Carlos/Carlos-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Carlos/Carlos-vertical.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Emily/Emily-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Emily/Emily-square.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Emily/Emily-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Emily/Emily-vertical.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Juan Gabriel/Juan Gabriel-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Juan Gabriel/Juan Gabriel-square.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Juan Gabriel/Juan Gabriel-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Juan Gabriel/Juan Gabriel-vertical.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Sophia/Sophia-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Sophia/Sophia-square.png -------------------------------------------------------------------------------- /HealthApp/Utilities/Profile Pictures/Sophia/Sophia-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/HealthApp/Utilities/Profile Pictures/Sophia/Sophia-vertical.png -------------------------------------------------------------------------------- /HealthApp/ViewControllers/AppointmentConflictViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppointmentConflictViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-03-29. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppointmentConflictViewController: UIViewController { 11 | @IBOutlet weak var titleLabel: UILabel! 12 | @IBOutlet weak var subtitleLabel: UILabel! 13 | @IBOutlet weak var profilePictureImageView: CacheImageView! 14 | @IBOutlet weak var personTitleLabel: UILabel! 15 | @IBOutlet weak var personSubtitleLabel: UILabel! 16 | @IBOutlet weak var dateLabel: UILabel! 17 | @IBOutlet weak var timeLabel: UILabel! 18 | var appointment: Appointment! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | self.profilePictureImageView.setCircularImage() 23 | self.profilePictureImageView.loadImageFrom(url: self.appointment.doctor.profilePicture) 24 | self.personTitleLabel.text = self.appointment.doctor.fullName 25 | self.personSubtitleLabel.text = self.appointment.doctor.specialization 26 | self.dateLabel.text = self.appointment.date.shortDateWithYear 27 | self.timeLabel.text = self.appointment.date.twelveHoursFormmatedHour 28 | 29 | } 30 | 31 | @IBAction func closeButtonPressed(_ sender: Any) { 32 | self.dismiss(animated: true) 33 | } 34 | 35 | @IBAction func goBackButtonPressed(_ sender: UIButton) { 36 | self.dismiss(animated: true) 37 | } 38 | 39 | 40 | @IBAction func cancelAppointmentButtonPressed(_ sender: UIButton) { 41 | guard let url = URL(string: RequestManager.shared.baseURL + "\(self.appointment.id)/" + EndPoint.appointment.rawValue) else { 42 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 43 | return 44 | } 45 | Task { 46 | guard await RequestManager.shared.request(url: url, method: .delete).httpStatusCode == .empty else { 47 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 48 | return 49 | } 50 | 51 | self.showFloatingAlert(text: "Appointment Deleted", alertType: .success) 52 | self.dismiss(animated: true) 53 | 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Doctor/DoctorSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoctorSettingsViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class DoctorSettingsViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.tableView.alwaysBounceVertical = false 16 | self.navigationController?.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.label] 17 | } 18 | } 19 | 20 | extension DoctorSettingsViewController: UITableViewDelegate, UITableViewDataSource { 21 | func numberOfSections(in tableView: UITableView) -> Int { 22 | return 5 23 | } 24 | 25 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 26 | section == 2 ? 3 : 1 27 | } 28 | 29 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 | switch indexPath.section { 31 | case 0: 32 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.profileHorizontalCell, for: indexPath) as! ProfileHorizontalTableViewCell 33 | cell.customizeCell(picture: .doctor, name: "Juan Gabriel Gomila Salas", specilization: "Oncologyst") 34 | return cell 35 | case 1: 36 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.dualSelectionCell, for: indexPath) as! DualSelectionTableViewCell 37 | return cell 38 | case 2: 39 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.scheduleCell, for: indexPath) as! ScheduleTableViewCell 40 | cell.customizeCell(days: "Mon, Tue, Fri", startHour: "10:00 AM", endHour: "08:00 PM") 41 | return cell 42 | default: 43 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 44 | var content = UIListContentConfiguration.cell() 45 | content.textProperties.font = UIFont(name: "HelveticaNeue", size: 18.0) ?? UIFont() 46 | if indexPath.section == 3 { 47 | cell.backgroundColor = .systemRed 48 | content.textProperties.color = .white 49 | content.textProperties.alignment = .center 50 | content.text = "Save" 51 | } else { 52 | cell.backgroundColor = .clear 53 | content.textProperties.color = .systemRed 54 | content.textProperties.alignment = .center 55 | content.text = "Log out" 56 | } 57 | 58 | cell.contentConfiguration = content 59 | return cell 60 | } 61 | } 62 | 63 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 64 | if indexPath.section == 2 { 65 | self.performSegue(withIdentifier: Constants.Segues.showSchedulesVC, sender: nil) 66 | } else if indexPath.section == 3 { 67 | self.navigationController?.popViewController(animated: true) 68 | } else if indexPath.section == 4 { 69 | SessionManager.shared.logOut() 70 | } 71 | tableView.deselectRow(at: indexPath, animated: true) 72 | } 73 | 74 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 75 | if section == 1 { 76 | return "Current Status" 77 | } else if section == 2 { 78 | return "Schedules" 79 | } 80 | 81 | return nil 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Doctor/PatientsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PatientsViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-13. 6 | // 7 | 8 | import UIKit 9 | 10 | class PatientsViewController: UIViewController { 11 | 12 | @IBOutlet weak var patientsTableView: UITableView! 13 | private var patients: [Patient] = [] 14 | var todayPatients: [Patient] = [] 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.label] 19 | self.loadPatients() 20 | } 21 | 22 | override func viewWillAppear(_ animated: Bool) { 23 | super.viewWillAppear(animated) 24 | self.navigationController?.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.label] 25 | } 26 | 27 | func loadPatients() { 28 | Task { 29 | guard let url = RequestManager.shared.getURLWithDoctorFor(endpoint: .patients), 30 | let responseData = await RequestManager.shared.request(url: url, method: .get).rawData else { 31 | self.showFloatingAlert(text: "Error loading patients", alertType: .error) 32 | return 33 | } 34 | 35 | do { 36 | let decoder = JSONDecoder() 37 | let dateFormatter = DateFormatter() 38 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" 39 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 40 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 41 | decoder.dateDecodingStrategy = .formatted(dateFormatter) 42 | let patients = try decoder.decode([String: [Patient]].self, from: responseData)["patients"] ?? [] 43 | self.patients = patients 44 | self.patientsTableView.reloadData() 45 | 46 | } catch { 47 | self.showFloatingAlert(text: "Error loading patients", alertType: .error) 48 | } 49 | 50 | } 51 | } 52 | 53 | } 54 | 55 | extension PatientsViewController: UITableViewDataSource, UITableViewDelegate { 56 | func numberOfSections(in tableView: UITableView) -> Int { 57 | return 2 58 | } 59 | 60 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 61 | return section == 0 ? self.todayPatients.count : self.patients.count 62 | } 63 | 64 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 65 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.dataTableViewCell, for: indexPath) as! DataTableViewCell 66 | var patient: Patient? 67 | if indexPath.section == 0 { 68 | patient = self.todayPatients[indexPath.row] 69 | } else { 70 | patient = self.patients[indexPath.row] 71 | } 72 | if let patient { 73 | cell.customizeCell(title: patient.fullName, value: "\(patient.biologicalSex)\n\(patient.age) y/o", imageURL: patient.profilePicture, roundedImage: true) 74 | } 75 | return cell 76 | } 77 | 78 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 79 | var patient: Patient? 80 | patient = indexPath.section == 0 ? self.todayPatients[indexPath.row] : self.patients[indexPath.row] 81 | 82 | guard let patientProfileViewController = UIStoryboard(name: Constants.Storyboard.patientProfile, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.patientProfileVC) as? PatientProfileViewController else { 83 | tableView.deselectRow(at: indexPath, animated: true) 84 | return 85 | } 86 | patientProfileViewController.patient = patient 87 | self.navigationController?.pushViewController(patientProfileViewController, animated: true) 88 | tableView.deselectRow(at: indexPath, animated: true) 89 | } 90 | 91 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 92 | section == 0 ? self.todayPatients.isEmpty ? nil : "Today Patients" : "All my patients" 93 | } 94 | 95 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 96 | section == 0 ? 30.0 : 20.0 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Doctor/SchedulesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SchedulesViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-14. 6 | // 7 | 8 | import UIKit 9 | 10 | class SchedulesViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | var collectionView: UICollectionView? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | self.tableView.alwaysBounceVertical = false 18 | } 19 | 20 | } 21 | 22 | extension SchedulesViewController: UITableViewDataSource, UITableViewDelegate { 23 | func numberOfSections(in tableView: UITableView) -> Int { 24 | return 4 25 | } 26 | 27 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 28 | return 1 29 | } 30 | 31 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 32 | switch indexPath.section { 33 | case 0: 34 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.collectionViewTableViewCell, for: indexPath) as! CollectionViewTableViewCell 35 | if let collectionView { 36 | cell.collectionView = collectionView 37 | } else { 38 | self.collectionView = cell.collectionView 39 | } 40 | return cell 41 | case 1, 2: 42 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.datePickerCell, for: indexPath) as! DatePickerTableViewCell 43 | return cell 44 | default: 45 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 46 | var content = UIListContentConfiguration.cell() 47 | content.textProperties.font = UIFont(name: "HelveticaNeue", size: 18.0) ?? UIFont() 48 | content.textProperties.color = .white 49 | content.textProperties.alignment = .center 50 | content.text = "Save" 51 | cell.contentConfiguration = content 52 | return cell 53 | } 54 | } 55 | 56 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 57 | guard indexPath.section == 0 else { 58 | return UITableView.automaticDimension 59 | } 60 | 61 | return 70.0 62 | } 63 | 64 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 65 | switch section { 66 | case 0: 67 | return "Days" 68 | case 1: 69 | return "☀️ Start Time" 70 | case 2: 71 | return "🌙 End Time" 72 | default: 73 | return nil 74 | } 75 | } 76 | 77 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 78 | return section == 0 ? 30.0 : 0.0 79 | } 80 | 81 | } 82 | 83 | extension SchedulesViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { 84 | func numberOfSections(in collectionView: UICollectionView) -> Int { 85 | return 1 86 | } 87 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 88 | return 7 89 | } 90 | 91 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 92 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.Cells.singleLabelCell, for: indexPath) as! SingleLabelCollectionViewCell 93 | var text = String() 94 | switch indexPath.row { 95 | case 0: 96 | text = "M" 97 | case 1: 98 | text = "T" 99 | case 2: 100 | text = "W" 101 | case 3: 102 | text = "T" 103 | case 4: 104 | text = "F" 105 | case 5: 106 | text = "S" 107 | default: 108 | text = "S" 109 | } 110 | cell.customizeCell(text: text, selected: Bool.random()) 111 | return cell 112 | } 113 | 114 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 115 | guard let cell = collectionView.cellForItem(at: indexPath) as? SingleLabelCollectionViewCell else { return } 116 | cell.updateSelected() 117 | self.tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .automatic) 118 | } 119 | 120 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 121 | return CGSize(width: 44.0, height: 44.0) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Patient/AddDoctorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddDoctorViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-01-25. 6 | // 7 | 8 | import UIKit 9 | 10 | class AddDoctorViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.isModalInPresentation = true 17 | self.tableView.alwaysBounceVertical = false 18 | } 19 | @IBAction func addButtonPressed(_ sender: UIButton) { 20 | sender.showLoading() 21 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { 22 | sender.hideLoading(title: "Added") 23 | self.dismiss(animated: true) 24 | }) 25 | } 26 | 27 | @IBAction func cancelButtonPressed(_ sender: UIButton) { 28 | self.dismiss(animated: true) 29 | } 30 | } 31 | 32 | extension AddDoctorViewController: UITableViewDataSource, UITableViewDelegate { 33 | func numberOfSections(in tableView: UITableView) -> Int { 34 | return 2 35 | } 36 | 37 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 38 | section == 0 ? 2 : 1 39 | } 40 | 41 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 42 | if indexPath.section == 0, 43 | indexPath.row == 0 { 44 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.imageCell, for: indexPath) as! ImageTableViewCell 45 | cell.customizeCell(image: .doctor, showLines: false, circular: true) 46 | return cell 47 | } 48 | 49 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 50 | var content = UIListContentConfiguration.cell() 51 | 52 | if indexPath.section == 0 { 53 | cell.backgroundColor = .clear 54 | content.textProperties.alignment = .center 55 | content.text = "John Doe" 56 | content.textProperties.font = UIFont(name: "HelveticaNeue-Bold", size: 22.0) ?? UIFont() 57 | content.secondaryText = "Cardiologyst" 58 | content.secondaryTextProperties.font = UIFont(name: "HelveticaNeue", size: 16.0) ?? UIFont() 59 | content.secondaryTextProperties.color = .secondaryLabel 60 | content.secondaryTextProperties.alignment = .center 61 | } else { 62 | cell.backgroundColor = .secondarySystemGroupedBackground 63 | content.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 64 | content.textProperties.font = UIFont(name: "HelveticaNeue", size: 16.0) ?? UIFont() 65 | content.textProperties.color = .secondaryLabel 66 | } 67 | 68 | cell.contentConfiguration = content 69 | 70 | return cell 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Patient/PrimaryTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrimaryTableViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | enum RowOption: Int { 11 | case me 12 | case doctors 13 | case appointments 14 | } 15 | 16 | struct PrimaryRow { 17 | let title: String 18 | let icon: UIImage 19 | let rowOption: RowOption 20 | } 21 | 22 | class PrimaryTableViewController: UITableViewController { 23 | 24 | let items = [ 25 | PrimaryRow(title: "Me", icon: UIImage.sfSymbol(Constants.SFSymbols.personFill, color: .red), rowOption: .me), 26 | PrimaryRow(title: "Doctors", icon: UIImage.sfSymbol(Constants.SFSymbols.heartFill, color: .red), rowOption: .doctors), 27 | PrimaryRow(title: "Appointments", icon: UIImage.sfSymbol(Constants.SFSymbols.calendar, color: .red), rowOption: .appointments) 28 | ] 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | self.title = "HealthApp" 33 | self.navigationController?.navigationBar.prefersLargeTitles = true 34 | guard let splitViewController else { return } 35 | 36 | self.tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: .top) 37 | splitViewController.setViewController(nil, for: .supplementary) 38 | splitViewController.setViewController(UIStoryboard(name: Constants.Storyboard.dashboard, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.dashboardNavigationController), for: .secondary) 39 | 40 | } 41 | 42 | override func viewDidAppear(_ animated: Bool) { 43 | super.viewDidAppear(animated) 44 | 45 | } 46 | 47 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 48 | return items.count 49 | } 50 | 51 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 52 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.labelIconCell, for: indexPath) as! IconLabelTableViewCell 53 | let item = items[indexPath.row] 54 | cell.configure(title: item.title, icon: item.icon) 55 | return cell 56 | } 57 | 58 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 59 | guard let splitViewController else { return } 60 | 61 | switch items[indexPath.row].rowOption { 62 | case .me: 63 | splitViewController.setViewController(nil, for: .supplementary) 64 | splitViewController.setViewController(UIStoryboard(name: Constants.Storyboard.dashboard, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.dashboardNavigationController), for: .secondary) 65 | case .doctors: 66 | splitViewController.setViewController(UIStoryboard(name: Constants.Storyboard.doctors, bundle: nil).instantiateViewController(withIdentifier: "DoctorsVC"), for: .supplementary) 67 | splitViewController.setViewController(UIStoryboard(name: Constants.Storyboard.doctors, bundle: nil).instantiateViewController(withIdentifier: "DoctorVC"), for: .secondary) 68 | case .appointments: 69 | splitViewController.setViewController(nil, for: .supplementary) 70 | splitViewController.setViewController(UIStoryboard(name: Constants.Storyboard.appointments, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.appointmentsNC), for: .secondary) 71 | } 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Patient/SplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplitViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | 11 | class SplitViewController: UIViewController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | } 15 | 16 | override func viewDidAppear(_ animated: Bool) { 17 | super.viewDidAppear(animated) 18 | 19 | // Create the split view controller 20 | let splitViewController = UISplitViewController(style: .tripleColumn) 21 | splitViewController.preferredDisplayMode = .twoBesideSecondary 22 | 23 | // Set up the view controllers 24 | let primaryViewController = PrimaryTableViewController() 25 | let secondaryViewController = SecondaryViewController() 26 | let supplementaryViewController = SupplementaryViewController() 27 | 28 | splitViewController.setViewController(primaryViewController, for: .primary) 29 | splitViewController.setViewController(secondaryViewController, for: .secondary) 30 | splitViewController.setViewController(supplementaryViewController, for: .supplementary) 31 | splitViewController.modalPresentationStyle = .fullScreen 32 | self.present(splitViewController, animated: false) 33 | } 34 | } 35 | 36 | class SecondaryViewController: UIViewController { 37 | 38 | private let label = UILabel() 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | view.backgroundColor = .white 43 | view.addSubview(label) 44 | label.translatesAutoresizingMaskIntoConstraints = false 45 | NSLayoutConstraint.activate([ 46 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor), 47 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor) 48 | ]) 49 | } 50 | 51 | func showLabel(withText text: String) { 52 | label.text = text 53 | } 54 | } 55 | 56 | class SupplementaryViewController: UITableViewController { 57 | 58 | init() { 59 | super.init(style: .insetGrouped) 60 | } 61 | 62 | required init?(coder: NSCoder) { 63 | fatalError("init(coder:) has not been implemented") 64 | } 65 | 66 | var selectionHandler: ((Int) -> Void)? 67 | 68 | override func viewDidLoad() { 69 | super.viewDidLoad() 70 | self.title = "Doctors" 71 | self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") 72 | } 73 | 74 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 75 | return 10 76 | } 77 | 78 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 79 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 80 | cell.textLabel?.text = "Row \(indexPath.row + 1)" 81 | return cell 82 | } 83 | 84 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 85 | selectionHandler?(indexPath.row + 1) 86 | guard let splitViewController = splitViewController, 87 | let secondaryViewController = splitViewController.viewController(for: .secondary) as? SecondaryViewController else { return } 88 | splitViewController.hide(.primary) 89 | secondaryViewController.showLabel(withText: "Secondary Row \(indexPath.row + 1)") 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Patient/TabBarViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class TabBarViewController: UITabBarController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | let primaryViewController = UIStoryboard(name: Constants.Storyboard.dashboard, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.dashboardNavigationController) 16 | 17 | let secondaryViewController = UIStoryboard(name: Constants.Storyboard.doctors, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.doctorsNC) 18 | 19 | 20 | 21 | let supplementaryViewController = UIStoryboard(name: Constants.Storyboard.appointments, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.appointmentsNC) 22 | 23 | primaryViewController.tabBarItem = UITabBarItem(title: "Me", image: UIImage.sfSymbol(Constants.SFSymbols.heart, color: .red), selectedImage: UIImage.sfSymbol(Constants.SFSymbols.heartFill, color: .red)) 24 | secondaryViewController.tabBarItem = UITabBarItem(title: "Doctors", image: UIImage.sfSymbol(Constants.SFSymbols.person, color: .red), selectedImage: UIImage.sfSymbol(Constants.SFSymbols.personFill, color: .red)) 25 | supplementaryViewController.tabBarItem = UITabBarItem(title: "Appointments", image: UIImage.sfSymbol(Constants.SFSymbols.calendar, color: .red), selectedImage: UIImage.sfSymbol(Constants.SFSymbols.calendar, color: .red)) 26 | 27 | // Add the view controllers to the tab bar controller 28 | self.viewControllers = [primaryViewController, secondaryViewController, supplementaryViewController] 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Shared/AutenticationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutenticationViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-11. 6 | // 7 | 8 | import UIKit 9 | import LocalAuthentication 10 | 11 | class AutenticationViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | } 16 | 17 | @IBAction func verifyButtonPressed(_ sender: UIButton) { 18 | self.authenticateUser() 19 | } 20 | 21 | override func viewDidAppear(_ animated: Bool) { 22 | super.viewDidAppear(animated) 23 | self.authenticateUser() 24 | } 25 | 26 | private func checkBiometricAvailability() -> Bool { 27 | var error: NSError? 28 | return LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) 29 | } 30 | 31 | private func authenticateUser() { 32 | let context = LAContext() 33 | var error: NSError? 34 | 35 | guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else { return } 36 | let reason = "Please use you Face ID to authenticate you and see you records" 37 | 38 | context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authenticationError in 39 | DispatchQueue.main.async { 40 | if success { 41 | let presetingViewController = UIDevice.current.userInterfaceIdiom == .phone ? UIStoryboard(name: Constants.Storyboard.tabBar, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.tabBarViewController) : self.getSplitViewController() 42 | presetingViewController.modalPresentationStyle = .fullScreen 43 | self.present(presetingViewController, animated: true) 44 | } else { 45 | guard (authenticationError as? LAError)?.code == .authenticationFailed else { return } 46 | self.authenticateUser() 47 | } 48 | } 49 | } 50 | 51 | } 52 | 53 | private func getSplitViewController() -> UISplitViewController { 54 | let splitViewController = UISplitViewController(style: .tripleColumn) 55 | splitViewController.preferredDisplayMode = .twoBesideSecondary 56 | 57 | let primaryViewController = UIStoryboard(name: Constants.Storyboard.main, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.primaryTableViewController) 58 | 59 | let secondaryViewController = UIStoryboard(name: "Doctors", bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.doctorsNC) 60 | let supplementaryViewController = SupplementaryViewController() 61 | 62 | splitViewController.setViewController(primaryViewController, for: .primary) 63 | splitViewController.setViewController(secondaryViewController, for: .secondary) 64 | splitViewController.setViewController(supplementaryViewController, for: .supplementary) 65 | splitViewController.modalPresentationStyle = .fullScreen 66 | 67 | return splitViewController 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Shared/InitialViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class InitialViewController: UIViewController { 11 | private var loggedIn = SessionManager.shared.isLoggedIn 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | override func viewDidAppear(_ animated: Bool) { 19 | super.viewDidAppear(animated) 20 | 21 | guard SessionManager.shared.isLoggedIn else { 22 | self.performSegue(withIdentifier: Constants.Segues.showLoginViewController, sender: nil) 23 | return 24 | } 25 | 26 | guard SessionManager.shared.getPatientId() != nil else { 27 | let doctorDashboard = UIStoryboard(name: Constants.Storyboard.doctorDashboard, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.doctorDashboardNC) 28 | doctorDashboard.modalPresentationStyle = .fullScreen 29 | self.present(doctorDashboard, animated: false) 30 | return 31 | } 32 | 33 | let dashboardViewController = UIStoryboard(name: Constants.Storyboard.tabBar, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.tabBarViewController) 34 | dashboardViewController.modalPresentationStyle = .fullScreen 35 | self.present(dashboardViewController, animated: false) 36 | 37 | /*guard let autenticationViewController = UIStoryboard(name: Constants.Storyboard.main, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.authenticationVC) as? AutenticationViewController else { return } 38 | autenticationViewController.modalPresentationStyle = .fullScreen 39 | self.present(autenticationViewController, animated: false)*/ 40 | } 41 | 42 | private func getSplitViewController() -> UISplitViewController { 43 | let splitViewController = UISplitViewController(style: .tripleColumn) 44 | splitViewController.preferredDisplayMode = .twoBesideSecondary 45 | 46 | let primaryViewController = UIStoryboard(name: Constants.Storyboard.main, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.primaryTableViewController) 47 | 48 | let secondaryViewController = UIStoryboard(name: "Doctors", bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.doctorsNC) 49 | let supplementaryViewController = SupplementaryViewController() 50 | 51 | splitViewController.setViewController(primaryViewController, for: .primary) 52 | splitViewController.setViewController(secondaryViewController, for: .secondary) 53 | splitViewController.setViewController(supplementaryViewController, for: .supplementary) 54 | splitViewController.modalPresentationStyle = .fullScreen 55 | 56 | return splitViewController 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Initializers/Shared/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-02-10. 6 | // 7 | 8 | import UIKit 9 | 10 | class LoginViewController: UIViewController { 11 | 12 | @IBOutlet weak var loginButton: UIButton! 13 | @IBOutlet weak var passwordTextField: UITextField! 14 | @IBOutlet weak var usernameTextField: UITextField! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | @IBAction func loginButtonPressed(_ sender: UIButton) { 21 | Task { @MainActor in 22 | guard let password = passwordTextField.text, 23 | let username = usernameTextField.text else { return } 24 | let response = await RequestManager.shared.request(endPoint: .login, method: .post, body: [ 25 | "username": username, 26 | "password": password 27 | ]) 28 | 29 | switch response.httpStatusCode { 30 | case .success: 31 | guard let body = response.body else { 32 | self.showFloatingAlert(text: "Oops! Something went wrong. Try again.", alertType: .error) 33 | return 34 | } 35 | 36 | let session = SessionManager(json: body) 37 | guard session.getPatientId() != nil else { 38 | let doctorDashboard = UIStoryboard(name: Constants.Storyboard.doctorDashboard, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.doctorDashboardNC) 39 | doctorDashboard.modalPresentationStyle = .fullScreen 40 | self.present(doctorDashboard, animated: true) 41 | return 42 | } 43 | let dashboardViewController = UIStoryboard(name: Constants.Storyboard.tabBar, bundle: nil).instantiateViewController(withIdentifier: Constants.ViewIdentifiers.tabBarViewController) 44 | dashboardViewController.modalPresentationStyle = .fullScreen 45 | self.present(dashboardViewController, animated: true) 46 | case .unauthorized: 47 | self.showFloatingAlert(text: "Incorrect credentials", alertType: .warning) 48 | default: 49 | self.showFloatingAlert(text: "Oops! Something went wrong. Try again.", alertType: .error) 50 | } 51 | } 52 | } 53 | 54 | @IBAction func signInWithAppleButtonPressed(_ sender: UIButton) { 55 | 56 | } 57 | 58 | @IBAction func forgotPasswordPressed(_ sender: UIButton) { 59 | } 60 | 61 | } 62 | 63 | extension LoginViewController: UITextFieldDelegate { 64 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 65 | guard let currentText = textField.text, 66 | let stringRange = Range(range, in: currentText) else { return false } 67 | let updatedText = currentText.replacingCharacters(in: stringRange, with: string) 68 | self.updateLoginButtonState(with: updatedText, isUsernameField: textField == usernameTextField) 69 | return true 70 | } 71 | 72 | func updateLoginButtonState(with text: String, isUsernameField: Bool) { 73 | let isUsernameValid = isUsernameField ? text.count >= 3 : (usernameTextField.text?.count ?? 0) >= 3 74 | let isPasswordValid = !isUsernameField ? text.count >= 6 : (passwordTextField.text?.count ?? 0) >= 6 75 | self.loginButton.isEnabled = isUsernameValid && isPasswordValid 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Appointments/AppointmentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppointmentViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppointmentViewController: UIViewController { 11 | @IBOutlet weak var tableView: UITableView! 12 | var appointment: Appointment! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.tableView.alwaysBounceVertical = false 17 | } 18 | 19 | override func viewDidAppear(_ animated: Bool) { 20 | super.viewDidAppear(animated) 21 | } 22 | 23 | @IBAction func deleteAppointmentButtonPressed(_ sender: UIButton) { 24 | guard let url = URL(string: RequestManager.shared.baseURL + "\(self.appointment.id)/" + EndPoint.appointment.rawValue) else { 25 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 26 | return 27 | } 28 | Task { 29 | guard await RequestManager.shared.request(url: url, method: .delete).httpStatusCode == .empty else { 30 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 31 | return 32 | } 33 | 34 | self.showFloatingAlert(text: "Appointment Deleted", alertType: .success) 35 | self.navigationController?.popViewController(animated: true) 36 | 37 | } 38 | } 39 | 40 | 41 | } 42 | 43 | 44 | extension AppointmentViewController: UITableViewDataSource, UITableViewDelegate { 45 | func numberOfSections(in tableView: UITableView) -> Int { 46 | return 3 47 | } 48 | 49 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 50 | section == 0 ? 2 : 1 51 | } 52 | 53 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 54 | switch indexPath.section { 55 | case 0: 56 | let doctor = self.appointment.doctor 57 | if indexPath.row == 0 { 58 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.imageCell, for: indexPath) as! ImageTableViewCell 59 | cell.separatorInset.removeSeparator() 60 | cell.customizeCell(url: doctor.profilePicture) 61 | return cell 62 | } 63 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 64 | cell.backgroundColor = .clear 65 | cell.separatorInset.removeSeparator() 66 | 67 | var content = UIListContentConfiguration.cell() 68 | content.textProperties.alignment = .center 69 | content.secondaryTextProperties.alignment = .center 70 | content.textProperties.font = UIFont(name: "HelveticaNeue-Medium", size: 22.0) ?? UIFont() 71 | content.text = doctor.fullName 72 | content.secondaryTextProperties.font = UIFont(name: "HelveticaNeue", size: 18.0) ?? UIFont() 73 | content.secondaryTextProperties.color = .secondaryLabel 74 | content.secondaryText = doctor.specialization 75 | 76 | cell.contentConfiguration = content 77 | return cell 78 | case 1: 79 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.dateTimeCell, for: indexPath) as! DateTimeTableViewCell 80 | cell.dateLabel.text = self.appointment.date.shortDateWithYear 81 | cell.timeLabel.text = self.appointment.date.twelveHoursFormmatedHour 82 | return cell 83 | default: 84 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 85 | cell.contentView.backgroundColor = .secondarySystemGroupedBackground 86 | var content = UIListContentConfiguration.cell() 87 | content.text = self.appointment.notes 88 | content.textProperties.color = .secondaryLabel 89 | cell.contentConfiguration = content 90 | return cell 91 | } 92 | } 93 | 94 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 95 | if section == 1 { 96 | return "Date and Time" 97 | } else if section == 2 { 98 | return "Notes" 99 | } 100 | return nil 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Appointments/AppointmentsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppointmentsViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppointmentsViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | var selectedDate = Date() 14 | var selectedAppointment: Appointment? 15 | var appointments: [Appointment] = [] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | override func viewDidAppear(_ animated: Bool) { 24 | super.viewDidAppear(animated) 25 | self.loadAppointmnets() 26 | } 27 | 28 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 29 | guard segue.identifier == Constants.Segues.showAppointmentVC, 30 | let destination = segue.destination as? AppointmentViewController, 31 | let selectedAppointment else { return } 32 | destination.appointment = selectedAppointment 33 | } 34 | 35 | private func loadAppointmnets() { 36 | guard let url = RequestManager.shared.getURLWithPatientFor(endpoint: .appointments) else { 37 | self.showFloatingAlert(text: "Error loading appointments", alertType: .error) 38 | return 39 | } 40 | Task { 41 | 42 | guard let responseData = await RequestManager.shared.request(url: url, method: .get).rawData else { 43 | self.showFloatingAlert(text: "Error loading appointments", alertType: .error) 44 | return 45 | } 46 | 47 | do { 48 | let decoder = JSONDecoder() 49 | decoder.dateDecodingStrategy = .customISO8601 50 | let appointmentsContainer = try decoder.decode(AppointmentsContainer.self, from: responseData) 51 | self.appointments = appointmentsContainer.appointments.filter({ $0.date.isOnSameDayThan(date: self.selectedDate) }) 52 | } catch { 53 | self.appointments.removeAll() 54 | self.showFloatingAlert(text: "Error loading appointments", alertType: .error) 55 | } 56 | self.tableView.reloadData() 57 | } 58 | } 59 | 60 | @objc private func datePickerChanged(_ sender: UIDatePicker) { 61 | self.selectedDate = sender.date 62 | self.loadAppointmnets() 63 | } 64 | 65 | } 66 | 67 | extension AppointmentsViewController: UITableViewDataSource, UITableViewDelegate { 68 | func numberOfSections(in tableView: UITableView) -> Int { 69 | return 2 70 | } 71 | 72 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 73 | section == 0 ? 1 : self.appointments.count 74 | } 75 | 76 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 77 | guard indexPath.section == 0 else { 78 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.appointmentCell, for: indexPath) as! AppointmentTableViewCell 79 | cell.separatorInset.addSeparator() 80 | cell.customizeCell(appointment: self.appointments[indexPath.row]) 81 | return cell 82 | } 83 | 84 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.datePickerCell, for: indexPath) as! DatePickerTableViewCell 85 | cell.datePicker.date = self.selectedDate 86 | cell.datePicker.addTarget(self, action: #selector(self.datePickerChanged(_:)), for: .valueChanged) 87 | cell.updateDate() 88 | return cell 89 | } 90 | 91 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 92 | guard indexPath.section == 1 else { return } 93 | self.selectedAppointment = self.appointments[indexPath.row] 94 | tableView.deselectRow(at: indexPath, animated: true) 95 | self.performSegue(withIdentifier: Constants.Segues.showAppointmentVC, sender: nil) 96 | } 97 | 98 | func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { 99 | guard indexPath.section == 1 else { return nil } 100 | 101 | let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completionHandler) in 102 | guard let url = URL(string: RequestManager.shared.baseURL + "\(self.appointments[indexPath.row].id)/" + EndPoint.appointment.rawValue) else { 103 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 104 | completionHandler(false) 105 | return 106 | } 107 | Task { 108 | guard await RequestManager.shared.request(url: url, method: .delete).httpStatusCode == .empty else { 109 | self.showFloatingAlert(text: "Error deleting appointment", alertType: .error) 110 | completionHandler(false) 111 | return 112 | } 113 | 114 | self.appointments.remove(at: indexPath.row) 115 | tableView.deleteRows(at: [indexPath], with: .automatic) 116 | completionHandler(true) 117 | self.showFloatingAlert(text: "Appointment Deleted", alertType: .success) 118 | } 119 | } 120 | 121 | deleteAction.image = .sfSymbol(Constants.SFSymbols.trashCircleFill, color: .white) 122 | let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) 123 | 124 | return configuration 125 | } 126 | 127 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 128 | section == 0 ? self.selectedDate.dayOfWeek.uppercased() : "Upcoming Appointments" 129 | } 130 | 131 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 132 | section == 0 ? 50.0 : 20.0 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Dashboard/FilterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 30/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class FilterViewController: UIViewController { 11 | @IBOutlet weak var tableView: UITableView! 12 | var filters: ((_ from: Date?, _ to: Date?) -> Void)? 13 | var fromDatePicker: UIDatePicker? 14 | var toDatePicker: UIDatePicker? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | @IBAction func cancelButtonPressed(_ sender: UIBarButtonItem) { 21 | self.dismiss(animated: true) 22 | } 23 | 24 | @objc private func datePickerPressed(_ sender: UIDatePicker) { 25 | self.tableView.scrollToRow(at: IndexPath(row: 0, section: 2), at: .top, animated: true) 26 | } 27 | } 28 | 29 | extension FilterViewController: UITableViewDataSource, UITableViewDelegate { 30 | func numberOfSections(in tableView: UITableView) -> Int { 31 | return 4 32 | } 33 | 34 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 35 | return 1 36 | } 37 | 38 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 39 | if indexPath.section > 1 { 40 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 41 | var content = UIListContentConfiguration.cell() 42 | content.textProperties.font = UIFont(name: "HelveticaNeue-Medium", size: 18.0) ?? UIFont() 43 | content.textProperties.alignment = .center 44 | if indexPath.section == 2 { 45 | cell.backgroundColor = .systemRed 46 | content.textProperties.color = .white 47 | content.text = "Save" 48 | } else { 49 | cell.backgroundColor = .systemGroupedBackground 50 | content.textProperties.color = .systemRed 51 | content.text = "Reset filters" 52 | } 53 | 54 | cell.contentConfiguration = content 55 | return cell 56 | } 57 | 58 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.inlineDatePicker, for: indexPath) as! InlineDatePickerTableViewCell 59 | if indexPath.section == 0 { 60 | if let fromDatePicker { 61 | cell.datePicker = fromDatePicker 62 | } else { 63 | self.fromDatePicker = cell.datePicker 64 | } 65 | self.fromDatePicker?.addTarget(self, action: #selector(self.datePickerPressed(_:)), for: .valueChanged) 66 | } else { 67 | if let toDatePicker { 68 | cell.datePicker = toDatePicker 69 | } else { 70 | self.toDatePicker = cell.datePicker 71 | } 72 | } 73 | return cell 74 | } 75 | 76 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 77 | if indexPath.section == 2, 78 | let fromDate = fromDatePicker?.date, 79 | let toDate = toDatePicker?.date { 80 | self.dismiss(animated: true) { 81 | self.filters?(fromDate, toDate) 82 | } 83 | 84 | } else if indexPath.section == 3 { 85 | self.dismiss(animated: true) { 86 | self.filters?(nil, nil) 87 | } 88 | } 89 | 90 | } 91 | 92 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 93 | section == 0 ? 40.0 : 0.0 94 | } 95 | 96 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 97 | switch section { 98 | case 0: 99 | return "Start Date" 100 | case 1: 101 | return "End Date" 102 | default: 103 | return nil 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Dashboard/HistoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | import HealthKit 11 | 12 | class HistoryViewController: UIViewController { 13 | 14 | @IBOutlet weak var tableView: UITableView! 15 | @IBOutlet weak var filterButton: UIBarButtonItem! 16 | private let alert = UIAlertController(title: nil, message: "Loading data...", preferredStyle: .alert) 17 | private let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) 18 | var healthIdentifier: HKQuantityTypeIdentifier! 19 | var appliedFilter = false 20 | var healthRecords = [GenericHealthData]() 21 | var from: Date? 22 | var to: Date? 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.loadingIndicator.hidesWhenStopped = true 27 | self.loadingIndicator.style = UIActivityIndicatorView.Style.medium 28 | self.alert.view.addSubview(loadingIndicator) 29 | self.loadData() 30 | } 31 | 32 | @IBAction @objc func filterButtonPressed(_ sender: Any) { 33 | self.performSegue(withIdentifier: Constants.Segues.showFilterVC, sender: nil) 34 | } 35 | 36 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 37 | super.prepare(for: segue, sender: sender) 38 | guard let destination = segue.destination as? FilterViewController else { return } 39 | destination.filters = { [weak self] (from, to) in 40 | guard let self else { return } 41 | self.from = from 42 | self.to = to 43 | self.appliedFilter = self.from != nil && self.to != nil 44 | self.loadData() 45 | } 46 | } 47 | 48 | private func loadData() { 49 | guard let unit = HealthKitManager.shared.getUnitFor(identifier: self.healthIdentifier) else { return } 50 | let initalDate = from ?? Date().onPast(days: self.daysFor(identifier: self.healthIdentifier)) 51 | let finalDate = to ?? Date() 52 | HealthKitManager.shared.quantityRecordsFor(typeIdentifier: self.healthIdentifier, withUnit: unit, from: initalDate, to: finalDate) { records in 53 | let healthRecords = records.map { GenericHealthData(value: $0.value, date: $0.date) } 54 | DispatchQueue.main.async { 55 | guard !healthRecords.isEmpty else { 56 | self.showFloatingAlert(text: "No records available", alertType: .warning) 57 | return 58 | } 59 | self.healthRecords = healthRecords 60 | self.tableView.reloadData() 61 | self.updateRightBarButtonImage() 62 | } 63 | } 64 | } 65 | 66 | private func updateRightBarButtonImage() { 67 | let image = self.appliedFilter ? UIImage.sfSymbol("line.3.horizontal.decrease.circle.fill", color: .systemRed) : UIImage.sfSymbol("line.3.horizontal.decrease.circle", color: .systemRed) 68 | self.filterButton.image = image 69 | } 70 | 71 | private func daysFor(identifier: HKQuantityTypeIdentifier) -> Int { 72 | switch identifier { 73 | case .bodyMass: 74 | return .year 75 | case .heartRate, .activeEnergyBurned: 76 | return .aDay 77 | default: 78 | return .week 79 | } 80 | } 81 | } 82 | 83 | extension HistoryViewController: UITableViewDataSource, UITableViewDelegate { 84 | func numberOfSections(in tableView: UITableView) -> Int { 85 | return 2 86 | } 87 | 88 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 89 | section == 0 ? 1 : self.healthRecords.count 90 | } 91 | 92 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 93 | guard indexPath.section != 0 else { 94 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 95 | 96 | cell.contentConfiguration = UIHostingConfiguration { 97 | LineChart(healthRecords: self.healthRecords) 98 | } 99 | return cell 100 | } 101 | 102 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.historyRecordCell, for: indexPath) as! HistoryRecordsTableViewCell 103 | let healtDataRow = self.healthRecords[indexPath.row] 104 | cell.customizeCell(date: healtDataRow.date.longDate, value: "\(healtDataRow.value)", time: healtDataRow.date.twelveHoursFormmatedHour) 105 | return cell 106 | } 107 | 108 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 109 | indexPath.section == 0 ? 250 : UITableView.automaticDimension 110 | } 111 | 112 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 113 | section == 1 ? "Records" : nil 114 | } 115 | 116 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 117 | return 30.0 118 | } 119 | } 120 | 121 | 122 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Dashboard/PermissionsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionsViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-01-15. 6 | // 7 | 8 | import UIKit 9 | 10 | class PermissionsViewController: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | override func viewDidAppear(_ animated: Bool) { 19 | super.viewDidAppear(animated) 20 | HealthKitManager.shared.requestAuthorization { allowed, _ in 21 | guard !allowed else { 22 | DispatchQueue.main.async { 23 | self.dismiss(animated: true) 24 | } 25 | return 26 | } 27 | } 28 | } 29 | 30 | @IBAction func allowAccessButtonPressed(_ sender: UIButton) { 31 | guard let url = URL(string: "x-apple-health://") else { return } 32 | UIApplication.shared.open(url) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Dashboard/SleepHistoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SleepHistoryViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-01-26. 6 | // 7 | 8 | import UIKit 9 | 10 | class SleepHistoryViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.navigationController?.setMinimalBackButton() 16 | } 17 | 18 | override func viewDidAppear(_ animated: Bool) { 19 | super.viewDidAppear(animated) 20 | self.navigationController?.cleanNavigation() 21 | } 22 | 23 | 24 | override func viewWillDisappear(_ animated: Bool) { 25 | super.viewWillDisappear(animated) 26 | self.navigationController?.defaultNavigation() 27 | } 28 | 29 | } 30 | 31 | extension SleepHistoryViewController: UITableViewDataSource, UITableViewDelegate { 32 | func numberOfSections(in tableView: UITableView) -> Int { 33 | return 4 34 | } 35 | 36 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | section == 3 ? 10 : 1 38 | } 39 | 40 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 | 42 | switch indexPath.section { 43 | case 0, 2: 44 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 45 | var content = UIListContentConfiguration.cell() 46 | content.textProperties.alignment = .center 47 | if indexPath.section == 0 { 48 | cell.selectionStyle = .none 49 | content.textProperties.font = UIFont(name: "HelveticaNeue-Bold", size: 30.0) ?? UIFont() 50 | content.textProperties.color = .white 51 | content.secondaryTextProperties.font = UIFont(name: "HelveticaNeue", size: 20.0) ?? UIFont() 52 | content.secondaryTextProperties.color = .lightGray 53 | content.secondaryTextProperties.alignment = .center 54 | content.text = "8 Sleep Hours" 55 | content.secondaryText = "March 24, 2024" 56 | } else { 57 | cell.selectionStyle = .default 58 | cell.backgroundColor = .purple 59 | content.textProperties.font = UIFont(name: "HelveticaNeue", size: 18.0) ?? UIFont() 60 | content.textProperties.color = .yellow 61 | content.textProperties.color = .white 62 | content.text = "See my stadistics" 63 | } 64 | cell.contentConfiguration = content 65 | return cell 66 | case 1: 67 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.imageCell, for: indexPath) as! ImageTableViewCell 68 | cell.customizeCell(image: UIImage.sfSymbol("moon.fill", color: .yellow)) 69 | return cell 70 | default: 71 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.historyRecordCell, for: indexPath) as! HistoryRecordsTableViewCell 72 | cell.customizeCell(date: Date().formatted(), value: "100", time: "01:00 PM") 73 | return cell 74 | } 75 | } 76 | 77 | 78 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 79 | indexPath.section == 1 ? self.safeAreaHeight * 0.65 : UITableView.automaticDimension 80 | } 81 | 82 | 83 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 84 | tableView.deselectRow(at: indexPath, animated: true) 85 | tableView.scrollToRow(at: IndexPath(row: 0, section: 3), at: .top, animated: true) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Doctors/DoctorProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoctorProfileViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class DoctorProfileViewController: UIViewController { 11 | 12 | var doctor: Doctor! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.navigationController?.setMinimalBackButton() 17 | } 18 | 19 | override func viewDidAppear(_ animated: Bool) { 20 | super.viewDidAppear(animated) 21 | self.navigationController?.cleanNavigation() 22 | } 23 | 24 | 25 | override func viewWillDisappear(_ animated: Bool) { 26 | super.viewWillDisappear(animated) 27 | self.navigationController?.defaultNavigation() 28 | } 29 | 30 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 31 | guard segue.identifier == Constants.Segues.showBookAppointmentVC, 32 | let destination = segue.destination as? BookAppointmentViewController else { return } 33 | destination.doctor = self.doctor 34 | } 35 | } 36 | 37 | extension DoctorProfileViewController: UITableViewDataSource, UITableViewDelegate { 38 | func numberOfSections(in tableView: UITableView) -> Int { 39 | return 1 40 | } 41 | 42 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 43 | return 4 44 | } 45 | 46 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 47 | switch indexPath.row { 48 | case 0: 49 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.profileImageCell, for: indexPath) as! ProfileImageTableViewCell 50 | cell.customizeCell(imageURL: self.doctor.backgroundImage, title: self.doctor.fullName, detail: self.doctor.specialization) 51 | return cell 52 | case 2: 53 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.buttonCell, for: indexPath) as! ButtonCellTableViewCell 54 | cell.button.isUserInteractionEnabled = false 55 | cell.button.setTitle("Book Appointment", for: .normal) 56 | return cell 57 | default: 58 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 59 | var content = UIListContentConfiguration.cell() 60 | if indexPath.row == 1 { 61 | let baseString = self.doctor.description ?? .empty 62 | let fullRange = NSRange(location: 0, length: baseString.count) 63 | let attributedString = NSMutableAttributedString(string: baseString) 64 | attributedString.addAttribute(.font, value: UIFont.systemFont(ofSize: 16), range: fullRange) 65 | attributedString.addAttribute(.foregroundColor, value: UIColor.secondaryLabel, range: fullRange) 66 | if let readMoreRange = baseString.range(of: "Read more") { 67 | let nsRange = NSRange(readMoreRange, in: baseString) 68 | attributedString.addAttribute(.foregroundColor, value: UIColor.systemRed, range: nsRange) 69 | } 70 | content.attributedText = attributedString 71 | cell.contentConfiguration = content 72 | } else { 73 | cell.backgroundColor = .clear 74 | content.text = "Delete Doctor" 75 | content.textProperties.color = .red 76 | content.textProperties.alignment = .center 77 | cell.contentConfiguration = content 78 | } 79 | return cell 80 | } 81 | 82 | } 83 | 84 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 85 | if indexPath.row == 2 { 86 | self.performSegue(withIdentifier: Constants.Segues.showBookAppointmentVC, sender: nil) 87 | } else if indexPath.row == 3 { 88 | guard let patientId = SessionManager.shared.getPatientId() else { return } 89 | Task { 90 | let response = await RequestManager.shared.request(endPoint: .doctors, method: .delete, body: [ 91 | "userId": patientId, 92 | "doctorId": self.doctor.id 93 | ]) 94 | 95 | guard response.httpStatusCode == .success else { 96 | self.showFloatingAlert(text: "Error at deleting", alertType: .error) 97 | return 98 | } 99 | 100 | self.showFloatingAlert(text: "Doctor deleted", alertType: .success) 101 | self.navigationController?.popViewController(animated: true) 102 | } 103 | } 104 | } 105 | 106 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 107 | indexPath.row == 0 ? self.view.frame.size.height * 0.50 : UITableView.automaticDimension 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Doctors/DoctorsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DoctorsViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class DoctorsViewController: UIViewController { 12 | 13 | @IBOutlet weak var doctorsCollectionView: UICollectionView! 14 | var doctors: [Doctor] = [] 15 | var selectedDoctor: Doctor? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | } 21 | 22 | override func viewDidAppear(_ animated: Bool) { 23 | super.viewDidAppear(animated) 24 | guard let url = RequestManager.shared.getURLWithPatientFor(endpoint: .doctors) else { 25 | self.showFloatingAlert(text: "Error loading doctors", alertType: .error) 26 | return 27 | } 28 | Task { 29 | 30 | guard let responseData = await RequestManager.shared.request(url: url, method: .get).rawData else { 31 | self.showFloatingAlert(text: "Error loading doctors", alertType: .error) 32 | return 33 | } 34 | 35 | do { 36 | let decoder = JSONDecoder() 37 | let doctors = try decoder.decode([String: [Doctor]].self, from: responseData)["doctors"] ?? [] 38 | self.doctors = doctors 39 | self.doctorsCollectionView.reloadData() 40 | 41 | } catch { 42 | self.showFloatingAlert(text: "Error loading doctors", alertType: .error) 43 | } 44 | 45 | } 46 | } 47 | 48 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 49 | guard segue.identifier == Constants.Segues.showDoctorProfileVC, 50 | let destination = segue.destination as? DoctorProfileViewController, 51 | let selectedDoctor else { return } 52 | 53 | destination.doctor = selectedDoctor 54 | } 55 | 56 | @IBAction func addDoctorButtonPressed(_ sender: UIBarButtonItem) { 57 | let hostingController = UIHostingController(rootView: QRView()) 58 | self.navigationController?.pushViewController(hostingController, animated: true) 59 | } 60 | 61 | } 62 | 63 | extension DoctorsViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 64 | func numberOfSections(in collectionView: UICollectionView) -> Int { 65 | return 1 66 | } 67 | 68 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 69 | return self.doctors.count 70 | } 71 | 72 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 73 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.Cells.doctorCell, for: indexPath) as! DoctorCollectionViewCell 74 | cell.cutomizeCell(doctor: self.doctors[indexPath.row]) 75 | return cell 76 | } 77 | 78 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 79 | self.selectedDoctor = self.doctors[indexPath.row] 80 | self.performSegue(withIdentifier: Constants.Segues.showDoctorProfileVC, sender: nil) 81 | } 82 | 83 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 84 | let height = view.frame.size.height 85 | let width = view.safeAreaLayoutGuide.layoutFrame.size.width 86 | 87 | let device = UIDevice.current 88 | 89 | if device.userInterfaceIdiom == .phone { //If device is an iPhone 90 | //if device.orientation.isLandscape { //If device is Landscaoe 91 | // return CGSize(width: width * 0.45, height: height * 0.4) 92 | //} 93 | 94 | if indexPath.section == 0 { 95 | return CGSize(width: width * 0.45, height: height * 0.30) 96 | } 97 | 98 | return CGSize(width: width * 0.40, height: height * 0.30) 99 | } 100 | return CGSize(width: width * 0.48, height: height * 0.35) //If device is iPad 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Ingested Food/CaloriesSummaryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CaloriesSummaryViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 03/01/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class CaloriesSummaryViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | @IBOutlet weak var createRecommendationButton: UIButton! 14 | private let imagePicker = UIImagePickerController() 15 | private var ingredientsImage: UIImage? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | self.imagePicker.delegate = self 20 | self.imagePicker.sourceType = .photoLibrary 21 | 22 | } 23 | 24 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 25 | guard let pickedImage = info[.originalImage] as? UIImage else { 26 | self.dismiss(animated: true, completion: nil) 27 | return 28 | } 29 | self.createRecommendationButton.isEnabled = true 30 | self.ingredientsImage = pickedImage 31 | self.tableView.reloadSections(IndexSet(arrayLiteral: 2), with: .automatic) 32 | self.dismiss(animated: true, completion: nil) 33 | } 34 | 35 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 36 | self.dismiss(animated: true, completion: nil) 37 | } 38 | 39 | @IBAction func createRecommendationButtonPressed(_ sender: UIButton) { 40 | self.performSegue(withIdentifier: Constants.Segues.showRecommendationViewController, sender: nil) 41 | } 42 | } 43 | 44 | extension CaloriesSummaryViewController: UITableViewDataSource, UITableViewDelegate { 45 | func numberOfSections(in tableView: UITableView) -> Int { 46 | return 3 47 | } 48 | 49 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 50 | return 1 51 | } 52 | 53 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 54 | guard indexPath.section < 2 else { 55 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.imageCell, for: indexPath) as! ImageTableViewCell 56 | if let ingredientsImage { 57 | cell.customImageView.image = ingredientsImage 58 | } 59 | return cell 60 | } 61 | 62 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 63 | var configuration = UIListContentConfiguration.cell() 64 | if indexPath.section == 0 { 65 | configuration.text = "Calories consumed" 66 | configuration.secondaryText = "1345" 67 | } else { 68 | configuration.text = "Calories Left" 69 | configuration.secondaryText = "1450" 70 | } 71 | cell.contentConfiguration = configuration 72 | return cell 73 | } 74 | 75 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 76 | guard indexPath.section == 2 else { return } 77 | self.present(self.imagePicker, animated: true, completion: nil) 78 | } 79 | 80 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 81 | return indexPath.section == 2 ? 300.0 : UITableView.automaticDimension 82 | } 83 | 84 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 85 | guard section == 2 else { return nil } 86 | let text = self.ingredientsImage != nil ? "Image selected" : "Select an image" 87 | return text 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Ingested Food/RecommendationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecommendationViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 03/01/24. 6 | // 7 | 8 | import UIKit 9 | 10 | class RecommendationViewController: UIViewController { 11 | 12 | @IBOutlet weak var resultTextView: UITextView! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | /*guard let url = URL(string: "https://api.openai.com/v1/engines/davinci/completions"), 17 | let openAIKey = Config.getPropertieListValue(forKey: "openAIKey", in: "keys") else { return } 18 | let body: [String: Any] = [ 19 | "model": "text-davinci-003", 20 | "prompt": "this is a testing API call, can you response if all correct", 21 | "max_tokens": 100 22 | ] 23 | let headers: [String: String] = [ 24 | "Authorization": "Bearer \(openAIKey)", 25 | "Content-Type": "application/json" 26 | ] 27 | 28 | Task { 29 | let (statusCode, response) = await RequestManager.shared.request(endPoint: url, method: .post, body: body, headers: headers) 30 | print("Status Code: \(statusCode)") 31 | print("Response: \(String(describing: response))") 32 | }*/ 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Profile/FitzpatrickExplanationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FitzpatrickExplanationViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-01-14. 6 | // 7 | 8 | import UIKit 9 | import HealthKit 10 | 11 | class FitzpatrickExplanationViewController: UIViewController { 12 | 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | self.navigationController?.setMinimalBackButton() 17 | } 18 | 19 | } 20 | 21 | extension FitzpatrickExplanationViewController: UITableViewDataSource, UITableViewDelegate { 22 | func numberOfSections(in tableView: UITableView) -> Int { 23 | return 2 24 | } 25 | 26 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 27 | section == 0 ? 1 : 6 28 | } 29 | 30 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 31 | if indexPath.section == 0 { 32 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 33 | var content = UIListContentConfiguration.cell() 34 | content.textProperties.font = UIFont(name: "HelveticaNeue-Medium", size: 18.0) ?? UIFont() 35 | content.text = "The Fitzpatrick scale is a numerical classification for skin color based on the skins response to sun exposure in terms of the degree of burning and tanning." 36 | cell.contentConfiguration = content 37 | return cell 38 | } 39 | 40 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.colorCell, for: indexPath) as! ColorTableViewCell 41 | 42 | switch indexPath.row { 43 | case 0: 44 | cell.titleLabel.text = "I" 45 | cell.valueLabel.text = "Pale white skin that always burns easily in the sun and never tans." 46 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.I) 47 | case 1: 48 | cell.titleLabel.text = "II" 49 | cell.valueLabel.text = "White skin that burns easily and tans minimally." 50 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.II) 51 | case 2: 52 | cell.titleLabel.text = "III" 53 | cell.valueLabel.text = "White to light brown skin that burns moderately and tans uniformly." 54 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.III) 55 | case 3: 56 | cell.titleLabel.text = "IV" 57 | cell.valueLabel.text = "Beige-olive, lightly tanned skin that burns minimally and tans moderately." 58 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.IV) 59 | case 4: 60 | cell.titleLabel.text = "V" 61 | cell.valueLabel.text = "Brown skin that rarely burns and tans profusely." 62 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.V) 63 | case 5: 64 | cell.titleLabel.text = "VI" 65 | cell.valueLabel.text = "Dark brown to black skin that never burns and tans profusely." 66 | cell.colorView.backgroundColor = HealthKitManager.shared.colorForFitzpatrickSkinType(.VI) 67 | default: break 68 | } 69 | return cell 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Profile/FitzpatrickViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FitzpatrickViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Moisés Córdova on 2024-01-14. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | import HealthKit 11 | import CoreML 12 | import Vision 13 | 14 | class FitzpatrickViewController: UIViewController { 15 | @IBOutlet weak var emojiLabel: UILabel! 16 | @IBOutlet weak var detailLabel: UILabel! 17 | private var skinType: Int? 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | if let fitzpatrickSkinType = HealthKitManager.shared.retrieveSecureFitzpatrickSkinType() { 22 | self.skinType = fitzpatrickSkinType 23 | self.detailLabel.text = "Fitzpatrick Scale: \(fitzpatrickSkinType.toRomanNumeral() ?? .empty)" 24 | self.emojiLabel.text = self.getEmojiFor(fitzpatrickSkinType: fitzpatrickSkinType.toRomanNumeral() ?? .empty) 25 | } else { 26 | HealthKitManager.shared.getFitzpatrickSkinType { value, _ in 27 | guard let value else { return } 28 | self.detailLabel.text = "Fitzpatrick Scale: \(value)" 29 | self.emojiLabel.text = self.getEmojiFor(fitzpatrickSkinType: value) 30 | } 31 | } 32 | } 33 | 34 | @IBAction func scanFaceButtonPressed(_ sender: UIButton) { 35 | let scannerView = ScannerView(scanType: .face, description: "Center your face in the oval, face will be automatically detected", onImageCapture: { faceImage in 36 | guard let faceImage else { return } 37 | self.fitzpatrickPredictionFrom(image: faceImage) { scale in 38 | guard let scale, 39 | let stringScale = scale.toRomanNumeral() else { return } 40 | self.skinType = scale 41 | self.detailLabel.text = "Fitzpatrick Scale: \(stringScale)" 42 | self.emojiLabel.text = self.getEmojiFor(fitzpatrickSkinType: stringScale) 43 | } 44 | 45 | }) 46 | let hostingController = UIHostingController(rootView: scannerView) 47 | self.present(hostingController, animated: true) 48 | } 49 | 50 | @IBAction func saveButtonPressed(_ sender: UIButton) { 51 | if let skinType { 52 | if HealthKitManager.shared.saveFitzpatrick(skinType: skinType) { print("SAVED") } 53 | } 54 | self.dismiss(animated: true) 55 | } 56 | 57 | @IBAction func moreInformationButtonPressed(_ sender: UIBarButtonItem) { 58 | self.performSegue(withIdentifier: "ShowExplanationVC", sender: nil) 59 | } 60 | 61 | private func fitzpatrickPredictionFrom(image: UIImage, completion: @escaping (Int?) -> Void) { 62 | guard let model = try? VNCoreMLModel(for: FitzpatrickScale().model) else { 63 | completion(nil) 64 | return 65 | } 66 | 67 | let request = VNCoreMLRequest(model: model) { request, error in 68 | guard let results = request.results as? [VNClassificationObservation], 69 | let topResult = results.first, 70 | let scaleValue = Int(topResult.identifier) else { 71 | completion(nil) 72 | return 73 | } 74 | 75 | DispatchQueue.main.async { 76 | completion(scaleValue) 77 | } 78 | } 79 | 80 | guard let ciImage = CIImage(image: image) else { 81 | completion(nil) 82 | return 83 | } 84 | 85 | let handler = VNImageRequestHandler(ciImage: ciImage) 86 | do { 87 | try handler.perform([request]) 88 | } catch { 89 | completion(nil) 90 | } 91 | } 92 | 93 | private func getEmojiFor(fitzpatrickSkinType: String) -> String { 94 | switch fitzpatrickSkinType { 95 | case "I": 96 | return "🧑🏻‍🦲" 97 | case "II": 98 | return "🧑🏼‍🦲" 99 | case "III": 100 | return "🧑🏽‍🦲" 101 | case "IV": 102 | return "🧑🏾‍🦲" 103 | case "V", "VI": 104 | return "🧑🏿‍🦲" 105 | default: 106 | return "🧑‍🦲" 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Patient/Profile/ProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileViewController.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 28/12/23. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class ProfileViewController: UIViewController { 12 | 13 | var person: Person? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | // Do any additional setup after loading the view. 19 | } 20 | @IBAction func doneButtonPressed(_ sender: UIBarButtonItem) { 21 | self.dismiss(animated: true) 22 | } 23 | @IBAction func cancelButtonPressed(_ sender: UIBarButtonItem) { 24 | self.dismiss(animated: true) 25 | } 26 | } 27 | 28 | extension ProfileViewController: UITableViewDataSource, UITableViewDelegate { 29 | func numberOfSections(in tableView: UITableView) -> Int { 30 | return 4 31 | } 32 | 33 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 34 | switch section { 35 | case 0: 36 | return 2 37 | case 1: 38 | return 4 39 | default: 40 | return 1 41 | } 42 | } 43 | 44 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | if indexPath.section == 0 { 46 | if indexPath.row == 0 { 47 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.imageCell, for: indexPath) as! ImageTableViewCell 48 | cell.customizeCell(image: UIImage(named: "profile") ?? UIImage()) 49 | cell.separatorInset.removeSeparator() 50 | return cell 51 | } 52 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.textFieldCell) as! TextFieldTableViewCell 53 | cell.separatorInset.removeSeparator() 54 | cell.textField.placeholder = "Enter your name" 55 | cell.textField.text = "John Doe" 56 | return cell 57 | } else if indexPath.section == 1 { 58 | guard indexPath.row != 3 else { 59 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.colorCell, for: indexPath) as! ColorTableViewCell 60 | cell.selectionStyle = .default 61 | cell.accessoryType = .disclosureIndicator 62 | var stringValue = String.empty 63 | 64 | if let fitzpatrickSkinType = person?.fitzpatrickSkinType { stringValue = fitzpatrickSkinType.type } 65 | cell.customizeCell(title: "Fitzpatrick Skin Type", value: stringValue, color: person?.fitzpatrickSkinType.color ?? .clear) 66 | return cell 67 | } 68 | 69 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) as! AppointmentTableViewCell 70 | cell.selectionStyle = .none 71 | cell.backgroundColor = .secondarySystemGroupedBackground 72 | cell.separatorInset.addSeparator() 73 | var content = UIListContentConfiguration.cell() 74 | content.textProperties.font = UIFont(name: "HelveticaNeue-Medium", size: 18.0) ?? UIFont() 75 | content.secondaryTextProperties.font = UIFont(name: "HelveticaNeue", size: 18.0) ?? UIFont() 76 | content.secondaryTextProperties.color = .secondaryLabel 77 | var stringValue = String.empty 78 | switch indexPath.row { 79 | case 0: 80 | content.text = "Biological Sex" 81 | if let biologicalSex = self.person?.biologicalSex { stringValue = "\(biologicalSex)" } 82 | case 1: 83 | content.text = "Age" 84 | if let age = self.person?.age { stringValue = "\(age)" } 85 | case 2: 86 | content.text = "Wheelchair Use" 87 | stringValue = person?.wheelCharUse ?? false ? "Yes" : "No" 88 | case 3: 89 | content.text = "Blood Type" 90 | if let bloodType = person?.bloodYpe { stringValue = bloodType } 91 | default: break 92 | } 93 | content.secondaryText = stringValue 94 | cell.contentConfiguration = content 95 | 96 | return cell 97 | } 98 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cells.basicCell, for: indexPath) 99 | var content = UIListContentConfiguration.cell() 100 | content.textProperties.alignment = .center 101 | cell.selectionStyle = .default 102 | 103 | if indexPath.section == 2 { 104 | cell.backgroundColor = .systemRed 105 | content.text = "Save" 106 | content.textProperties.color = .white 107 | } else { 108 | cell.backgroundColor = .clear 109 | content.text = "Log Out" 110 | content.textProperties.color = .systemRed 111 | } 112 | cell.contentConfiguration = content 113 | 114 | return cell 115 | } 116 | 117 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 118 | if indexPath.row == 3 { 119 | self.performSegue(withIdentifier: Constants.Segues.showFitzpatrickView, sender: nil) 120 | } else if indexPath.section == 3 { 121 | SessionManager.shared.logOut() 122 | } 123 | tableView.deselectRow(at: indexPath, animated: true) 124 | } 125 | 126 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 127 | return indexPath.section == 0 && indexPath.row == 0 ? 150 : UITableView.automaticDimension 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Shared/Camera Scanning/QRView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRView.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 30/12/23. 6 | // 7 | 8 | import SwiftUI 9 | import CoreImage.CIFilterBuiltins 10 | 11 | struct QRView: View { 12 | @State private var isPresentingCamera = false 13 | var body: some View { 14 | VStack { 15 | Spacer() 16 | ZStack { 17 | RoundedRectangle(cornerRadius: 18.0) 18 | .frame(width: 350.0, height: 350.0) 19 | .foregroundStyle(Color(uiColor: .secondarySystemBackground)) 20 | .shadow(color: .gray, radius: 20, x: 0, y: 20) 21 | Image(uiImage: generateQRCode(from: "Value")) 22 | .resizable() 23 | .interpolation(.none) 24 | .scaledToFit() 25 | .frame(width: 250, height: 250) 26 | .overlay( 27 | Image("profile") 28 | .resizable() 29 | .frame(width: 60, height: 60) 30 | .background(Color.white) 31 | .clipShape(Circle()) 32 | , alignment: .center 33 | ) 34 | } 35 | .padding(.bottom, 30.0) 36 | Text("John Doe") 37 | .bold() 38 | .font(.title) 39 | 40 | Spacer() 41 | 42 | if let url = URL(string: "healthApp://userID12") { 43 | ShareLink(item: url) { 44 | ZStack { 45 | RoundedRectangle(cornerRadius: 18.0) 46 | .frame(width: 350.0, height: 50.0) 47 | .foregroundStyle(.red) 48 | HStack { 49 | Image(systemName: "square.and.arrow.up.fill") 50 | Text("Share") 51 | .bold() 52 | }.foregroundStyle(.white) 53 | } 54 | } 55 | } 56 | 57 | Button { 58 | self.isPresentingCamera = true 59 | } label: { 60 | ZStack { 61 | RoundedRectangle(cornerRadius: 18.0) 62 | .frame(width: 350.0, height: 50.0) 63 | .foregroundStyle(.blue) 64 | HStack { 65 | Image(systemName: "qrcode.viewfinder") 66 | Text("Scan") 67 | .bold() 68 | }.foregroundStyle(.white) 69 | } 70 | 71 | } 72 | 73 | } 74 | .frame(maxWidth: .infinity, maxHeight: .infinity) 75 | .background(Color(uiColor: .systemGroupedBackground)) 76 | .padding(.bottom) 77 | .sheet(isPresented: $isPresentingCamera) { 78 | ScannerView(scanType: .qr) 79 | } 80 | } 81 | } 82 | 83 | func generateQRCode(from string: String) -> UIImage { 84 | let filter = CIFilter.qrCodeGenerator() 85 | let data = Data(string.utf8) 86 | filter.setValue(data, forKey: "inputMessage") 87 | 88 | let context = CIContext() 89 | let transform = CGAffineTransform(scaleX: 10, y: 10) 90 | 91 | guard let qrImage = filter.outputImage?.transformed(by: transform), 92 | let qrCodeCGImage = context.createCGImage(qrImage, from: qrImage.extent)else { 93 | return UIImage() 94 | } 95 | let imageSize = qrImage.extent.size 96 | UIGraphicsBeginImageContext(imageSize) 97 | 98 | let graphicsContext = UIGraphicsGetCurrentContext() 99 | graphicsContext?.draw(qrCodeCGImage, in: qrImage.extent) 100 | 101 | let modifiedQRCodeImage = UIGraphicsGetImageFromCurrentImageContext() 102 | UIGraphicsEndImageContext() 103 | 104 | return modifiedQRCodeImage ?? UIImage() 105 | } 106 | 107 | 108 | struct QRView_Previews: PreviewProvider { 109 | static var previews: some View { 110 | QRView() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Shared/SwiftUI/LineChart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 29/12/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import Charts 11 | 12 | struct GenericHealthData: Identifiable { 13 | let id = UUID() 14 | let value: Double 15 | let date: Date 16 | } 17 | 18 | struct LineChart: View { 19 | var healthRecords: [GenericHealthData] 20 | 21 | static var dateFormatter: DateFormatter = { 22 | var df = DateFormatter() 23 | df.dateFormat = "dd/MM/yy" 24 | return df 25 | }() 26 | 27 | var body: some View { 28 | Chart(self.healthRecords) { healthData in 29 | LineMark(x: .value("Month", healthData.date), y: .value("Value", healthData.value)) 30 | 31 | .interpolationMethod(.cardinal) 32 | } 33 | .chartYScale(domain: [self.healthRecords.map { $0.value }.min() ?? 0.0, self.healthRecords.map { $0.value }.max() ?? 0.0]) 34 | .foregroundStyle(.red) 35 | } 36 | 37 | 38 | } 39 | 40 | 41 | struct LineChart_Previews: PreviewProvider { 42 | static var previews: some View { 43 | LineChart(healthRecords: []) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /HealthApp/ViewControllers/Shared/Utilities/FloatingAlertView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatingAlertView.swift 3 | // HealthApp 4 | // 5 | // Created by Cordova Garcia Moises Emmanuel on 31/12/23. 6 | // 7 | 8 | import UIKit 9 | 10 | enum AlertType { 11 | case defaultAlert 12 | case success 13 | case warning 14 | case error 15 | } 16 | 17 | class FloatingAlertView: UIView { 18 | private let imageView = UIImageView() 19 | private let textLabel = UILabel() 20 | private var dismissTimer: Timer? 21 | 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | setupView() 25 | addGestureRecognizers() 26 | } 27 | 28 | required init?(coder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | private func setupView() { 33 | self.layer.cornerRadius = self.frame.height / 2 34 | self.layer.shadowColor = UIColor.gray.cgColor 35 | self.layer.shadowOffset = CGSize(width: 0, height: 4) 36 | self.layer.shadowOpacity = 0.5 37 | self.layer.shadowRadius = 15 38 | self.clipsToBounds = false 39 | 40 | self.imageView.contentMode = .scaleAspectFill 41 | self.addSubview(self.imageView) 42 | 43 | self.textLabel.textAlignment = .center 44 | self.textLabel.textColor = .label 45 | self.addSubview(self.textLabel) 46 | 47 | self.imageView.translatesAutoresizingMaskIntoConstraints = false 48 | self.textLabel.translatesAutoresizingMaskIntoConstraints = false 49 | NSLayoutConstraint.activate([ 50 | self.imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12), 51 | self.imageView.centerYAnchor.constraint(equalTo: centerYAnchor), 52 | self.imageView.heightAnchor.constraint(equalToConstant: 24), 53 | self.imageView.widthAnchor.constraint(equalTo: self.imageView.heightAnchor), 54 | 55 | self.textLabel.leadingAnchor.constraint(equalTo: self.imageView.trailingAnchor, constant: 8), 56 | self.textLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), 57 | self.textLabel.centerYAnchor.constraint(equalTo: centerYAnchor) 58 | ]) 59 | } 60 | 61 | private func addGestureRecognizers() { 62 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture)) 63 | self.addGestureRecognizer(panGesture) 64 | self.dismissTimer = Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.dismissAlert), userInfo: nil, repeats: false) 65 | } 66 | 67 | @objc private func handlePanGesture(gesture: UIPanGestureRecognizer) { 68 | let translation = gesture.translation(in: self) 69 | self.transform = CGAffineTransform(translationX: 0, y: translation.y) 70 | guard gesture.state == .ended else { return } 71 | self.dismissAlert() 72 | } 73 | 74 | @objc private func dismissAlert() { 75 | self.dismissTimer?.invalidate() 76 | UIView.animate(withDuration: 0.3, animations: { 77 | self.alpha = 0 78 | }) { _ in 79 | self.removeFromSuperview() 80 | } 81 | } 82 | 83 | func configure(text: String, alertType: AlertType) { 84 | let color: UIColor = alertType == .error ? .white : .darkGray 85 | var image: UIImage? 86 | 87 | switch alertType { 88 | case .success: 89 | image = UIImage.sfSymbol("checkmark.circle.fill", color: color) 90 | case .warning: 91 | image = UIImage.sfSymbol("exclamationmark.triangle.fill", color: color) 92 | case .error: 93 | image = UIImage.sfSymbol("exclamationmark.octagon.fill", color: color) 94 | default: break 95 | } 96 | 97 | self.imageView.image = image 98 | self.textLabel.text = text 99 | self.textLabel.textColor = color 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README Files/Appointment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Appointment.png -------------------------------------------------------------------------------- /README Files/Appointments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Appointments.png -------------------------------------------------------------------------------- /README Files/Created Appointment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Created Appointment.png -------------------------------------------------------------------------------- /README Files/Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Dashboard.png -------------------------------------------------------------------------------- /README Files/Doctor Profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Doctor Profile.png -------------------------------------------------------------------------------- /README Files/Fitzpatrick Explanation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Fitzpatrick Explanation.png -------------------------------------------------------------------------------- /README Files/Fitzpatrick Scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Fitzpatrick Scan.png -------------------------------------------------------------------------------- /README Files/Fitzpatrick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Fitzpatrick.png -------------------------------------------------------------------------------- /README Files/History.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/History.png -------------------------------------------------------------------------------- /README Files/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Logo.png -------------------------------------------------------------------------------- /README Files/My Doctors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/My Doctors.png -------------------------------------------------------------------------------- /README Files/Nevus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Nevus.png -------------------------------------------------------------------------------- /README Files/Profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Profile.png -------------------------------------------------------------------------------- /README Files/QR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/QR.png -------------------------------------------------------------------------------- /README Files/Sleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ColeMacGrath/HealthApp/dca64a590778abc7be17fed9152bcfaf8eec849e/README Files/Sleep.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo 3 |

4 | 5 | ![In Progress](https://img.shields.io/badge/status-in%20progress-yellow.svg) 6 | 7 | # HealthApp: Next-Generation Health Monitoring 8 | 9 | > **HealthApp** brings cutting-edge health monitoring and collaboration with healthcare professionals directly to your fingertips. Our project is actively being developed to transform the management and sharing of health-related data. 10 | 11 | Utilizing technologies like HealthKit, CoreML, and ChatGPT, HealthApp offers a comprehensive suite of tools for personal health management and professional healthcare collaboration. 12 | 13 | ## Some screenshots 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
Dashboard
Dashboard
Profile
Profile
My Doctors
My Doctors
Doctor Profile
Doctor Profile
Appointment
Appointment
Appointments
Appointments
Created Appointment
Created Appointment
Fitzpatrick Explanation
Fitzpatrick Explanation
Fitzpatrick Scan
Fitzpatrick Scan
Fitzpatrick
Fitzpatrick
Nevus
Nevus
History
History
Sleep
Sleep
QR
QR
39 | 40 | ## Key Features ✨ 41 | 42 | - **HealthKit Integration:** Seamlessly retrieve health metrics from HealthKit to monitor your well-being. Perfect for keeping a close eye on vital statistics. 43 | - **Doctor Collaboration:** Add doctors, schedule appointments, and share health data securely. Using QR codes, sharing information is both safe and simple. 44 | - **Skin Issue Detection:** Utilize advanced CoreML models to identify skin issues, such as melanomas, through a simple camera snap. Early detection is key to effective treatment. 45 | - **Fitzpatrick Scale Detection:** Determine your skin type with ease using our specialized CoreML Model for accurate skincare recommendations. 46 | - **Comprehensive Dashboards:** Easily access your health metrics, including BPM, sleep hours, and more, presented in a user-friendly interface. 47 | - **Mocking Support:** With fully mocked interactions using Proxyman, ensure a robust testing environment that mimics real-world application use. 48 | 49 | ## Getting Started 🚀 50 | 51 | ### Prerequisites 52 | 53 | - **Xcode 15** or newer 54 | - An **iPhone** or **iPad** running **iOS 17** or newer 55 | 56 | ### Setup 57 | 58 | 1. **Clone the repository** to your local machine: 59 | ```bash 60 | git clone https://github.com/ColeMacGrath/HealthApp.git 61 | 62 | 2. **Navigate** to `HealthApp/Utilities/keys.plist` and configure your OpenAI key: 63 | 64 | ```plist 65 | openAIKey 66 | YourKeyValueHere 67 | ``` 68 | 69 | To obtain an OpenAI key, visit [OpenAI API](https://openai.com/api/). 70 | 71 | ### Installation 72 | 73 | 1. Open `HealthApp.xcodeproj` in Xcode. 74 | 2. Build the project for your target device. 75 | 3. Run the app on your device or an emulator to begin exploring its features. 76 | 77 | ## How to Use 📲 78 | 79 | - **Dashboard and Profile:** Get an overview of your health status and manage your personal profile. 80 | - **Doctors and Appointments:** Easily add doctors to your network and schedule appointments. 81 | - **Skin Health:** Use the nevus analyzer to detect potential skin issues early. 82 | - **Sleep and Activity Tracking:** Monitor and analyze your sleep patterns and daily activities for better health management. 83 | 84 | ## Contributing 85 | 86 | We welcome contributions from the community! If you'd like to contribute, please: 87 | 88 | 1. Fork the repository. 89 | 2. Create a new branch for your feature. 90 | 3. Add or improve features. 91 | 4. Submit a pull request. 92 | 93 | ## Mocking With Proxyman ![Coming Soon](https://img.shields.io/badge/coming-soon-red.svg) 94 | 95 | ### Test HealthApp with Simulated Data: 96 | 97 | 1. Install Proxyman on your Mac. 98 | 2. Configure Proxyman to route HealthApp's network traffic: 99 | - Use the local URL: `https://api.healthapp.local/` 100 | 101 | Detailed configuration for routes and endpoints can be found in `HealthApp/Models/Shared/RequestManager.swift`. 102 | 103 | | Script Name | Matching Rule | Type | Method | 104 | | ------------------ | ------------------------------------------------- | -------- | ------ | 105 | | Book Appointment | https://api.healthapp.local/bookAppointment | Wildcard | POST | 106 | | Delete Appointment | https:\/\/api\.healthapp\.local\/.*\/appointment | Regex | DELETE | 107 | | Appointment List | https://api.healthapp.local/doctors | Wildcard | GET | 108 | | Add Doctor | https:\/\/api\.healthapp\.local\/.*\/appointments | Regex | PATCH | 109 | | Delete Doctor | https://api.healthapp.local/doctors | Wildcard | DELETE | 110 | | Doctors List | https:\/\/api\.healthapp\.local\/.*\/doctors | Regex | GET | 111 | | Patients List | https:\/\/api\.healthapp\.local\/.*\/patients | Regex | GET | --------------------------------------------------------------------------------