diff --git a/Uplift.xcodeproj/project.pbxproj b/Uplift.xcodeproj/project.pbxproj index 786063f..a2d1082 100644 --- a/Uplift.xcodeproj/project.pbxproj +++ b/Uplift.xcodeproj/project.pbxproj @@ -28,6 +28,10 @@ 2E15F5062B39F81B00414BEC /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E15F5052B39F81B00414BEC /* MainView.swift */; }; 2E15F50C2B3A0B2100414BEC /* CapacityCircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E15F50B2B3A0B2100414BEC /* CapacityCircleView.swift */; }; 2E15F5122B3A3D0000414BEC /* TriangleShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E15F5112B3A3D0000414BEC /* TriangleShape.swift */; }; + 2E2748CB2BCD37810023882E /* GiveawayPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2748CA2BCD37810023882E /* GiveawayPopup.swift */; }; + 2E2748CD2BCD4C300023882E /* UserMutations.graphql in Resources */ = {isa = PBXBuildFile; fileRef = 2E2748CC2BCD4C300023882E /* UserMutations.graphql */; }; + 2E2748CF2BCD4D1F0023882E /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2748CE2BCD4D1F0023882E /* MainViewModel.swift */; }; + 2E2748D22BCD4EC00023882E /* UpliftAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 2E2748D12BCD4EC00023882E /* UpliftAPI */; }; 2E2932C62BA2B9B5008445CE /* UpliftAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 2E2932C52BA2B9B5008445CE /* UpliftAPI */; }; 2E3838412BB7536700AE15DC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 2E3838402BB7536700AE15DC /* PrivacyInfo.xcprivacy */; }; 2E39D8162B3B3AD600AD238B /* ScaleButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E39D8152B3B3AD600AD238B /* ScaleButtonStyle.swift */; }; @@ -43,6 +47,7 @@ 2E39D82F2B3BCBA400AD238B /* UpliftAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 2E39D82E2B3BCBA400AD238B /* UpliftAPI */; }; 2E39D8312B3BEE5900AD238B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2E39D8302B3BEE5900AD238B /* LaunchScreen.storyboard */; }; 2E3D6C202B1285D200B51BB2 /* UpliftEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E3D6C1F2B1285D200B51BB2 /* UpliftEnvironment.swift */; }; + 2E3DC2432BCD2F1D0063B369 /* GiveawayModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E3DC2422BCD2F1D0063B369 /* GiveawayModal.swift */; }; 2E45B2402B4E361100FB83B7 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 2E45B23F2B4E361100FB83B7 /* FirebaseCrashlytics */; }; 2E45B2422B4E5CE100FB83B7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2E45B2412B4E5CE100FB83B7 /* GoogleService-Info.plist */; }; 2E45B2452B4F632700FB83B7 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 2E45B2442B4F632700FB83B7 /* FirebaseAnalytics */; }; @@ -110,6 +115,9 @@ 2E15F5052B39F81B00414BEC /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 2E15F50B2B3A0B2100414BEC /* CapacityCircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapacityCircleView.swift; sourceTree = ""; }; 2E15F5112B3A3D0000414BEC /* TriangleShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriangleShape.swift; sourceTree = ""; }; + 2E2748CA2BCD37810023882E /* GiveawayPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiveawayPopup.swift; sourceTree = ""; }; + 2E2748CC2BCD4C300023882E /* UserMutations.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UserMutations.graphql; sourceTree = ""; }; + 2E2748CE2BCD4D1F0023882E /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 2E3838402BB7536700AE15DC /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 2E39D8152B3B3AD600AD238B /* ScaleButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleButtonStyle.swift; sourceTree = ""; }; 2E39D81B2B3B5F3700AD238B /* NavBackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBackButton.swift; sourceTree = ""; }; @@ -125,6 +133,7 @@ 2E3D6C1C2B12809900B51BB2 /* Keys.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Keys.xcconfig; sourceTree = ""; }; 2E3D6C1D2B12840C00B51BB2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2E3D6C1F2B1285D200B51BB2 /* UpliftEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpliftEnvironment.swift; sourceTree = ""; }; + 2E3DC2422BCD2F1D0063B369 /* GiveawayModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiveawayModal.swift; sourceTree = ""; }; 2E45B2412B4E5CE100FB83B7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 2E45B2462B4F643500FB83B7 /* AnalyticsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsManager.swift; sourceTree = ""; }; 2E5726C62B4A63AE00D3DB36 /* ShimmerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerModifier.swift; sourceTree = ""; }; @@ -173,6 +182,7 @@ 2E090ECB2B12FF5900BAE982 /* UpliftAPI in Frameworks */, 2E45B2452B4F632700FB83B7 /* FirebaseAnalytics in Frameworks */, 2E39D82F2B3BCBA400AD238B /* UpliftAPI in Frameworks */, + 2E2748D22BCD4EC00023882E /* UpliftAPI in Frameworks */, 63A7ABCD2B86B971008D58FB /* UpliftAPI in Frameworks */, 2E45B2402B4E361100FB83B7 /* FirebaseCrashlytics in Frameworks */, 636E3D412BBE1EB100B6EFFC /* UpliftAPI in Frameworks */, @@ -264,6 +274,7 @@ 2E39D8232B3B66FD00AD238B /* FitnessCenterViewModel.swift */, 2E6785C12B3A5CE000DD3ADA /* GymDetailViewModel.swift */, 2E1105BE2B13B0E100119F5B /* HomeViewModel.swift */, + 2E2748CE2BCD4D1F0023882E /* MainViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -275,6 +286,7 @@ 2EF1A2572B129EEB007A299F /* Network.swift */, 2E090EC42B12EF2600BAE982 /* Publishers.swift */, 2EE0C7CA2B12D6EE000D3BF6 /* GymQueries.graphql */, + 2E2748CC2BCD4C300023882E /* UserMutations.graphql */, ); path = Networking; sourceTree = ""; @@ -327,6 +339,7 @@ 2E39D81B2B3B5F3700AD238B /* NavBackButton.swift */, 2E39D8212B3B631200AD238B /* DividerLine.swift */, 2E39D8252B3B6C6500AD238B /* SlidingTabBarView.swift */, + 2E3DC2412BCD2EDF0063B369 /* Giveaway */, ); path = Supporting; sourceTree = ""; @@ -354,6 +367,15 @@ path = UpliftSecrets; sourceTree = ""; }; + 2E3DC2412BCD2EDF0063B369 /* Giveaway */ = { + isa = PBXGroup; + children = ( + 2E3DC2422BCD2F1D0063B369 /* GiveawayModal.swift */, + 2E2748CA2BCD37810023882E /* GiveawayPopup.swift */, + ); + path = Giveaway; + sourceTree = ""; + }; 2E5726C92B4A68BD00D3DB36 /* ci_scripts */ = { isa = PBXGroup; children = ( @@ -481,6 +503,7 @@ 2E2932C52BA2B9B5008445CE /* UpliftAPI */, 636E3D402BBE1EB100B6EFFC /* UpliftAPI */, 636E3D432BBE1F3800B6EFFC /* UpliftAPI */, + 2E2748D12BCD4EC00023882E /* UpliftAPI */, ); productName = Uplift; productReference = 2E8FE38C2B1278B700B3DC6A /* Uplift.app */; @@ -520,7 +543,7 @@ 2E6785BA2B3A48D700DD3ADA /* XCRemoteSwiftPackageReference "WrappingHStack" */, 2EC3EE642B3C000E00E927BF /* XCRemoteSwiftPackageReference "Kingfisher" */, 2E45B23E2B4E361100FB83B7 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, - 636E3D422BBE1F3800B6EFFC /* XCLocalSwiftPackageReference "UpliftAPI" */, + 8996FEE12BE1D2BB00F13C67 /* XCLocalSwiftPackageReference "UpliftAPI" */, ); productRefGroup = 2E8FE38D2B1278B700B3DC6A /* Products */; projectDirPath = ""; @@ -547,6 +570,7 @@ 2EE0C7CB2B12D6EE000D3BF6 /* GymQueries.graphql in Resources */, 2EB090B52B131CA80039EB3B /* Montserrat-Medium.ttf in Resources */, 2EB090B72B131CA80039EB3B /* Montserrat-Regular.ttf in Resources */, + 2E2748CD2BCD4C300023882E /* UserMutations.graphql in Resources */, 2EB090B62B131CA80039EB3B /* Montserrat-Bold.ttf in Resources */, 2EB090B82B131CA80039EB3B /* Montserrat-Light.ttf in Resources */, 2E8FE3992B1278B900B3DC6A /* Preview Assets.xcassets in Resources */, @@ -660,6 +684,7 @@ 2EB161C92BBC9C3100493B71 /* StretchyModifier.swift in Sources */, 2E39D82C2B3BB35000AD238B /* FacilityExpandedViewModel.swift in Sources */, 2E3D6C202B1285D200B51BB2 /* UpliftEnvironment.swift in Sources */, + 2E2748CF2BCD4D1F0023882E /* MainViewModel.swift in Sources */, 2E5726C72B4A63AE00D3DB36 /* ShimmerModifier.swift in Sources */, 2E15F5022B396EC500414BEC /* ShadowModifier.swift in Sources */, 2E15F4DA2B39102A00414BEC /* Capacity.swift in Sources */, @@ -675,11 +700,13 @@ 2E39D8262B3B6C6500AD238B /* SlidingTabBarView.swift in Sources */, 2E1105C42B13D1B000119F5B /* DummyData.swift in Sources */, 2E15F4E22B3917B200414BEC /* Status.swift in Sources */, + 2E3DC2432BCD2F1D0063B369 /* GiveawayModal.swift in Sources */, 2E15F5042B39E54700414BEC /* LocationManager.swift in Sources */, 2E39D81E2B3B610200AD238B /* UINavigationController+Extension.swift in Sources */, 2E15F4DC2B39117300414BEC /* OpenHours.swift in Sources */, 2E090ED62B13121600BAE982 /* Constants.swift in Sources */, 2E39D8282B3B726D00AD238B /* GymTabType.swift in Sources */, + 2E2748CB2BCD37810023882E /* GiveawayPopup.swift in Sources */, 2E15F4DE2B3912C700414BEC /* Facility.swift in Sources */, 2E6785C22B3A5CE000DD3ADA /* GymDetailViewModel.swift in Sources */, ); @@ -866,7 +893,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 27; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ZGMCXU7X3U; ENABLE_PREVIEWS = YES; @@ -887,7 +914,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.5; + MARKETING_VERSION = 2.5.7; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.cornellappdev.uplift.ios; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -910,7 +937,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 21; + CURRENT_PROJECT_VERSION = 27; DEVELOPMENT_TEAM = ZGMCXU7X3U; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -930,7 +957,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.5; + MARKETING_VERSION = 2.5.7; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.cornellappdev.uplift.ios; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -977,7 +1004,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 636E3D422BBE1F3800B6EFFC /* XCLocalSwiftPackageReference "UpliftAPI" */ = { + 8996FEE12BE1D2BB00F13C67 /* XCLocalSwiftPackageReference "UpliftAPI" */ = { isa = XCLocalSwiftPackageReference; relativePath = UpliftAPI; }; @@ -1027,6 +1054,10 @@ isa = XCSwiftPackageProductDependency; productName = UpliftAPI; }; + 2E2748D12BCD4EC00023882E /* UpliftAPI */ = { + isa = XCSwiftPackageProductDependency; + productName = UpliftAPI; + }; 2E2932C52BA2B9B5008445CE /* UpliftAPI */ = { isa = XCSwiftPackageProductDependency; productName = UpliftAPI; diff --git a/Uplift/Models/Gym.swift b/Uplift/Models/Gym.swift index 20da912..6dc811b 100644 --- a/Uplift/Models/Gym.swift +++ b/Uplift/Models/Gym.swift @@ -115,7 +115,7 @@ struct Gym: Hashable { - 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. + - Returns: A `Status` object based on its fitness centers' hours. `nil` if there are no hours in the future. */ func determineStatus(currentTime: Date = Date.now) -> Status? { if fitnessCenterIsOpen(currentTime: currentTime) { diff --git a/Uplift/Resources/Assets.xcassets/cross.imageset/Contents.json b/Uplift/Resources/Assets.xcassets/cross.imageset/Contents.json new file mode 100644 index 0000000..ba54b7a --- /dev/null +++ b/Uplift/Resources/Assets.xcassets/cross.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "cross.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "cross@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "cross@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Uplift/Resources/Assets.xcassets/cross.imageset/cross.png b/Uplift/Resources/Assets.xcassets/cross.imageset/cross.png new file mode 100644 index 0000000..9637d82 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/cross.imageset/cross.png differ diff --git a/Uplift/Resources/Assets.xcassets/cross.imageset/cross@2x.png b/Uplift/Resources/Assets.xcassets/cross.imageset/cross@2x.png new file mode 100644 index 0000000..b659c61 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/cross.imageset/cross@2x.png differ diff --git a/Uplift/Resources/Assets.xcassets/cross.imageset/cross@3x.png b/Uplift/Resources/Assets.xcassets/cross.imageset/cross@3x.png new file mode 100644 index 0000000..5e36021 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/cross.imageset/cross@3x.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/Contents.json b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/Contents.json new file mode 100644 index 0000000..94cf837 --- /dev/null +++ b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "giveaway_modal_bg.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "giveaway_modal_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "giveaway_modal_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg.png b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg.png new file mode 100644 index 0000000..457c929 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@2x.png b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@2x.png new file mode 100644 index 0000000..222635f Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@2x.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@3x.png b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@3x.png new file mode 100644 index 0000000..c0f9dbd Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_modal_bg.imageset/giveaway_modal_bg@3x.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/Contents.json b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/Contents.json new file mode 100644 index 0000000..e4074e8 --- /dev/null +++ b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "giveaway_popup_bg.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "giveaway_popup_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "giveaway_popup_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg.png b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg.png new file mode 100644 index 0000000..52590a0 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@2x.png b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@2x.png new file mode 100644 index 0000000..b8f81f8 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@2x.png differ diff --git a/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@3x.png b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@3x.png new file mode 100644 index 0000000..693f7fc Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/giveaway_popup_bg.imageset/giveaway_popup_bg@3x.png differ diff --git a/Uplift/Resources/Assets.xcassets/logo_white.imageset/Contents.json b/Uplift/Resources/Assets.xcassets/logo_white.imageset/Contents.json new file mode 100644 index 0000000..2a45082 --- /dev/null +++ b/Uplift/Resources/Assets.xcassets/logo_white.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "logo_white.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo_white@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo_white@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white.png b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white.png new file mode 100644 index 0000000..bb469a6 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white.png differ diff --git a/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@2x.png b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@2x.png new file mode 100644 index 0000000..51f8d37 Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@2x.png differ diff --git a/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@3x.png b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@3x.png new file mode 100644 index 0000000..9e05a2f Binary files /dev/null and b/Uplift/Resources/Assets.xcassets/logo_white.imageset/logo_white@3x.png differ diff --git a/Uplift/Services/Networking/GymQueries.graphql b/Uplift/Services/Networking/GymQueries.graphql index cd7b815..e38a8d9 100644 --- a/Uplift/Services/Networking/GymQueries.graphql +++ b/Uplift/Services/Networking/GymQueries.graphql @@ -55,7 +55,7 @@ fragment gymFields on Gym { } query GetAllGyms { - gyms { + gyms: getAllGyms { ...gymFields } } diff --git a/Uplift/Services/Networking/Publishers.swift b/Uplift/Services/Networking/Publishers.swift index b3bad79..e3b99f2 100644 --- a/Uplift/Services/Networking/Publishers.swift +++ b/Uplift/Services/Networking/Publishers.swift @@ -11,6 +11,11 @@ import ApolloAPI import Combine import Foundation +/// A structure that represents a custom error from GraphQL. +struct GraphQLErrorWrapper: Error { + let msg: String +} + extension Publishers { // MARK: - Queries @@ -86,9 +91,6 @@ extension Publishers { // MARK: - Mutations - // As of 11/25/23, Uplift does not contain any mutations (perform operations). - // However, this may be needed in the future. - Vin - /// A configuration for an a GraphQL Mutation used by Apollo. struct ApolloMutationConfiguration { let client: ApolloClientProtocol @@ -136,8 +138,13 @@ extension Publishers { ) { [weak self] result in switch result { case .success(let data): - _ = self?.subscriber?.receive(data) - self?.subscriber?.receive(completion: .finished) + if let graphQLError = data.errors?.first { + let error = GraphQLErrorWrapper(msg: graphQLError.description) + self?.subscriber?.receive(completion: .failure(error)) + } else { + _ = self?.subscriber?.receive(data) + self?.subscriber?.receive(completion: .finished) + } case .failure(let error): self?.subscriber?.receive(completion: .failure(error)) diff --git a/Uplift/Services/Networking/UserMutations.graphql b/Uplift/Services/Networking/UserMutations.graphql new file mode 100644 index 0000000..dae21f2 --- /dev/null +++ b/Uplift/Services/Networking/UserMutations.graphql @@ -0,0 +1,36 @@ +fragment userFields on User { + id + instagram + netId + giveaways { + id + name + } +} + + +fragment giveawayFields on Giveaway { + id + name + users { + ...userFields + } +} + +mutation CreateUser($instagram: String, $netId: String!) { + createUser(instagram: $instagram, netId: $netId) { + user { + ...userFields + } + } +} + +mutation EnterGiveaway($giveawayId: Int!, $userNetId: String!) { + enterGiveaway(giveawayId: $giveawayId, userNetId: $userNetId) { + giveawayInstance { + id + userId + numEntries + } + } +} diff --git a/Uplift/Utils/Constants.swift b/Uplift/Utils/Constants.swift index 65d21ca..6b713cf 100644 --- a/Uplift/Utils/Constants.swift +++ b/Uplift/Utils/Constants.swift @@ -35,6 +35,9 @@ struct Constants { static let gray03 = Color(red: 161/255, green: 165/255, blue: 166/255) static let gray04 = Color(red: 112/255, green: 112/255, blue: 112/255) static let gray05 = Color(red: 115/255, green: 131/255, blue: 144/255) + + // Others + static let giveawayBgColor = Color(red: 34/255, green: 36/255, blue: 42/255) } /// Facility names in Uplift. @@ -95,11 +98,15 @@ struct Constants { static let basketball = Image("basketball") static let bowling = Image("bowling") static let clock = Image("clock") + static let cross = Image("cross") static let dumbbellLarge = Image("dumbbell_large") static let dumbbellSmall = Image("dumbbell_small") static let elevator = Image("elevator") + static let giveawayModalBackground = Image("giveaway_modal_bg") + static let giveawayPopupBackground = Image("giveaway_popup_bg") static let lock = Image("lock") static let logo = Image("logo") + static let logoWhite = Image("logo_white") static let parking = Image("parking") static let pool = Image("pool") static let shower = Image("shower") diff --git a/Uplift/ViewModels/MainViewModel.swift b/Uplift/ViewModels/MainViewModel.swift new file mode 100644 index 0000000..658bb22 --- /dev/null +++ b/Uplift/ViewModels/MainViewModel.swift @@ -0,0 +1,104 @@ +// +// MainViewModel.swift +// Uplift +// +// Created by Vin Bui on 4/15/24. +// Copyright © 2024 Cornell AppDev. All rights reserved. +// + +import Combine +import OSLog +import SwiftUI +import UpliftAPI + +extension MainView { + + /// The ViewModel for the Main page view. + @MainActor + class ViewModel: ObservableObject { + + // MARK: - Properties + + @Published var instagram: String = "" + @Published var netID: String = "" + @Published var popUpGiveaway: Bool = false + @Published var didClickSubmit: Bool = false + @Published var showGiveawayErrorAlert: Bool = false + @Published var submitSuccessful: Bool = false + + private var queryBag = Set() + + // MARK: - Constants + + // TODO: Change hardcoded giveaway ID if needed + private let giveawayID: Int = 1 + + // MARK: - Requests + + /** + Enter a giveaway. + */ + func enterGiveaway() { + createUserRequest(enterGiveawayRequest) + } + + /** + Creates a user in the backend. + + - Parameters: + - callback: A callback function to be called once the request is done. + */ + private func createUserRequest(_ callback: @escaping () -> Void) { + // Make lowercase and remove whitespace + netID = netID.lowercased().replacingOccurrences(of: " ", with: "") + + Network.client.mutationPublisher( + mutation: CreateUserMutation( + instagram: GraphQLNullable(stringLiteral: instagram), + netId: netID + ) + ) + .compactMap(\.data?.createUser?.user?.netId) + .sink { completion in + if case let .failure(error) = completion { + callback() // If user already created (error thrown), still enter giveaway + Logger.data.critical("Error in MainViewModel.createUserRequest: \(error)") + } + } receiveValue: { netID in + callback() +#if DEBUG + Logger.data.log("Created a new user with NetID \(netID)") +#endif + } + .store(in: &queryBag) + } + + /// Enters a user to a giveaway in the backend. + private func enterGiveawayRequest() { + Network.client.mutationPublisher( + mutation: EnterGiveawayMutation(giveawayId: giveawayID, userNetId: netID) + ) + .sink { [weak self] completion in + guard let self else { return } + + if case let .failure(error) = completion { + Logger.data.critical("Error in MainViewModel.enterGiveawayRequest: \(error)") + submitSuccessful = false + showGiveawayErrorAlert = true + } + } receiveValue: { [weak self] _ in + guard let self else { return } + + withAnimation { + self.submitSuccessful = true + } +#if DEBUG + Logger.data.log("NetID \(netID) has entered giveaway ID \(giveawayID)") +#endif + } + .store(in: &queryBag) + } + + } + +} diff --git a/Uplift/Views/HomeView.swift b/Uplift/Views/HomeView.swift index 5621248..36a503d 100644 --- a/Uplift/Views/HomeView.swift +++ b/Uplift/Views/HomeView.swift @@ -14,6 +14,7 @@ struct HomeView: View { // MARK: - Properties @EnvironmentObject var locationManager: LocationManager + @Binding var popUpGiveaway: Bool @StateObject private var viewModel = ViewModel() // MARK: - UI @@ -131,6 +132,8 @@ struct HomeView: View { VStack(alignment: .leading, spacing: 12) { viewModel.showCapacities ? capacitiesView : nil + giveawayModalCell + HStack { Text("GYMS") .foregroundStyle(Constants.Colors.gray03) @@ -184,7 +187,8 @@ struct HomeView: View { Button { // Only toggle if gyms are loaded guard viewModel.gyms != nil else { return } - withAnimation(.easeOut) { + + withAnimation { viewModel.showCapacities.toggle() } AnalyticsManager.shared.log( @@ -208,6 +212,16 @@ struct HomeView: View { ) } + private var giveawayModalCell: some View { + Button { + withAnimation(.easeInOut(duration: 0.2)) { + popUpGiveaway = true + } + } label: { + GiveawayModal() + } + } + // MARK: - Supporting private func capacityCircleNavLink(fitnessCenterName: String) -> some View { @@ -235,5 +249,5 @@ struct HomeView: View { } #Preview { - HomeView() + HomeView(popUpGiveaway: .constant(false)) } diff --git a/Uplift/Views/MainView.swift b/Uplift/Views/MainView.swift index bc97bbf..f69dbab 100644 --- a/Uplift/Views/MainView.swift +++ b/Uplift/Views/MainView.swift @@ -14,12 +14,13 @@ struct MainView: View { // MARK: - Properties @State private var selectedTab: Screen = .home + @StateObject private var viewModel = ViewModel() // MARK: - UI var body: some View { ZStack { - HomeView() + HomeView(popUpGiveaway: $viewModel.popUpGiveaway) // TODO: Temporarily remove tab bar for release // ZStack(alignment: .bottom) { // TabView(selection: $selectedTab) { @@ -29,6 +30,34 @@ struct MainView: View { // // tabBar // } + + if viewModel.popUpGiveaway { + Constants.Colors.gray04 + .opacity(0.4) + .ignoresSafeArea(.all) + + GiveawayPopup( + didClickSubmit: $viewModel.didClickSubmit, + instagram: $viewModel.instagram, + netID: $viewModel.netID, + popUpGiveaway: $viewModel.popUpGiveaway, + submitSuccessful: $viewModel.submitSuccessful + ) + .padding(.horizontal, 20) + .transition(.scale(scale: 0.5, anchor: .bottom)) + .transition(.opacity) + .alert(isPresented: $viewModel.showGiveawayErrorAlert) { + Alert( + title: Text("Unable to enter giveaway"), + message: Text("Something went wrong.") + ) + } + } + } + .onChange(of: viewModel.didClickSubmit) { didClickSubmit in + if didClickSubmit { + viewModel.enterGiveaway() + } } } diff --git a/Uplift/Views/Supporting/Giveaway/GiveawayModal.swift b/Uplift/Views/Supporting/Giveaway/GiveawayModal.swift new file mode 100644 index 0000000..c972576 --- /dev/null +++ b/Uplift/Views/Supporting/Giveaway/GiveawayModal.swift @@ -0,0 +1,46 @@ +// +// GiveawayModal.swift +// Uplift +// +// Created by Vin Bui on 4/15/24. +// Copyright © 2024 Cornell AppDev. All rights reserved. +// + +import SwiftUI + +/// View representing the giveaway modal cell that is tapped on. +struct GiveawayModal: View { + + // MARK: - UI + + var body: some View { + HStack(spacing: 16) { + Constants.Images.logoWhite + .resizable() + .frame(width: 56, height: 56) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + VStack(alignment: .leading, spacing: 4) { + Text("Uplift Giveaway!") + .font(Constants.Fonts.h2) + + Text("Tell us who you are for a chance to win special prizes!!") + .font(Constants.Fonts.labelNormal) + } + .foregroundStyle(Constants.Colors.white) + .multilineTextAlignment(.leading) + } + .frame(maxWidth: .infinity) + .padding(24) + .background( + Constants.Images.giveawayModalBackground + ) + .clipShape(RoundedRectangle(cornerRadius: 14)) + .upliftShadow(Constants.Shadows.normalLight) + } + +} + +#Preview { + GiveawayModal() +} diff --git a/Uplift/Views/Supporting/Giveaway/GiveawayPopup.swift b/Uplift/Views/Supporting/Giveaway/GiveawayPopup.swift new file mode 100644 index 0000000..5df22e6 --- /dev/null +++ b/Uplift/Views/Supporting/Giveaway/GiveawayPopup.swift @@ -0,0 +1,174 @@ +// +// GiveawayPopup.swift +// Uplift +// +// Created by Vin Bui on 4/15/24. +// Copyright © 2024 Cornell AppDev. All rights reserved. +// + +import SwiftUI + +/// View representing the giveaway popup. +struct GiveawayPopup: View { + + // MARK: - Properties + + @Binding var didClickSubmit: Bool + @Binding var instagram: String + @Binding var netID: String + @Binding var popUpGiveaway: Bool + @Binding var submitSuccessful: Bool + + // MARK: - UI + + var body: some View { + ZStack(alignment: .topTrailing) { + VStack(spacing: 0) { + if submitSuccessful { + thanksMessage + } else { + header + bodySection + } + } + + closeButton + } + .clipShape(RoundedRectangle(cornerRadius: 20)) + .frame(height: 469) + .upliftShadow(Constants.Shadows.smallDark) + } + + private var header: some View { + ZStack(alignment: .center) { + Constants.Images.giveawayPopupBackground + .resizable() + .scaledToFill() + .frame(height: 165) + .clipped() + + Constants.Images.logoWhite + .resizable() + .frame(width: 82, height: 82) + .clipShape(RoundedRectangle(cornerRadius: 20)) + } + } + + private var bodySection: some View { + VStack(spacing: 40) { + VStack(spacing: 12) { + Text("Uplift Giveaway!") + .font(Constants.Fonts.h1) + + Text("Tell us who you are for a chance to win special prizes!!") + .font(Constants.Fonts.bodyNormal) + .multilineTextAlignment(.leading) + } + + VStack(spacing: 28) { + VStack(spacing: 8) { + netIDTextField + instagramTextField + } + + submitButton + } + } + .foregroundStyle(Constants.Colors.black) + .padding(EdgeInsets(top: 20, leading: 40, bottom: 20, trailing: 40)) + .background(Constants.Colors.white) + } + + private var netIDTextField: some View { + TextField( + "", + text: $netID, + prompt: Text("Cornell NetID") + .foregroundColor(Constants.Colors.gray02) + ) + .font(Constants.Fonts.bodyLight) + .padding(8) + .background(Constants.Colors.white) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Constants.Colors.gray02, lineWidth: 1) + ) + } + + private var instagramTextField: some View { + TextField( + "", + text: $instagram, + prompt: Text("Instagram username") + .foregroundColor(Constants.Colors.gray02) + ) + .font(Constants.Fonts.bodyLight) + .padding(8) + .background(Constants.Colors.white) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Constants.Colors.gray02, lineWidth: 1) + ) + } + + private var submitButton: some View { + Button { + if !netID.isEmpty { + didClickSubmit = true + } + } label: { + Text("SUBMIT") + .font(Constants.Fonts.h3) + .padding(EdgeInsets(top: 12, leading: 24, bottom: 12, trailing: 24)) + .background( + RoundedRectangle(cornerRadius: 40) + .fill(Constants.Colors.yellow) + ) + .opacity(netID.isEmpty ? 0.5 : 1) + } + } + + private var closeButton: some View { + Button { + withAnimation { + popUpGiveaway = false + } + } label: { + Constants.Images.cross + .resizable() + .frame(width: 12, height: 12) + .background( + Circle() + .fill(Constants.Colors.white) + .frame(width: 28, height: 28) + ) + } + .padding([.trailing, .top], 24) + } + + private var thanksMessage: some View { + ZStack(alignment: .center) { + Constants.Images.giveawayPopupBackground + .resizable() + .scaledToFill() + .clipped() + + VStack(spacing: 32) { + Constants.Images.logoWhite + .resizable() + .frame(width: 82, height: 82) + .clipShape(RoundedRectangle(cornerRadius: 20)) + + Text("Thanks for your response!") + .foregroundStyle(Constants.Colors.white) + .font(Constants.Fonts.h1) + .multilineTextAlignment(.center) + .background( + Constants.Colors.giveawayBgColor + ) + } + .padding(.horizontal, 40) + } + } + +}