diff --git a/Uplift.xcodeproj/project.pbxproj b/Uplift.xcodeproj/project.pbxproj index 4a34b95..c8f0480 100644 --- a/Uplift.xcodeproj/project.pbxproj +++ b/Uplift.xcodeproj/project.pbxproj @@ -511,9 +511,9 @@ 2EE5F3E02B12EDB6008E0299 /* XCRemoteSwiftPackageReference "apollo-ios" */, 2E6785BA2B3A48D700DD3ADA /* XCRemoteSwiftPackageReference "WrappingHStack" */, 2EC3EE642B3C000E00E927BF /* XCRemoteSwiftPackageReference "Kingfisher" */, - 2E4F06DA2B4B48DC008905C8 /* XCLocalSwiftPackageReference "UpliftAPI" */, 2E45B23E2B4E361100FB83B7 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 2EE5D6512B65E7DC004BB8F5 /* XCRemoteSwiftPackageReference "appdev-announcements" */, + 89ED9AE92B9D76EA00995941 /* XCLocalSwiftPackageReference "UpliftAPI" */, ); productRefGroup = 2E8FE38D2B1278B700B3DC6A /* Products */; projectDirPath = ""; @@ -991,7 +991,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 2E4F06DA2B4B48DC008905C8 /* XCLocalSwiftPackageReference "UpliftAPI" */ = { + 89ED9AE92B9D76EA00995941 /* XCLocalSwiftPackageReference "UpliftAPI" */ = { isa = XCLocalSwiftPackageReference; relativePath = UpliftAPI; }; diff --git a/Uplift.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Uplift.xcworkspace/xcshareddata/swiftpm/Package.resolved index 298d86b..433e9cb 100644 --- a/Uplift.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Uplift.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "4b9b3950fbe6da2fe6e272e05e7838a9e224fb88becd3ddc341ab9714f0b8c6d", "pins" : [ { "identity" : "abseil-cpp-binary", @@ -163,5 +164,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/Uplift/Models/Gym.swift b/Uplift/Models/Gym.swift index 7a7f279..20da912 100644 --- a/Uplift/Models/Gym.swift +++ b/Uplift/Models/Gym.swift @@ -90,4 +90,66 @@ struct Gym: Hashable { facilities.filter { $0.facilityType != .fitness } } + /** + Determine whether at least one fitness center is open at this `Gym` depending on its fitness centers' hours. + + - Parameters: + - currentTime: The current time to compare with and determine the status. Default is now. + + - Returns: A `Bool` representing whether at least one of its fitness centers is open. + */ + func fitnessCenterIsOpen(currentTime: Date = Date.now) -> Bool { + fitnessCenters.contains { + switch $0.hours.getStatus(currentTime: currentTime) { + case .open: + return true + default: + return false + } + } + } + + /** + Retrieve the status of the `Gym` depending on the fitness centers' hours. + + - Parameters: + - currentTime: The current time to compare with and determine the status. Default is now. + + - Returns: A `Status` object based on its fitness centers' hours. `nil` if there are no open or close hours in the future. + */ + func determineStatus(currentTime: Date = Date.now) -> Status? { + if fitnessCenterIsOpen(currentTime: currentTime) { + // Get all possible close times + let closeTimes = fitnessCenters.compactMap { + switch $0.hours.getStatus(currentTime: currentTime) { + case .open(let closeTime): + return closeTime + default: + return nil + } + } + + // Get the latest closing time + if let closeTime = closeTimes.max() { + return Status.open(closeTime: closeTime) + } + } else { + // Get all possible open times + let openTimes = fitnessCenters.compactMap { + switch $0.hours.getStatus(currentTime: currentTime) { + case .closed(let openTime): + return openTime + default: + return nil + } + } + + // Get the earliest open time + if let openTime = openTimes.min() { + return Status.closed(openTime: openTime) + } + } + return nil + } + } diff --git a/Uplift/Utils/DummyData.swift b/Uplift/Utils/DummyData.swift index 7ab977c..16b66f3 100644 --- a/Uplift/Utils/DummyData.swift +++ b/Uplift/Utils/DummyData.swift @@ -100,31 +100,37 @@ struct DummyData { [ "__typename": "OpenHours", "endTime": 1703466000, + "isSpecial": false, "startTime": 1703430000 ], [ "__typename": "OpenHours", "endTime": 1703556000, + "isSpecial": false, "startTime": 1703502000 ], [ "__typename": "OpenHours", "endTime": 1703642400, + "isSpecial": false, "startTime": 1703588400 ], [ "__typename": "OpenHours", "endTime": 1703728800, + "isSpecial": false, "startTime": 1703674800 ], [ "__typename": "OpenHours", "endTime": 1703815200, + "isSpecial": false, "startTime": 1703761200 ], [ "__typename": "OpenHours", "endTime": 1703901600, + "isSpecial": false, "startTime": 1703847600 ] ], @@ -138,78 +144,93 @@ struct DummyData { [ "__typename": "OpenHours", "endTime": 1703507400, + "isSpecial": false, "startTime": 1703502000 ], [ "__typename": "OpenHours", "endTime": 1703512500, + "isSpecial": false, "startTime": 1703508300 ], [ "__typename": "OpenHours", "endTime": 1703522700, + "isSpecial": false, "isWomen": true, "startTime": 1703520000 ], [ "__typename": "OpenHours", "endTime": 1703530800, + "isSpecial": false, "startTime": 1703523600 ], [ "__typename": "OpenHours", "endTime": 1703595300, + "isSpecial": false, "startTime": 1703588400 ], [ "__typename": "OpenHours", "endTime": 1703608200, + "isSpecial": false, "startTime": 1703602800 ], [ "__typename": "OpenHours", "endTime": 1703617200, + "isSpecial": false, "startTime": 1703611800 ], [ "__typename": "OpenHours", "endTime": 1703680200, + "isSpecial": false, "startTime": 1703674800 ], [ "__typename": "OpenHours", "endTime": 1703685300, + "isSpecial": false, "startTime": 1703681100 ], [ "__typename": "OpenHours", "endTime": 1703695500, + "isSpecial": false, "isWomen": true, "startTime": 1703692800 ], [ "__typename": "OpenHours", "endTime": 1703703600, + "isSpecial": false, "startTime": 1703696400 ], [ "__typename": "OpenHours", "endTime": 1703768100, + "isSpecial": false, "startTime": 1703761200 ], [ "__typename": "OpenHours", "endTime": 1703854800, + "isSpecial": false, "startTime": 1703847600 ], [ "__typename": "OpenHours", "endTime": 1703867400, + "isSpecial": false, "startTime": 1703856600 ], [ "__typename": "OpenHours", "endTime": 1703872800, + "isSpecial": false, "startTime": 1703869200 ] ], @@ -237,66 +258,77 @@ struct DummyData { "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703475000, + "isSpecial": false, "startTime": 1703439000 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703514600, + "isSpecial": false, "startTime": 1703502000 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703561400, + "isSpecial": false, "startTime": 1703521800 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703598300, + "isSpecial": false, "startTime": 1703588400 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703647800, + "isSpecial": false, "startTime": 1703633400 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703687400, + "isSpecial": false, "startTime": 1703674800 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703734200, + "isSpecial": false, "startTime": 1703694600 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703771100, + "isSpecial": false, "startTime": 1703761200 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703820600, + "isSpecial": false, "startTime": 1703792700 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703860200, + "isSpecial": false, "startTime": 1703847600 ], [ "__typename": "OpenHours", "courtType": "BASKETBALL", "endTime": 1703903400, + "isSpecial": false, "startTime": 1703876400 ] ], @@ -317,12 +349,14 @@ struct DummyData { "__typename": "OpenHours", "courtType": "VOLLEYBALL", "endTime": 1703475000, + "isSpecial": false, "startTime": 1703439000 ], [ "__typename": "OpenHours", "courtType": "VOLLEYBALL", "endTime": 1703514600, + "isSpecial": false, "startTime": 1703502000 ], [ @@ -335,48 +369,56 @@ struct DummyData { "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703598300, + "isSpecial": false, "startTime": 1703588400 ], [ "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703647800, + "isSpecial": false, "startTime": 1703633400 ], [ "__typename": "OpenHours", "courtType": "VOLLEYBALL", "endTime": 1703687400, + "isSpecial": false, "startTime": 1703674800 ], [ "__typename": "OpenHours", "courtType": "VOLLEYBALL", "endTime": 1703734200, + "isSpecial": false, "startTime": 1703694600 ], [ "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703771100, + "isSpecial": false, "startTime": 1703761200 ], [ "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703820600, + "isSpecial": false, "startTime": 1703792700 ], [ "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703860200, + "isSpecial": false, "startTime": 1703847600 ], [ "__typename": "OpenHours", "courtType": "BADMINTON", "endTime": 1703903400, + "isSpecial": false, "startTime": 1703876400 ] ], @@ -387,31 +429,37 @@ struct DummyData { [ "__typename": "OpenHours", "endTime": 1703476800, + "isSpecial": false, "startTime": 1703430000 ], [ "__typename": "OpenHours", "endTime": 1703563200, + "isSpecial": false, "startTime": 1703502000 ], [ "__typename": "OpenHours", "endTime": 1703649600, + "isSpecial": false, "startTime": 1703588400 ], [ "__typename": "OpenHours", "endTime": 1703736000, + "isSpecial": false, "startTime": 1703674800 ], [ "__typename": "OpenHours", "endTime": 1703822400, + "isSpecial": false, "startTime": 1703761200 ], [ "__typename": "OpenHours", "endTime": 1703905200, + "isSpecial": false, "startTime": 1703847600 ] ], @@ -421,26 +469,243 @@ struct DummyData { "name": "Helen Newman" ] + /// Dummy data for Teagle. + let teagle: [String: Any] = [ + "__typename": "Gym", + "id": "2", + "address": "512 Campus Rd", + "amenities": [ + [ + "__typename": "Amenity", + "type": "SHOWERS" + ], + [ + "__typename": "Amenity", + "type": "LOCKERS" + ], + [ + "__typename": "Amenity", + "type": "PARKING" + ] + ], + "facilities": [ + [ + "__typename": "Facility", + "id": "1", + "capacity": [ + "__typename": "Capacity", + "count": 0, + "percent": 0.0, + "updated": 1709155500 + ], + "facilityType": "FITNESS", + "hours": [ + [ + "__typename": "OpenHours", + "endTime": 1709418600, + "isSpecial": false, + "startTime": 1709398800 + ], + [ + "__typename": "OpenHours", + "endTime": 1709505000, + "isSpecial": false, + "startTime": 1709485200 + ], + [ + "__typename": "OpenHours", + "endTime": 1709610300, + "isSpecial": false, + "startTime": 1709553600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709696700, + "isSpecial": false, + "startTime": 1709640000 + ], + [ + "__typename": "OpenHours", + "endTime": 1709178300, + "isSpecial": false, + "startTime": 1709121600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709264700, + "isSpecial": false, + "startTime": 1709208000 + ], + [ + "__typename": "OpenHours", + "endTime": 1709351100, + "isSpecial": false, + "startTime": 1709294400 + ] + ], + "name": "Teagle Up Fitness Center" + ], + [ + "__typename": "Facility", + "id": "2", + "capacity": [ + "__typename": "Capacity", + "count": 0, + "percent": 0.0, + "updated": 1709155320 + ], + "facilityType": "FITNESS", + "hours": [ + [ + "__typename": "OpenHours", + "endTime": 1709418600, + "isSpecial": false, + "startTime": 1709398800 + ], + [ + "__typename": "OpenHours", + "endTime": 1709505000, + "isSpecial": false, + "startTime": 1709485200 + ], + [ + "__typename": "OpenHours", + "endTime": 1709559000, + "isSpecial": false, + "startTime": 1709553600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709610300, + "isSpecial": false, + "startTime": 1709564400 + ], + [ + "__typename": "OpenHours", + "endTime": 1709645400, + "isSpecial": false, + "startTime": 1709640000 + ], + [ + "__typename": "OpenHours", + "endTime": 1709696700, + "isSpecial": false, + "startTime": 1709650800 + ], + [ + "__typename": "OpenHours", + "endTime": 1709127000, + "isSpecial": false, + "startTime": 1709121600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709178300, + "isSpecial": false, + "startTime": 1709132400 + ], + [ + "__typename": "OpenHours", + "endTime": 1709213400, + "isSpecial": false, + "startTime": 1709208000 + ], + [ + "__typename": "OpenHours", + "endTime": 1709264700, + "isSpecial": false, + "startTime": 1709218800 + ], + [ + "__typename": "OpenHours", + "endTime": 1709299800, + "isSpecial": false, + "startTime": 1709294400 + ], + [ + "__typename": "OpenHours", + "endTime": 1709351100, + "isSpecial": false, + "startTime": 1709305200 + ] + ], + "name": "Teagle Down Fitness Center" + ] + ], + "hours": [ + [ + "__typename": "OpenHours", + "endTime": 1709179200, + "isSpecial": false, + "startTime": 1709121600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709265600, + "isSpecial": false, + "startTime": 1709208000 + ], + [ + "__typename": "OpenHours", + "endTime": 1709352000, + "isSpecial": false, + "startTime": 1709294400 + ], + [ + "__typename": "OpenHours", + "endTime": 1709420400, + "isSpecial": false, + "startTime": 1709391600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709506800, + "isSpecial": false, + "startTime": 1709485200 + ], + [ + "__typename": "OpenHours", + "endTime": 1709611200, + "isSpecial": false, + "startTime": 1709553600 + ], + [ + "__typename": "OpenHours", + "endTime": 1709697600, + "isSpecial": false, + "startTime": 1709640000 + ] + ], + "imageUrl": "https://raw.githubusercontent.com/cuappdev/assets/master/uplift/gyms/teagle.jpg", + "latitude": 42.4459926380709, + "longitude": -76.47915389837931, + "name": "Teagle" + ] + /// Dummy data for open hours. let openHours: [[String: Any]] = [ [ "__typename": "OpenHours", "endTime": 1703509200, // Mon Dec 25 2023 13:00 UTC + "isSpecial": false, "startTime": 1703502000 // Mon Dec 25 2023 11:00 UTC ], [ "__typename": "OpenHours", "endTime": 1703595600, // Tue Dec 26 2023 13:00 UTC + "isSpecial": false, "startTime": 1703588400 // Tue Dec 26 2023 11:00 UTC ], [ "__typename": "OpenHours", "endTime": 1703610000, // Tue Dec 26 2023 17:00 UTC + "isSpecial": false, "startTime": 1703602800 // Tue Dec 26 2023 15:00 UTC ], [ "__typename": "OpenHours", "endTime": 1703617200, // Tue Dec 26 2023 19:00 UTC + "isSpecial": false, "startTime": 1703613600 // Tue Dec 26 2023 18:00 UTC ] ] diff --git a/Uplift/Utils/Extensions/Array+Extension.swift b/Uplift/Utils/Extensions/Array+Extension.swift index c8ede25..98793ea 100644 --- a/Uplift/Utils/Extensions/Array+Extension.swift +++ b/Uplift/Utils/Extensions/Array+Extension.swift @@ -71,7 +71,7 @@ extension Array where Element == OpenHours { Retrieve the status of the `Gym` or `Facility` depending on its hours. - Parameters: - - currentTime: The current time to compare determine the status. Default is now. + - currentTime: The current time to compare with and determine the status. Default is now. - Returns: A `Status` object based on its hours. */ diff --git a/Uplift/Views/Home/GymDetailView.swift b/Uplift/Views/Home/GymDetailView.swift index 4c71871..7f40b00 100644 --- a/Uplift/Views/Home/GymDetailView.swift +++ b/Uplift/Views/Home/GymDetailView.swift @@ -118,20 +118,35 @@ struct GymDetailView: View { VStack(spacing: 4) { Spacer() - switch gym.status { - case .closed: - Text("CLOSED") - .font(Constants.Fonts.h3) - .foregroundStyle(Constants.Colors.closed) - case .open: + if gym.fitnessCenterIsOpen() { Text("OPEN") .font(Constants.Fonts.h3) .foregroundStyle(Constants.Colors.open) - case .none: - EmptyView() + // Temporary padding to center status text while `viewHoursButton` is removed + .padding(12) + } else { + Text("CLOSED") + .font(Constants.Fonts.h3) + .foregroundStyle(Constants.Colors.closed) + // Temporary padding to center status text while `viewHoursButton` is removed + .padding(12) } - viewHoursButton + // TODO: Removed building hours. Determine what should be displayed here. +// switch gym.status { +// case .closed: +// Text("CLOSED") +// .font(Constants.Fonts.h3) +// .foregroundStyle(Constants.Colors.closed) +// case .open: +// Text("OPEN") +// .font(Constants.Fonts.h3) +// .foregroundStyle(Constants.Colors.open) +// case .none: +// EmptyView() +// } +// +// viewHoursButton } .frame(height: 120) } diff --git a/Uplift/Views/Home/HomeGymCell.swift b/Uplift/Views/Home/HomeGymCell.swift index 4c80256..bfc10d5 100644 --- a/Uplift/Views/Home/HomeGymCell.swift +++ b/Uplift/Views/Home/HomeGymCell.swift @@ -63,20 +63,13 @@ struct HomeGymCell: View { gymNameText statusText - if gym.fitnessCenters.allSatisfy({ - switch $0.status { - case .closed: - return true - default: - return false - } - }) { + if gym.fitnessCenterIsOpen() { + capacityText + } else { // All fitness centers are closed Text("Fitness Centers Closed") .font(Constants.Fonts.labelMedium) .foregroundStyle(Constants.Colors.gray03) - } else { - capacityText } } @@ -112,7 +105,7 @@ struct HomeGymCell: View { private var statusText: some View { HStack(spacing: 8) { - switch gym.status { + switch gym.determineStatus() { case .closed(let openTime): Text("Closed") .foregroundStyle(Constants.Colors.closed) diff --git a/UpliftTests/UpliftTests.swift b/UpliftTests/UpliftTests.swift index 4395446..87dae8d 100644 --- a/UpliftTests/UpliftTests.swift +++ b/UpliftTests/UpliftTests.swift @@ -71,4 +71,61 @@ final class UpliftTests: XCTestCase { XCTAssertEqual(expected, result) } + /// Test procedure for computing whether at least one fitness center is open at a `Gym`. + func testFitnessCenterIsOpen() { + let gym = DummyData.uplift.getGym(data: DummyData.uplift.teagle)! + + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/yyyy h:mm a" + formatter.timeZone = TimeZone(abbreviation: "UTC") + + // Both fitness centers are open + var current = formatter.date(from: "3/5/2024 12:00 PM")! + var result = gym.fitnessCenterIsOpen(currentTime: current) + XCTAssertTrue(result) + + // One fitness center is open + current = formatter.date(from: "3/5/2024 2:00 PM")! + result = gym.fitnessCenterIsOpen(currentTime: current) + XCTAssertTrue(result) + + // Neither fitness centers are open + current = formatter.date(from: "3/5/2024 4:00 AM")! + result = gym.fitnessCenterIsOpen(currentTime: current) + XCTAssertFalse(result) + } + + /// Test procedure for computing the status of the `Gym` based its fitness centers' hours. + func testDetermineStatus() { + let gym = DummyData.uplift.getGym(data: DummyData.uplift.teagle)! + + let formatter = DateFormatter() + formatter.dateFormat = "MM/dd/yyyy h:mm a" + formatter.timeZone = TimeZone(abbreviation: "UTC") + + // Both fitness centers are open, close times are the same + var current = formatter.date(from: "3/3/2024 7:00 PM")! + var expected = Status.open(closeTime: formatter.date(from: "3/3/2024 10:30 PM")!) + var result = gym.determineStatus(currentTime: current) + XCTAssertEqual(expected, result) + + // Both fitness centers are open, close times are different + current = formatter.date(from: "3/5/2024 12:00 PM")! + expected = Status.open(closeTime: formatter.date(from: "3/6/2024 3:45 AM")!) + result = gym.determineStatus(currentTime: current) + XCTAssertEqual(expected, result) + + // One fitness center is open, close time is of current fitness center open + current = formatter.date(from: "3/5/2024 2:00 PM")! + expected = Status.open(closeTime: formatter.date(from: "3/6/2024 3:45 AM")!) + result = gym.determineStatus(currentTime: current) + XCTAssertEqual(expected, result) + + // Neither fitness center is open + current = formatter.date(from: "3/5/2024 4:00 AM")! + expected = Status.closed(openTime: formatter.date(from: "3/5/2024 12:00 PM")!) + result = gym.determineStatus(currentTime: current) + XCTAssertEqual(expected, result) + } + }