├── .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 |
3 |
4 |
5 | 
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 |  Dashboard |
18 |  Profile |
19 |  My Doctors |
20 |  Doctor Profile |
21 |
22 |
23 |  Appointment |
24 |  Appointments |
25 |  Created Appointment |
26 |  Fitzpatrick Explanation |
27 |
28 |
29 |  Fitzpatrick Scan |
30 |  Fitzpatrick |
31 |  Nevus |
32 |  History |
33 |
34 |
35 |  Sleep |
36 |  QR |
37 |
38 |
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 
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 |
--------------------------------------------------------------------------------