diff --git a/Podfile.lock b/Podfile.lock index 59aabfdb..18b8d8a8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -239,4 +239,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: af336d88f53594af448d02dc18637c2b6ebe685e -COCOAPODS: 1.15.0 +COCOAPODS: 1.16.2 diff --git a/TCAT.xcodeproj/project.pbxproj b/TCAT.xcodeproj/project.pbxproj index 93ae1a61..38d64079 100644 --- a/TCAT.xcodeproj/project.pbxproj +++ b/TCAT.xcodeproj/project.pbxproj @@ -7,6 +7,21 @@ objects = { /* Begin PBXBuildFile section */ + 1C0296AA2D77D9F5005B92FC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296A92D77D9F5005B92FC /* SettingsViewController.swift */; }; + 1C0296B62D77E578005B92FC /* SettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */; }; + 1C0296D82D7FE055005B92FC /* SettingsAppIconViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296D72D7FE055005B92FC /* SettingsAppIconViewController.swift */; }; + 1C0296DA2D823F8D005B92FC /* SettingsPrivacyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */; }; + 1C0296E12D8240A7005B92FC /* SettingsFaveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296E02D8240A7005B92FC /* SettingsFaveViewController.swift */; }; + 1C0296E52D87A2EA005B92FC /* SettingsAboutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */; }; + 1C0296E72D87A37D005B92FC /* SettingsAboutMembersCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */; }; + 1C0296E92D87A3BA005B92FC /* SettingsAboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296E82D87A3B4005B92FC /* SettingsAboutViewController.swift */; }; + 1C0296ED2D87A696005B92FC /* SettingsPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */; }; + 1C0296F02D8B5CFC005B92FC /* ContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296EF2D8B5CF4005B92FC /* ContainerView.swift */; }; + 1C0296F22D8B604B005B92FC /* PillButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296F12D8B603F005B92FC /* PillButtonView.swift */; }; + 1C0296F42D8B60A3005B92FC /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296F32D8B609F005B92FC /* ButtonView.swift */; }; + 1C591B4B2D8D226300DDA71D /* SettingsSupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */; }; + 1C591B4F2D8D22BD00DDA71D /* SettingsSupportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C591B4E2D8D22BD00DDA71D /* SettingsSupportViewController.swift */; }; + 1C591B5A2D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */; }; 22948BFD221B75C5003FC43F /* RequestModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22948BFB221B75C5003FC43F /* RequestModels.swift */; }; 28EA3E17A0C473892F5506EC /* Pods_TCAT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 542B073726DFD1EE044EA97F /* Pods_TCAT.framework */; }; 2E70434E2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 2E70434D2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy */; }; @@ -146,6 +161,21 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1C0296A92D77D9F5005B92FC /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewCell.swift; sourceTree = ""; }; + 1C0296D72D7FE055005B92FC /* SettingsAppIconViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppIconViewController.swift; sourceTree = ""; }; + 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyViewController.swift; sourceTree = ""; }; + 1C0296E02D8240A7005B92FC /* SettingsFaveViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFaveViewController.swift; sourceTree = ""; }; + 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutHeaderView.swift; sourceTree = ""; }; + 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutMembersCarouselView.swift; sourceTree = ""; }; + 1C0296E82D87A3B4005B92FC /* SettingsAboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAboutViewController.swift; sourceTree = ""; }; + 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyView.swift; sourceTree = ""; }; + 1C0296EF2D8B5CF4005B92FC /* ContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerView.swift; sourceTree = ""; }; + 1C0296F12D8B603F005B92FC /* PillButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonView.swift; sourceTree = ""; }; + 1C0296F32D8B609F005B92FC /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = ""; }; + 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSupportView.swift; sourceTree = ""; }; + 1C591B4E2D8D22BD00DDA71D /* SettingsSupportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSupportViewController.swift; sourceTree = ""; }; + 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppIconCollectionViewCell.swift; sourceTree = ""; }; 22948BFB221B75C5003FC43F /* RequestModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestModels.swift; sourceTree = ""; }; 2E70434D2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 2E94165F2BC60A59003DEB44 /* UpliftQueries.graphql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UpliftQueries.graphql; sourceTree = ""; }; @@ -357,6 +387,8 @@ 2E9416762BC61679003DEB44 /* RouteTableViewCell.swift */, 2E94166D2BC61678003DEB44 /* ServiceAlertTableViewCell.swift */, 2E9416722BC61679003DEB44 /* SmallDetailTableViewCell.swift */, + 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */, + 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -383,6 +415,12 @@ 2E94168F2BC616B9003DEB44 /* ServiceAlertsViewController.swift */, 2E94168B2BC616B9003DEB44 /* SearchResultsViewController.swift */, 2E9416882BC616B9003DEB44 /* StopPickerViewController.swift */, + 1C0296A92D77D9F5005B92FC /* SettingsViewController.swift */, + 1C0296E82D87A3B4005B92FC /* SettingsAboutViewController.swift */, + 1C0296D72D7FE055005B92FC /* SettingsAppIconViewController.swift */, + 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */, + 1C591B4E2D8D22BD00DDA71D /* SettingsSupportViewController.swift */, + 1C0296E02D8240A7005B92FC /* SettingsFaveViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -477,6 +515,13 @@ 2E9417022BC61CF1003DEB44 /* SearchBarView.swift */, 2E9417062BC61CF1003DEB44 /* SummaryView.swift */, 2E94170C2BC61CF1003DEB44 /* WalkWithDistanceIcon.swift */, + 1C0296F32D8B609F005B92FC /* ButtonView.swift */, + 1C0296F12D8B603F005B92FC /* PillButtonView.swift */, + 1C0296EF2D8B5CF4005B92FC /* ContainerView.swift */, + 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */, + 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */, + 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, + 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */, ); path = Views; sourceTree = ""; @@ -592,13 +637,13 @@ 2E70434D2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy */, 2E9416662BC615B0003DEB44 /* Base */, 2E94166C2BC61604003DEB44 /* Cells */, + 2E9416FD2BC61CAE003DEB44 /* Views */, 2E9416822BC6168C003DEB44 /* Controllers */, 2E94165E2BC60A3B003DEB44 /* Ecosystem */, 2E9416AB2BC616DE003DEB44 /* Models */, FDE68D292C988CDB00024A69 /* Services */, 2E9416C72BC61763003DEB44 /* Supporting */, 2E9416E02BC618E6003DEB44 /* Utils */, - 2E9416FD2BC61CAE003DEB44 /* Views */, ); path = TCAT; sourceTree = ""; @@ -842,7 +887,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1C0296AA2D77D9F5005B92FC /* SettingsViewController.swift in Sources */, 2E9416982BC616B9003DEB44 /* RouteDetail+DrawerViewController.swift in Sources */, + 1C0296F42D8B60A3005B92FC /* ButtonView.swift in Sources */, + 1C0296DA2D823F8D005B92FC /* SettingsPrivacyViewController.swift in Sources */, 2E94169D2BC616B9003DEB44 /* FavoritesTableViewController.swift in Sources */, 2E9416692BC615DF003DEB44 /* AppDelegate.swift in Sources */, 2E9FFA802BC673240051793C /* CapacityFields.graphql.swift in Sources */, @@ -855,6 +903,7 @@ FDE68D1E2C97E24900024A69 /* NetworkManager.swift in Sources */, 2E9417162BC61CF1003DEB44 /* SearchBarView.swift in Sources */, 2E9FFA882BC673240051793C /* Amenity.graphql.swift in Sources */, + 1C0296B62D77E578005B92FC /* SettingsTableViewCell.swift in Sources */, 2E9FFA852BC673240051793C /* AmenityType.graphql.swift in Sources */, 2E9416F02BC61984003DEB44 /* Extensions+Shared.swift in Sources */, 2E9FFA8F2BC673240051793C /* SchemaMetadata.graphql.swift in Sources */, @@ -866,6 +915,7 @@ 2E94167F2BC61679003DEB44 /* PlaceTableViewCell.swift in Sources */, 2E9417192BC61CF1003DEB44 /* PhraseLabelFooterView.swift in Sources */, 2E9416FC2BC61984003DEB44 /* Phrases.swift in Sources */, + 1C0296E92D87A3BA005B92FC /* SettingsAboutViewController.swift in Sources */, 2E9417132BC61CF1003DEB44 /* InformationTableHeaderView.swift in Sources */, 2E9416A42BC616B9003DEB44 /* FavoritesViewController.swift in Sources */, 2E9416A82BC616B9003DEB44 /* RouteDetailDrawerViewController+Extensions.swift in Sources */, @@ -873,6 +923,7 @@ 2E9416A12BC616B9003DEB44 /* RouteOptionsViewController.swift in Sources */, 2E9416BF2BC61731003DEB44 /* Direction.swift in Sources */, 2EC1F5122BC66972001D9F66 /* ApolloClientProtocol.swift in Sources */, + 1C591B4B2D8D226300DDA71D /* SettingsSupportView.swift in Sources */, 2E9FFA902BC673240051793C /* UpliftAPI.graphql.swift in Sources */, 2E9416BD2BC61731003DEB44 /* LocationObject.swift in Sources */, 2E9416802BC61679003DEB44 /* RouteTableViewCell.swift in Sources */, @@ -884,24 +935,30 @@ 2E9417142BC61CF1003DEB44 /* RouteLine.swift in Sources */, 2E9FFA8C2BC673240051793C /* OpenHours.graphql.swift in Sources */, 2E9417182BC61CF1003DEB44 /* RouteDiagramSegment.swift in Sources */, + 1C0296F22D8B604B005B92FC /* PillButtonView.swift in Sources */, 2E9416C32BC61731003DEB44 /* SearchManager.swift in Sources */, 2E9417212BC61CF1003DEB44 /* NotificationBannerView.swift in Sources */, 2E9FFA832BC673240051793C /* OpenHoursFields.graphql.swift in Sources */, 2E9416972BC616B9003DEB44 /* RouteDetail+ContentViewController.swift in Sources */, + 1C0296E12D8240A7005B92FC /* SettingsFaveViewController.swift in Sources */, FDE68D262C97FC0D00024A69 /* TransitService.swift in Sources */, 2E9416F62BC61984003DEB44 /* Time.swift in Sources */, 2E9FFA8D2BC673240051793C /* Query.graphql.swift in Sources */, 2E9416792BC61679003DEB44 /* AddFavoritesCollectionViewCell.swift in Sources */, + 1C0296E52D87A2EA005B92FC /* SettingsAboutHeaderView.swift in Sources */, 2E9FFA812BC673240051793C /* FacilityFields.graphql.swift in Sources */, + 1C591B5A2D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift in Sources */, 2E94171F2BC61CF1003DEB44 /* BusIcon.swift in Sources */, FDA3439F2CB6DF5800608A1A /* NetworkMonitor.swift in Sources */, 2E9417242BC61CF1003DEB44 /* DetailIconView.swift in Sources */, 2E94171A2BC61CF1003DEB44 /* SummaryView.swift in Sources */, 2E9416992BC616B9003DEB44 /* RouteDetailContentViewController+Extensions.swift in Sources */, 2E9416F12BC61984003DEB44 /* Shared.swift in Sources */, + 1C0296F02D8B5CFC005B92FC /* ContainerView.swift in Sources */, 2E9FFA892BC673240051793C /* Capacity.graphql.swift in Sources */, 2E9416BB2BC61731003DEB44 /* ServiceAlert.swift in Sources */, FDE68D222C97EF6200024A69 /* ApiEndpoint.swift in Sources */, + 1C591B4F2D8D22BD00DDA71D /* SettingsSupportViewController.swift in Sources */, 2EC1F5162BC66CBA001D9F66 /* Publishers.swift in Sources */, 2E94169E2BC616B9003DEB44 /* SearchResultsViewController.swift in Sources */, 2E9FFA822BC673240051793C /* GymFields.graphql.swift in Sources */, @@ -915,6 +972,7 @@ 2E9416F52BC61984003DEB44 /* Extensions+App.swift in Sources */, 2E9416782BC61679003DEB44 /* LargeDetailTableViewCell.swift in Sources */, 2E94169F2BC616B9003DEB44 /* HomeOptionsCardViewController.swift in Sources */, + 1C0296ED2D87A696005B92FC /* SettingsPrivacyView.swift in Sources */, 2E94169A2BC616B9003DEB44 /* CustomNavigationController.swift in Sources */, 2E9416DE2BC618DA003DEB44 /* Constants.swift in Sources */, 2E9FFA862BC673240051793C /* CourtType.graphql.swift in Sources */, @@ -931,7 +989,9 @@ 2E9416F22BC61984003DEB44 /* Styles.swift in Sources */, 2E9416DF2BC618DA003DEB44 /* TransitEnvironment.swift in Sources */, 2E9FFA8B2BC673240051793C /* Gym.graphql.swift in Sources */, + 1C0296D82D7FE055005B92FC /* SettingsAppIconViewController.swift in Sources */, 2E9FFA8E2BC673240051793C /* SchemaConfiguration.swift in Sources */, + 1C0296E72D87A37D005B92FC /* SettingsAboutMembersCarouselView.swift in Sources */, 2E9416EF2BC61984003DEB44 /* EventPayload.swift in Sources */, FDE68D202C97EBBE00024A69 /* ApiErrorHandler.swift in Sources */, 22948BFD221B75C5003FC43F /* RequestModels.swift in Sources */, diff --git a/TCAT/Assets.xcassets/AppIcon.appiconset/Contents.json b/TCAT/Assets.xcassets/AppIcon.appiconset/Contents.json index 40623651..70e75fd6 100644 --- a/TCAT/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/TCAT/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,158 +1,158 @@ { "images" : [ { - "size" : "20x20", - "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" }, { - "size" : "20x20", - "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", - "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" }, { - "size" : "40x40", - "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", - "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" }, { - "size" : "57x57", - "idiom" : "iphone", "filename" : "Icon-App-57x57@1x.png", - "scale" : "1x" + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" }, { - "size" : "57x57", - "idiom" : "iphone", "filename" : "Icon-App-57x57@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" }, { - "size" : "60x60", - "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", - "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" }, { - "size" : "20x20", - "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" }, { - "size" : "20x20", - "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", - "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", - "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", - "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", - "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" }, { - "size" : "50x50", - "idiom" : "ipad", "filename" : "Icon-Small-50x50@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" }, { - "size" : "50x50", - "idiom" : "ipad", "filename" : "Icon-Small-50x50@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" }, { - "size" : "72x72", - "idiom" : "ipad", "filename" : "Icon-App-72x72@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" }, { - "size" : "72x72", - "idiom" : "ipad", "filename" : "Icon-App-72x72@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" }, { - "size" : "76x76", - "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" }, { - "size" : "76x76", - "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" }, { - "size" : "83.5x83.5", - "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" }, { - "size" : "1024x1024", - "idiom" : "ios-marketing", "filename" : "ItunesArtwork@2x.png", - "scale" : "1x" + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/TCAT/Assets.xcassets/AppIcons/Contents.json b/TCAT/Assets.xcassets/AppIcons/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/TCAT/Assets.xcassets/AppIcons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json b/TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json new file mode 100644 index 00000000..8c99c360 --- /dev/null +++ b/TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ItunesArtwork@2x copy.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/AppIcons/Default.imageset/ItunesArtwork@2x copy.png b/TCAT/Assets.xcassets/AppIcons/Default.imageset/ItunesArtwork@2x copy.png new file mode 100644 index 00000000..2cca64bf Binary files /dev/null and b/TCAT/Assets.xcassets/AppIcons/Default.imageset/ItunesArtwork@2x copy.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/Contents.json b/TCAT/Assets.xcassets/Settings Assets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Contents.json new file mode 100644 index 00000000..72614f14 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Globe.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Globe.pdf b/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Globe.pdf new file mode 100644 index 00000000..e0a4ea71 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Globe.pdf differ diff --git a/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Contents.json new file mode 100644 index 00000000..3d2cb7f6 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Report.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Report.pdf b/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Report.pdf new file mode 100644 index 00000000..b0f60d0c Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/Report.imageset/Report.pdf differ diff --git a/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/AppDevLogo.pdf b/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/AppDevLogo.pdf new file mode 100644 index 00000000..5e71b364 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/AppDevLogo.pdf differ diff --git a/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/Contents.json new file mode 100644 index 00000000..38ef5f9c --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "AppDevLogo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/Contents.json new file mode 100644 index 00000000..55e20d59 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "External Link.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/External Link.png b/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/External Link.png new file mode 100644 index 00000000..849becd9 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/External Link.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Contents.json new file mode 100644 index 00000000..178660d5 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Vector-2.png b/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Vector-2.png new file mode 100644 index 00000000..30635e6e Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Vector-2.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Contents.json new file mode 100644 index 00000000..446d6be0 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Vector.png b/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Vector.png new file mode 100644 index 00000000..bb33871e Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Vector.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Contents.json new file mode 100644 index 00000000..b4145831 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector-4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Vector-4.png b/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Vector-4.png new file mode 100644 index 00000000..defa08a0 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/lock.imageset/Vector-4.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Contents.json new file mode 100644 index 00000000..d4bd8ddd --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector-5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Vector-5.png b/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Vector-5.png new file mode 100644 index 00000000..ac8da6d8 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Vector-5.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Contents.json new file mode 100644 index 00000000..f3fc3ce1 --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector-6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Vector-6.png b/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Vector-6.png new file mode 100644 index 00000000..a44ca4c0 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Vector-6.png differ diff --git a/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Contents.json new file mode 100644 index 00000000..b1eca72c --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector-3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Vector-3.png b/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Vector-3.png new file mode 100644 index 00000000..118e6790 Binary files /dev/null and b/TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Vector-3.png differ diff --git a/TCAT/Cells/SettingsAppIconCollectionViewCell.swift b/TCAT/Cells/SettingsAppIconCollectionViewCell.swift new file mode 100644 index 00000000..d8c95f09 --- /dev/null +++ b/TCAT/Cells/SettingsAppIconCollectionViewCell.swift @@ -0,0 +1,60 @@ +// +// SettingsAppIconCollectionViewCell.swift +// TCAT +// +// Created by Asen Ou on 3/21/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class SettingsAppIconCollectionViewCell: UICollectionViewCell { + // MARK: - Properties (view) + private let centerLabel = UILabel() + private let iconView = UIImageView() + + // MARK: - Properties (data) + static let reuse: String = "SettingsAppIconCollectionViewCellReuse" + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + contentView.backgroundColor = .black + + setUpUI() + setUpConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Data config + func configure(image: UIImage?, isSelected: Bool) { + iconView.image = image + } + + // MARK: - View setup + private func setUpUI() { + centerLabel.text = "*icon*" + centerLabel.font = UIFont.systemFont(ofSize: 6, weight: .bold) + centerLabel.textColor = .white + centerLabel.textAlignment = .center + contentView.addSubview(centerLabel) + + setUpIcon() + contentView.addSubview(iconView) + } + + private func setUpIcon() { + // + iconView.contentMode = .scaleAspectFit + } + + // MARK: - Constraints + private func setUpConstraints() { + centerLabel.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} diff --git a/TCAT/Cells/SettingsTableViewCell.swift b/TCAT/Cells/SettingsTableViewCell.swift new file mode 100644 index 00000000..e985de10 --- /dev/null +++ b/TCAT/Cells/SettingsTableViewCell.swift @@ -0,0 +1,95 @@ +// +// SettingsTableViewCell.swift +// TCAT +// +// Created by Asen Ou on 3/4/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import SnapKit +import UIKit + +class SettingsTableViewCell: UITableViewCell { + + // MARK: - Properties (view) + private let iconView = UIImageView() + private let titleLabel = UILabel() + private let subtitleLabel = UILabel() + + // MARK: - Properties (data) + static let reuse: String = "SettingsTableViewCellReuse" + + // MARK: - Init + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setUpUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // MARK: - Data config + func configure(image: UIImage?, title: String, subtitle: String) { + iconView.image = image + titleLabel.text = title + subtitleLabel.text = subtitle + } + + // MARK: - View setup + private func setUpUI() { + + setUpIcon() + contentView.addSubview(iconView) + + setUpLabels() + contentView.addSubview(titleLabel) + contentView.addSubview(subtitleLabel) + + setUpConstraints() + } + + private func setUpIcon() { + iconView.contentMode = .scaleAspectFit + iconView.tintColor = Colors.black + } + + private func setUpLabels() { + titleLabel.textColor = .black + titleLabel.font = .getFont(.regular, size: 20) + + subtitleLabel.textColor = .gray + subtitleLabel.font = .getFont(.regular, size: 14) + } + + private func setUpConstraints() { + let iconLeftXInset = 30 + let iconTextSpacing = 15 + let textRightXInset = 60 + let textYInset = 18 + + iconView.snp.makeConstraints { make in +// make.right.equalTo(titleLabel.snp.left).offset(-15) +// make.top.bottom.equalToSuperview() + make.left.equalToSuperview().inset(iconLeftXInset) + make.centerY.equalToSuperview() + make.size.equalTo(33) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(iconView.snp.right).offset(iconTextSpacing) + make.right.equalToSuperview().inset(textRightXInset) + + make.top.equalToSuperview().inset(textYInset) + make.bottom.equalTo(subtitleLabel.snp.top) + } + + subtitleLabel.snp.makeConstraints { make in + make.left.equalTo(iconView.snp.right).offset(iconTextSpacing) + make.right.equalToSuperview().inset(textRightXInset) + + make.bottom.equalToSuperview().inset(textYInset) + } + } + +} diff --git a/TCAT/Controllers/HomeOptionsCardViewController.swift b/TCAT/Controllers/HomeOptionsCardViewController.swift index 292b112c..5efd4878 100644 --- a/TCAT/Controllers/HomeOptionsCardViewController.swift +++ b/TCAT/Controllers/HomeOptionsCardViewController.swift @@ -290,9 +290,10 @@ class HomeOptionsCardViewController: UIViewController { /// Open information screen @objc private func openInformationScreen() { - let informationViewController = InformationViewController() - let navigationVC = CustomNavigationController(rootViewController: informationViewController) - present(navigationVC, animated: true) + let informationViewController = SettingsViewController() + navigationController?.pushViewController(informationViewController, animated: true) +// let navigationVC = CustomNavigationController(rootViewController: informationViewController) +// present(navigationVC, animated: true) } // MARK: - Get Search Results diff --git a/TCAT/Controllers/InformationViewController.swift b/TCAT/Controllers/InformationViewController.swift index f47a4e3d..a2887b56 100644 --- a/TCAT/Controllers/InformationViewController.swift +++ b/TCAT/Controllers/InformationViewController.swift @@ -33,7 +33,7 @@ class InformationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - let payload = AboutPageOpenedPayload() + let payload = SettingsPageOpenedPayload() TransitAnalytics.shared.log(payload) title = Constants.Titles.aboutUs diff --git a/TCAT/Controllers/ServiceAlertsViewController.swift b/TCAT/Controllers/ServiceAlertsViewController.swift index b20d08b4..01153571 100644 --- a/TCAT/Controllers/ServiceAlertsViewController.swift +++ b/TCAT/Controllers/ServiceAlertsViewController.swift @@ -44,6 +44,9 @@ class ServiceAlertsViewController: UIViewController { title = Constants.Titles.serviceAlerts + // Temporary change for settings page (make nav title prefer large to fit settings page theme) + navigationController?.navigationBar.prefersLargeTitles = true + view.backgroundColor = Colors.backgroundWash tableView.backgroundColor = view.backgroundColor tableView.dataSource = self @@ -62,7 +65,11 @@ class ServiceAlertsViewController: UIViewController { setupConstraints() getServiceAlerts() - + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let payload = ServiceAlertsPayload() TransitAnalytics.shared.log(payload) } diff --git a/TCAT/Controllers/SettingsAboutViewController.swift b/TCAT/Controllers/SettingsAboutViewController.swift new file mode 100644 index 00000000..00d322b0 --- /dev/null +++ b/TCAT/Controllers/SettingsAboutViewController.swift @@ -0,0 +1,257 @@ +// +// SettingsAboutViewController.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class SettingsAboutViewController: UIViewController { + + // MARK: - Properties (view) + private let subtitleLabel = UILabel() + private let headerView = SettingsAboutHeaderView() + private let scrollView = UIScrollView() + private let stackView = UIStackView() + private let websiteButton = ButtonView(content: PillButtonView()) + + // MARK: - Properties (data) + private var firstTimeLoading = true + + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setUpView() + setUpConstraints() + setUpCarouselViews() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // Track Analytics + let payload = SettingsAboutPageOpenedPayload() + TransitAnalytics.shared.log(payload) + + scrollView.flashScrollIndicators() + if !firstTimeLoading { + reshuffle() + } else { + firstTimeLoading = false + } + } + + private func setUpNavigationItem() { + navigationItem.title = "About Transit" + } + + private func setUpView() { + view.backgroundColor = .white + + view.addSubview(subtitleLabel) + setUpSubtitleLabel() + + view.addSubview(headerView) + + view.addSubview(scrollView) + setUpScrollView() + + view.addSubview(websiteButton) + setUpWebsiteButton() + } + + private func setUpSubtitleLabel() { + subtitleLabel.text = "Learn more about Cornell AppDev" + subtitleLabel.textColor = .gray + subtitleLabel.font = .preferredFont(forTextStyle: .body) +// subtitleLabel.textColor = UIColor.Eatery.gray06 +// subtitleLabel.font = .preferredFont(for: .body, weight: .medium) + } + + private func setUpScrollView() { + scrollView.addSubview(stackView) + setUpStackView() + } + + private func setUpStackView() { + stackView.axis = .vertical + } + + private func setUpWebsiteButton() { + let pillView = websiteButton.content + pillView.titleLabel.text = "Visit our website" + pillView.imageView.image = UIImage(named: "Globe")?.withRenderingMode(.alwaysTemplate) + pillView.tintColor = .black + pillView.backgroundColor = Colors.carouselGray + pillView.layoutMargins = UIEdgeInsets(top: 12, left: 0, bottom: 12, right: 0) + + websiteButton.buttonPress { _ in + if let url = URL(string: "https://www.cornellappdev.com/") { + UIApplication.shared.open(url, options: [:]) + } + } + } + + private func setUpConstraints() { + subtitleLabel.snp.makeConstraints { make in + make.top.leading.equalTo(view.layoutMarginsGuide) + } + + headerView.snp.makeConstraints { make in + make.top.equalTo(subtitleLabel.snp.bottom).offset(24) + make.leading.trailing.equalTo(view.layoutMarginsGuide) + } + + scrollView.snp.makeConstraints { make in + make.top.equalTo(headerView.snp.bottom).offset(24) + make.leading.trailing.equalToSuperview() + make.bottom.equalTo(websiteButton.snp.top).offset(-24) + } + + stackView.snp.makeConstraints { make in + make.edges.equalTo(scrollView.contentLayoutGuide) + make.width.equalTo(scrollView.frameLayoutGuide) + } + + websiteButton.snp.makeConstraints { make in + make.leading.trailing.equalTo(view.layoutMarginsGuide) + make.bottom.equalTo(view.layoutMarginsGuide).inset(16) + } + } + + private struct HSection { + let title: String + let members: [String] + } + + private let sections = [ + HSection(title: "Pod Leads", members: [ + "Anvi Savant", + "Cindy Liang", + "Maxwell Pang", + "Amanda He", + "Connor Reinhold", + "Omar Rasheed", + "Maya Frai", + "Matt Barker" + ]), + HSection(title: "iOS Developers", members: [ + "Angelina Chen", + "Asen Ou", + "Jayson Hahn", + "Daniel Chuang", + "William Ma", + "Sergio Diaz", + "Kevin Chan", + "Omar Rasheed", + "Lucy Xu", + "Haiying Weng", + "Daniel Vebman", + "Yana Sang", + "Matt Barker", + "Austin Astorga", + "Monica Ong" + ]), + HSection(title: "Android Developers", members: [ + "Mihili Herath", + "Jonathan Chen", + "Veronica Starchenko", + "Adam Kadhim", + "Lesley Huang", + "Kevin Sun", + "Chris Desir", + "Connor Reinhold", + "Aastha Shah", + "Justin Jiang", + "Haichen Wang", + "Jonvi Rollins", + "Preston Rozwood", + "Ziwei Gu", + "Abdullah Islam" + ]), + HSection(title: "Product Designers", members: [ + "Gillian Fang", + "Leah Kim", + "Amy Ge", + "Lauren Jun", + "Zain Khoja", + "Maggie Ying", + "Femi Badero", + "Maya Frai", + "Mind Apivessa" + ]), + HSection(title: "Marketers", members: [ + "Anvi Savant", + "Christine Tao", + "Luke Stewart", + "Melika Khoshneviszadeh", + "Eddie Chi", + "Neha Malepati", + "Emily Shiang", + "Lucy Zhang", + "Catherine Wei" + ]), + HSection(title: "Backend Developers", members: [ + "Nicole Qiu", + "Daisy Chang", + "Lauren Ah-Hot", + "Maxwell Pang", + "Mateo Weiner", + "Cindy Liang", + "Raahi Menon", + "Kate Liang", + "Alanna Zhou", + "Kevin Chan", + "Nate Schickler" + ]) + ] + + private func addCarouselView(_ configure: (SettingsAboutMembersCarouselView) -> Void) { + let carouselView = SettingsAboutMembersCarouselView() + configure(carouselView) + stackView.addArrangedSubview(carouselView) + } + + private func setUpCarouselViews() { + let sections = [sections[0]] + sections[1...].shuffled() + for section in sections { + addCarouselView(section) + } + } + + private func addCarouselView(_ section: HSection) { + addCarouselView { carouselView in + carouselView.addTitleView(section.title) + carouselView.addSeparator() + + for (i, member) in section.members.shuffled().enumerated() { + carouselView.addMemberView(name: member) + + if i != section.members.count - 1 { + carouselView.addSeparator() + } + } + } + } + + private func reshuffle() { + let group = DispatchGroup() + for subview in stackView.subviews { + group.enter() + UIView.animate( + withDuration: 0.2, + animations: { subview.alpha = 0.0 }, + completion: { _ in + subview.removeFromSuperview() + group.leave() + } + ) + } + + group.notify(queue: .main) { + self.setUpCarouselViews() + } + } +} diff --git a/TCAT/Controllers/SettingsAppIconViewController.swift b/TCAT/Controllers/SettingsAppIconViewController.swift new file mode 100644 index 00000000..2bdab1fb --- /dev/null +++ b/TCAT/Controllers/SettingsAppIconViewController.swift @@ -0,0 +1,103 @@ +// +// SettingsAppIconViewController.swift +// TCAT +// +// Created by Asen Ou on 3/10/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import SnapKit +import UIKit + +struct AppIcon { + let name: String + let icon: UIImage? + var selected: Bool = false +} + +class SettingsAppIconViewController: UIViewController { + + // MARK: - Properties (data) + private let icons: [AppIcon] = [ + // icons go here + AppIcon(name: "Default", icon: UIImage(named: "AppIcon-Icon-App-20x20@2x")) + ] + + // MARK: - Properties (view) + private let collView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()) + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setupUI() + setupConstraints() + } + + // MARK: - Nav item setup + private func setUpNavigationItem() { + let rightBarButton = UIBarButtonItem() + navigationController?.navigationItem.rightBarButtonItem = rightBarButton + } + + // MARK: - UI Setup + private func setupUI() { + // Configure view + view.backgroundColor = .white + title = "App Icons" + + setUpIconsCollectionView() + view.addSubview(collView) + } + + private func setUpIconsCollectionView() { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + collView.setCollectionViewLayout(layout, animated: true) + collView.register(SettingsAppIconCollectionViewCell.self, forCellWithReuseIdentifier: SettingsAppIconCollectionViewCell.reuse) + collView.dataSource = self + collView.delegate = self + collView.showsVerticalScrollIndicator = false + collView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + +// let layout = UICollectionViewFlowLayout() +// collView.collectionViewLayout = layout +// layout.scrollDirection = .vertical +// layout.minimumLineSpacing = 15 +// +// collView.register(SettingsAppIconCollectionViewCell.self, forCellWithReuseIdentifier: SettingsAppIconCollectionViewCell.reuse) +// collView.delegate = self +// collView.dataSource = self + } + + // MARK: - Constraints + private func setupConstraints() { + collView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} + +extension SettingsAppIconViewController: UICollectionViewDelegate, UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return icons.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: SettingsAppIconCollectionViewCell.reuse, + for: indexPath + ) as? SettingsAppIconCollectionViewCell else { return UICollectionViewCell() } + + let appIcon = icons[indexPath.row] + cell.configure(image: appIcon.icon, isSelected: appIcon.selected) + + return cell + } + +} diff --git a/TCAT/Controllers/SettingsFaveViewController.swift b/TCAT/Controllers/SettingsFaveViewController.swift new file mode 100644 index 00000000..e8860ca9 --- /dev/null +++ b/TCAT/Controllers/SettingsFaveViewController.swift @@ -0,0 +1,49 @@ +// +// SettingsFaveViewController.swift +// TCAT +// +// Created by Asen Ou on 3/12/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import SnapKit +import UIKit + +class SettingsFaveViewController: UIViewController { + + // MARK: - Properties + private let centerLabel = UILabel() + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + setupConstraints() + } + + // MARK: - UI Setup + private func setupUI() { + // Configure view + view.backgroundColor = .white + title = "Favorites" + + // Configure label + centerLabel.text = "PLACEHOLDER" + centerLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold) + centerLabel.textColor = .black + centerLabel.textAlignment = .center + + // Add subviews + view.addSubview(centerLabel) + } + + // MARK: - Constraints + private func setupConstraints() { + centerLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(20) + } + } + +} diff --git a/TCAT/Controllers/SettingsPrivacyViewController.swift b/TCAT/Controllers/SettingsPrivacyViewController.swift new file mode 100644 index 00000000..a39d9436 --- /dev/null +++ b/TCAT/Controllers/SettingsPrivacyViewController.swift @@ -0,0 +1,113 @@ +// +// SettingsPrivacyViewController.swift +// TCAT +// +// Created by Asen Ou on 3/12/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// +import Combine +import Foundation +import CoreLocation +import SwiftUI + +class SettingsPrivacyViewController: UIViewController { + + private lazy var hostingController: UIHostingController = { + let hostingController = UIHostingController(rootView: SettingsPrivacyView()) + return hostingController + }() + + private var cancellables: Set = [] + + private let locationManager = CLLocationManager() + + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setUpView() + setUpConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // Track Analytics + let payload = SettingsNotifPrivacyPageOpenedPayload() + TransitAnalytics.shared.log(payload) + + updateView() + } + + private func setUpNavigationItem() { + navigationItem.title = "Notifications & Privacy" + } + + private func setUpView() { + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + + hostingController.rootView.viewModel.$isAnalyticsEnabled + .dropFirst() + .sink { isAnalyticsEnabled in + UserDefaults.standard.set(isAnalyticsEnabled, forKey: Constants.UserDefaults.isAnalyticsEnabled) + } + .store(in: &cancellables) + + hostingController.rootView.viewModel.$isLocationAllowed + .dropFirst() + .sink { isLocationAllowed in + UserDefaults.standard.set(isLocationAllowed, forKey: Constants.UserDefaults.isLocationAllowed) + } + .store(in: &cancellables) + + hostingController.rootView.viewModel.$isNotificationsAllowed + .dropFirst() + .sink { isNotificationsAllowed in + UserDefaults.standard.set(isNotificationsAllowed, forKey: Constants.UserDefaults.isNotificationsAllowed) + } + .store(in: &cancellables) + } + + private func setUpConstraints() { + hostingController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } + + private func updateView() { + // location update + let isLocationAllowed: Bool + switch locationManager.authorizationStatus { + case .authorizedWhenInUse, .authorizedAlways: + isLocationAllowed = true + default: + isLocationAllowed = false + } + hostingController.rootView.viewModel.isLocationAllowed = isLocationAllowed + + // notifications update + var isNotificationsAllowed = false + UNUserNotificationCenter.current().getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized: + isNotificationsAllowed = true + default: + isNotificationsAllowed = false + } + + DispatchQueue.main.async { + self.hostingController.rootView.viewModel.isNotificationsAllowed = isNotificationsAllowed + } + } + + // analytics update + let isAnalyticsEnabled = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isAnalyticsEnabled) + hostingController.rootView.viewModel.isAnalyticsEnabled = isAnalyticsEnabled + } + +} diff --git a/TCAT/Controllers/SettingsSupportViewController.swift b/TCAT/Controllers/SettingsSupportViewController.swift new file mode 100644 index 00000000..8863b752 --- /dev/null +++ b/TCAT/Controllers/SettingsSupportViewController.swift @@ -0,0 +1,56 @@ +// +// SettingsSupportViewController.swift +// Eatery Blue +// +// Created by William Ma on 1/26/22. +// + +import Combine +import SwiftUI +import UIKit + +class SettingsSupportViewController: UIViewController { + + private lazy var hostingController: UIHostingController = { + let hostingController = UIHostingController(rootView: SettingsSupportView()) + return hostingController + }() + + private var cancellables: Set = [] + + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setUpView() + setUpConstraints() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Track Analytics + let payload = SettingsSupportPageOpenedPayload() + TransitAnalytics.shared.log(payload) + } + + private func setUpNavigationItem() { + navigationItem.title = "Support" + } + + private func setUpView() { + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + } + + private func setUpConstraints() { + hostingController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } +} diff --git a/TCAT/Controllers/SettingsViewController.swift b/TCAT/Controllers/SettingsViewController.swift new file mode 100644 index 00000000..67b70d8c --- /dev/null +++ b/TCAT/Controllers/SettingsViewController.swift @@ -0,0 +1,242 @@ +// +// SettingsViewController.swift +// TCAT +// +// Created by Asen Ou on 3/4/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit +import SnapKit + +enum NavigationAction { + case push(UIViewController) + case present(UIViewController, [UISheetPresentationController.Detent]) +} + +struct RowItem { + let image: UIImage? + let title: String + let subtitle: String + let navAction: NavigationAction +} + +class SettingsViewController: UIViewController { + // MARK: - Main View Properties + private let tableView = UITableView() + + // MARK: - Table View Properties + private var rows: [RowItem] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + // Track Analytics + let payload = SettingsPageOpenedPayload() + TransitAnalytics.shared.log(payload) + + // Populate row items + setUpRowItems() + + // Set up UI + setUpUI() + + // Set up constraints + setUpConstraints() + } + + private func setUpRowItems() { + rows = [ + RowItem( + image: UIImage(named: "appDevLogo"), + title: "About Transit", + subtitle: "Learn more about the team behind the app", + navAction: .push(SettingsAboutViewController()) + ), + RowItem( + image: UIImage(named: "lightBulb"), + title: "Show Onboarding", + subtitle: "Need a refresher? See how to use the app", + navAction: .present(OnboardingViewController(initialViewing: false), [.large()]) + ), +// These two rows will be added as progress is made with design + ecosystem +// RowItem( +// image: UIImage(named: "favStar"), +// title: "Favorites", +// subtitle: "Manage your favorite stops", +// navAction: .push(SettingsFaveViewController()) +// ), +// RowItem( +// image: UIImage(named: "settingsBus"), +// title: "App Icons", +// subtitle: "Choose your adventure", +// navAction: .present(SettingsAppIconViewController(), [.medium()]) +// ), + RowItem( + image: UIImage(named: "lock"), + title: "Notifications & Privacy", + subtitle: "Manage permissions and analytics", + navAction: .push(SettingsPrivacyViewController()) + ), + RowItem( + image: UIImage(named: "qMark"), + title: "Support", + subtitle: "Report issues and contact Cornell AppDev", + navAction: .push(SettingsSupportViewController()) + ), + RowItem( + image: UIImage(named: "settingsBus"), + title: "TCAT Service Alerts", + subtitle: "Find service alerts about routes", + navAction: .push(ServiceAlertsViewController()) + ) + ] + } + + // MARK: - UI Set Up + private func setUpUI() { + // Set up main view & nav + setUpMainView() + setUpNavigationItem() + + // Set up subviews + setUpTableView() + view.addSubview(tableView) + } + + // MARK: - Main View Set Up + private func setUpMainView() { + // Initialize view defaults + title = "Settings" + view.backgroundColor = Colors.white + } + + // MARK: - Navigation Item Set Up + private func setUpNavigationItem() { + let backButton = UIBarButtonItem( + image: UIImage(named: "back"), + style: .plain, + target: self, + action: #selector(didTapBackButton) + ) + backButton.tintColor = .black + navigationItem.leftBarButtonItem = backButton + + // navigation bar appearance + let appearance = UINavigationBarAppearance() + appearance.largeTitleTextAttributes = [ + .foregroundColor: Colors.tcatBlue as Any + ] + + let scrollEdgeAppearance = appearance.copy() + scrollEdgeAppearance.backgroundColor = Colors.white + scrollEdgeAppearance.shadowColor = .clear + navigationItem.scrollEdgeAppearance = scrollEdgeAppearance + + let standardAppearance = appearance.copy() + navigationItem.standardAppearance = standardAppearance + + navigationController?.navigationBar.prefersLargeTitles = true + } +} + +// MARK: - TableView Set Up +extension SettingsViewController: UITableViewDataSource, UITableViewDelegate, InfoHeaderViewDelegate { + private func setUpTableView() { + tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.reuse) + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = Colors.white + tableView.separatorColor = Colors.dividerTextField + tableView.showsVerticalScrollIndicator = false + tableView.separatorInset = .zero + + let headerView = InformationTableHeaderView() + headerView.delegate = self + tableView.tableFooterView = headerView + } + + // function for InfoHeaderViewDelegate + func showFunMessage() { + let title = Constants.Alerts.MagicBus.title + let message = Constants.Alerts.MagicBus.message + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: Constants.Alerts.MagicBus.action, style: .default, handler: nil)) + present(alertController, animated: true) + } + + // navigation handler for this page + private func handleNavigation(action: NavigationAction) { + // customize pushed view controller's appearance + let appearance = UINavigationBarAppearance() + appearance.largeTitleTextAttributes = [ + .foregroundColor: Colors.tcatBlue as Any + ] + let scrollEdgeAppearance = appearance.copy() + scrollEdgeAppearance.shadowColor = .clear + + // custom action based on NavigationAction type + switch action { + case .push(let viewController): + scrollEdgeAppearance.backgroundColor = Colors.white + viewController.navigationItem.scrollEdgeAppearance = scrollEdgeAppearance + navigationController?.pushViewController(viewController, animated: true) + case .present(let viewController, let detents): + let nav = UINavigationController(rootViewController: viewController) + nav.modalPresentationStyle = .pageSheet + nav.navigationBar.prefersLargeTitles = true + + if let sheet = nav.sheetPresentationController { + sheet.detents = detents + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + } + + present(nav, animated: true) + } + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rows.count + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let rowItem = rows[indexPath.row] + handleNavigation(action: rowItem.navAction) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: SettingsTableViewCell.reuse, + for: indexPath + ) as? SettingsTableViewCell else { return UITableViewCell() } + + let rowItem = rows[indexPath.row] + cell.configure( + image: rowItem.image, + title: rowItem.title, + subtitle: rowItem.subtitle + ) + + if indexPath.row == (rows.count - 1) { + cell.separatorInset.left = .infinity + } + + cell.selectionStyle = .none + return cell + } + + // MARK: - Constraints Set Up + private func setUpConstraints() { + tableView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + // MARK: - Back button interaction + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } +} diff --git a/TCAT/InformationAbout/In.swift b/TCAT/InformationAbout/In.swift new file mode 100644 index 00000000..8e5b94f9 --- /dev/null +++ b/TCAT/InformationAbout/In.swift @@ -0,0 +1,8 @@ +// +// In.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + diff --git a/TCAT/InformationAbout/InformationAboutViewController.swift b/TCAT/InformationAbout/InformationAboutViewController.swift new file mode 100644 index 00000000..7dce0d65 --- /dev/null +++ b/TCAT/InformationAbout/InformationAboutViewController.swift @@ -0,0 +1,48 @@ +// +// InformationAboutViewController.swift +// TCAT +// +// Created by Asen Ou on 3/9/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class InformationAboutViewController: UIViewController { + + // MARK: - Properties + private let centerLabel = UILabel() + + // MARK: - Lifecycle + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + setupConstraints() + } + + // MARK: - UI Setup + private func setupUI() { + // Configure view + view.backgroundColor = .white + title = "About" + + // Configure label + centerLabel.text = "PLACEHOLDER" + centerLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold) + centerLabel.textColor = .black + centerLabel.textAlignment = .center + + // Add subviews + view.addSubview(centerLabel) + } + + // MARK: - Constraints + private func setupConstraints() { + centerLabel.snp.makeConstraints { make in + make.center.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(20) + } + } + +} diff --git a/TCAT/InformationAppIcon/Controllers/InfoAppIconSheetPresentationController.swift b/TCAT/InformationAppIcon/Controllers/InfoAppIconSheetPresentationController.swift new file mode 100644 index 00000000..6c6510af --- /dev/null +++ b/TCAT/InformationAppIcon/Controllers/InfoAppIconSheetPresentationController.swift @@ -0,0 +1,13 @@ +// +// InfoAppIconSheetPresentationController.swift +// TCAT +// +// Created by Asen Ou on 3/9/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class InfoAppIconSheetPresentationController: UISheetPresentationController { + +} diff --git a/TCAT/InformationPrivacy/Untitled.swift b/TCAT/InformationPrivacy/Untitled.swift new file mode 100644 index 00000000..e69de29b diff --git a/TCAT/Supporting/Constants.swift b/TCAT/Supporting/Constants.swift index 56033122..36ba1486 100644 --- a/TCAT/Supporting/Constants.swift +++ b/TCAT/Supporting/Constants.swift @@ -304,6 +304,12 @@ struct Constants { static let promotionDismissed = "promotionDismissed" static let recentSearch = "recentSearch" static let servicedRoutes = "servicedRoutes" + + /// Analytics variables for Settings Page / Privacy + static let isAnalyticsEnabled = "isAnalyticsEnabled" + static let isLocationAllowed = "isLocationAllowed" + static let isNotificationsAllowed = "isNotificationsAllowed" + static let activeIcon = "activeIcon" } struct Values { diff --git a/TCAT/Utils/Analytics.swift b/TCAT/Utils/Analytics.swift index 7a438ec3..b9b560cf 100644 --- a/TCAT/Utils/Analytics.swift +++ b/TCAT/Utils/Analytics.swift @@ -17,9 +17,18 @@ class TransitAnalytics { func log(_ payload: Payload) { #if !DEBUG - let fabricEvent = payload.convertToFabric() - Analytics.logEvent(fabricEvent.name, parameters: fabricEvent.attributes) + let analyticsEnabled = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isAnalyticsEnabled) + if analyticsEnabled { + let fabricEvent = payload.convertToFabric() + Analytics.logEvent(fabricEvent.name, parameters: fabricEvent.attributes) + } #endif + let analyticsEnabled = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isAnalyticsEnabled) + if analyticsEnabled { + print("I'm analysing you!") + } else { + print("No analysis") + } } } @@ -111,9 +120,27 @@ struct RouteResultsCellPeekedPayload: Payload { static let eventName: String = "Route Results Cell Peeked" } -/// Log opening of About page -struct AboutPageOpenedPayload: Payload { - static let eventName: String = "About Page Opened" +/// Log opening of About page (settings page) +struct SettingsPageOpenedPayload: Payload { + static let eventName: String = "Settings Page Opened" + var deviceInfo = DeviceInfo() +} + +/// Log opening of Settings about page +struct SettingsAboutPageOpenedPayload: Payload { + static let eventName: String = "Settings About Page Opened" + var deviceInfo = DeviceInfo() +} + +/// Log opening of Settings Notif/Privacy page +struct SettingsNotifPrivacyPageOpenedPayload: Payload { + static let eventName: String = "Settings Notifications & Privacy Page Opened" + var deviceInfo = DeviceInfo() +} + +/// Log opening of Settings Support page +struct SettingsSupportPageOpenedPayload: Payload { + static let eventName: String = "Settings Support Page Opened" var deviceInfo = DeviceInfo() } diff --git a/TCAT/Utils/Styles.swift b/TCAT/Utils/Styles.swift index 028324ee..bf7431d7 100644 --- a/TCAT/Utils/Styles.swift +++ b/TCAT/Utils/Styles.swift @@ -31,6 +31,7 @@ struct Colors { // MARK: - Constants static let black = UIColor.black static let white = UIColor.white + static let carouselGray = UIColor(hex: "EFF1F4") } diff --git a/TCAT/Views/ButtonView.swift b/TCAT/Views/ButtonView.swift new file mode 100644 index 00000000..0cae3d28 --- /dev/null +++ b/TCAT/Views/ButtonView.swift @@ -0,0 +1,58 @@ +// +// ButtonView.swift +// Eatery Blue +// +// Created by William Ma on 1/21/22. +// + +import UIKit + +class ButtonView: ContainerView, UIGestureRecognizerDelegate { + + private let button = UIButton(type: .custom) + + private var callback: ((UIButton) -> Void)? + + override init(content: Content) { + super.init(content: content) + + addSubview(button) + button.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + button.addTarget(self, action: #selector(buttonTouchDown), for: .touchDown) + button.addTarget(self, action: #selector(buttonTouchUpInside), for: .touchUpInside) + button.addTarget(self, action: #selector(buttonTouchUpOutside), for: .touchUpOutside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func buttonPress(_ callback: ((UIButton) -> Void)?) { + self.callback = callback + } + + @objc private func buttonTouchDown(_ sender: UIButton) { + UIView.animate(withDuration: 0.15, delay: 0, options: .beginFromCurrentState) { [weak self] in + self?.transform = CGAffineTransform(scaleX: 0.95, y: 0.95) + } + } + + @objc private func buttonTouchUpInside(_ sender: UIButton) { + UIView.animate(withDuration: 0.15, delay: 0.15, options: .beginFromCurrentState) { [weak self] in + self?.transform = .identity + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + self?.callback?(sender) + } + } + } + + @objc private func buttonTouchUpOutside(_ sender: UIButton) { + UIView.animate(withDuration: 0.15, delay: 0.15, options: .beginFromCurrentState) { [weak self] in + self?.transform = .identity + } + } + +} diff --git a/TCAT/Views/ContainerView.swift b/TCAT/Views/ContainerView.swift new file mode 100644 index 00000000..5f9b7fa3 --- /dev/null +++ b/TCAT/Views/ContainerView.swift @@ -0,0 +1,116 @@ +// +// ContainerView.swift +// Eatery Blue +// +// Created by William Ma on 12/22/21. +// + +import UIKit + +// Applies the following transformations to the content view +// 1. content inset based on layoutMargins +// 2. corner radius crop (if cornerRadius is non-zero) +// 3. shadow +// +class ContainerView: UIView { + + let cornerRadiusView = UIView() + + override var backgroundColor: UIColor? { + didSet { + cornerRadiusView.backgroundColor = backgroundColor + super.backgroundColor = nil + } + } + + var content: Content { + willSet { + content.removeFromSuperview() + } + didSet { + cornerRadiusView.addSubview(content) + content.snp.makeConstraints { make in + make.edges.equalTo(layoutMarginsGuide) + } + } + } + + var cornerRadius: CGFloat = 0 { + didSet { + cornerRadiusView.clipsToBounds = cornerRadius != 0 + cornerRadiusView.layer.cornerRadius = cornerRadius + } + } + + var shadowColor: UIColor? { + didSet { + layer.shadowColor = shadowColor?.cgColor + } + } + + var shadowOffset: CGSize = .zero { + didSet { + layer.shadowOffset = shadowOffset + } + } + + var shadowOpacity: Double = 0 { + didSet { + layer.shadowOpacity = Float(shadowOpacity) + } + } + + var shadowRadius: CGFloat = 0 { + didSet { + layer.shadowRadius = shadowRadius + } + } + + private var isPill: Bool = false + var interceptsHitTests: Bool = false + + init(content: Content) { + self.content = content + + super.init(frame: .null) + + insetsLayoutMarginsFromSafeArea = false + layoutMargins = .zero + + addSubview(cornerRadiusView) + cornerRadiusView.snp.makeConstraints { make in + make.edges.equalTo(self) + } + + cornerRadiusView.addSubview(content) + content.snp.makeConstraints { make in + make.edges.equalTo(layoutMarginsGuide) + } + } + + convenience init(pillContent: Content) { + self.init(content: pillContent) + self.isPill = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + if isPill { + cornerRadius = min(cornerRadiusView.bounds.width, cornerRadiusView.bounds.height) / 2 + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if interceptsHitTests { + return self + } + + return super.hitTest(point, with: event) + } + +} diff --git a/TCAT/Views/PillButtonView.swift b/TCAT/Views/PillButtonView.swift new file mode 100644 index 00000000..5189ddcd --- /dev/null +++ b/TCAT/Views/PillButtonView.swift @@ -0,0 +1,79 @@ +// +// PillButtonView.swift +// Eatery Blue +// +// Created by William Ma on 12/23/21. +// + +import UIKit + +class PillButtonView: UIView { + + private let container = UIView() + let imageView = UIImageView() + let titleLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + setUpSelf() + setUpConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpSelf() { + addSubview(container) + setUpContainer() + } + + private func setUpContainer() { + container.addSubview(imageView) + setUpImageView() + + container.addSubview(titleLabel) + setUpTitleLabel() + } + + private func setUpImageView() { + imageView.contentMode = .scaleAspectFit + } + + private func setUpTitleLabel() { +// titleLabel.font = .preferredFont(for: .body, weight: .semibold) + titleLabel.font = .preferredFont(forTextStyle: .body) + } + + private func setUpConstraints() { + container.snp.makeConstraints { make in + make.centerX.top.bottom.equalTo(layoutMarginsGuide) + make.leading.greaterThanOrEqualTo(layoutMarginsGuide) + make.trailing.lessThanOrEqualTo(layoutMarginsGuide) + } + + imageView.snp.makeConstraints { make in + make.width.height.equalTo(16) + make.leading.centerY.equalToSuperview() + make.top.greaterThanOrEqualToSuperview() + } + + titleLabel.snp.makeConstraints { make in + make.leading.equalTo(imageView.snp.trailing).offset(4) + make.top.trailing.bottom.equalToSuperview() + } + + titleLabel.setContentHuggingPriority( + imageView.contentHuggingPriority(for: .horizontal) + 1, + for: .horizontal + ) + } + + override func layoutSubviews() { + super.layoutSubviews() + + layer.cornerRadius = min(bounds.width, bounds.height) / 2 + } + +} diff --git a/TCAT/Views/SettingsAboutHeaderView.swift b/TCAT/Views/SettingsAboutHeaderView.swift new file mode 100644 index 00000000..cdad5b61 --- /dev/null +++ b/TCAT/Views/SettingsAboutHeaderView.swift @@ -0,0 +1,85 @@ +// +// SettingsAboutHeaderView.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class SettingsAboutHeaderView: UIView { + + private let stackView = UIStackView() + + private let logoView = UIImageView() + private let subtitleLabel = UILabel() + private let titleLabel = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + setUpSelf() + setUpConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpSelf() { + addSubview(stackView) + setUpStackView() + } + + private func setUpStackView() { + stackView.alignment = .center + stackView.axis = .vertical + + stackView.addArrangedSubview(logoView) + setUpLogoView() + stackView.setCustomSpacing(12, after: logoView) + + stackView.addArrangedSubview(subtitleLabel) + setUpSubtitleLabel() + stackView.setCustomSpacing(0, after: subtitleLabel) + + stackView.addArrangedSubview(titleLabel) + setUpTitleLabel() + } + + private func setUpLogoView() { + logoView.image = UIImage(named: "appDevLogo") + } + + private func setUpSubtitleLabel() { + subtitleLabel.text = "DESIGNED AND DEVELOPED BY" + let fontSize = UIFont.preferredFont(forTextStyle: .caption1).pointSize + subtitleLabel.font = .systemFont(ofSize: fontSize, weight: .medium) +// subtitleLabel.font = .preferredFont(for: .caption1, weight: .medium) +// subtitleLabel.textColor = UIColor + } + + private func setUpTitleLabel() { + let attributedText = NSMutableAttributedString() + attributedText.append(NSAttributedString(string: "Cornell", attributes: [ + .font: UIFont.systemFont(ofSize: 36, weight: .regular) + ])) + attributedText.append(NSAttributedString(string: "AppDev", attributes: [ + .font: UIFont.systemFont(ofSize: 36, weight: .semibold) + ])) + titleLabel.attributedText = attributedText + titleLabel.textColor = .black + } + + private func setUpConstraints() { + stackView.snp.makeConstraints { make in + make.edges.equalTo(layoutMarginsGuide) + } + + logoView.snp.makeConstraints { make in + make.width.height.equalTo(24) + } + } + +} diff --git a/TCAT/Views/SettingsAboutMembersCarouselView.swift b/TCAT/Views/SettingsAboutMembersCarouselView.swift new file mode 100644 index 00000000..decdb438 --- /dev/null +++ b/TCAT/Views/SettingsAboutMembersCarouselView.swift @@ -0,0 +1,108 @@ +// +// SettingsAboutCarouselView.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class SettingsAboutMembersCarouselView: UIView { + + private let scrollView = UIScrollView() + private let stackView = UIStackView() + + override init(frame: CGRect) { + super.init(frame: frame) + + setUpSelf() + setUpConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpSelf() { + insetsLayoutMarginsFromSafeArea = false + layoutMargins = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16) + + addSubview(scrollView) + setUpScrollView() + } + + private func setUpScrollView() { + scrollView.showsHorizontalScrollIndicator = false + scrollView.showsVerticalScrollIndicator = false + + scrollView.addSubview(stackView) + setUpStackView() + } + + private func setUpStackView() { + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = 12 + } + + private func setUpConstraints() { + scrollView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + stackView.snp.makeConstraints { make in + make.edges.equalTo(scrollView.contentLayoutGuide) + make.height.equalTo(layoutMarginsGuide) + } + } + + func addTitleView(_ title: String) { + let label = UILabel() +// label.font = .preferredFont(for: .footnote, weight: .semibold) + label.font = .preferredFont(forTextStyle: .footnote) + label.text = title + + addViewAnimated(label) + } + + func addMemberView(name: String) { + let label = UILabel() +// label.font = .preferredFont(for: .footnote, weight: .semibold) + label.font = .preferredFont(forTextStyle: .footnote) + label.text = name + + let container = ContainerView(pillContent: label) + container.layoutMargins = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 10) + container.backgroundColor = Colors.carouselGray + + addViewAnimated(container) + } + + func addSeparator() { + let imageView = UIImageView() + imageView.image = UIImage(named: "separatorStar")?.withRenderingMode(.alwaysTemplate) + imageView.tintColor = Colors.carouselGray + imageView.snp.makeConstraints { make in + make.width.height.equalTo(8) + } + + addViewAnimated(imageView) + } + + private func addViewAnimated(_ view: UIView) { + view.alpha = 0.0 + stackView.addArrangedSubview(view) + UIView.animate( + withDuration: 0.4, + animations: { view.alpha = 1.0 } + ) + } + + override func layoutMarginsDidChange() { + super.layoutMarginsDidChange() + + scrollView.contentInset = layoutMargins + } + +} diff --git a/TCAT/Views/SettingsPrivacyView.swift b/TCAT/Views/SettingsPrivacyView.swift new file mode 100644 index 00000000..c74f81aa --- /dev/null +++ b/TCAT/Views/SettingsPrivacyView.swift @@ -0,0 +1,159 @@ +// +// SettingsPrivacyView.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import SwiftUI + +class SettingsPrivacyViewModel: ObservableObject { + + @Published var isLocationAllowed: Bool = false + @Published var isAnalyticsEnabled: Bool = true + @Published var isNotificationsAllowed: Bool = false + +} + +struct SettingsPrivacyView: View { + + @ObservedObject var viewModel = SettingsPrivacyViewModel() + + var body: some View { + List { + // Intro section + Text("Manage permissions and analytics") + .foregroundColor(.gray) + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + .listRowSeparator(.hidden) + + // Custom header for Permissions + Text("Permissions") + .font(Font(UIFont.preferredFont(forTextStyle: .title2))) + .foregroundColor(.black) + .padding(.top, 12) + .listRowSeparator(.hidden) + + // Permissions item + // Location + Button { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + + UIApplication.shared.open(url, options: [:], completionHandler: nil) + + } label: { + HStack { + VStack(alignment: .leading, spacing: 4) { + Spacer(minLength: 12) + Text("Location Access") + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + .fontWeight(.semibold) + .foregroundColor(.black) + Text("Used to find routes near you") + .font(Font(UIFont.preferredFont(forTextStyle: .caption1))) + .fontWeight(.semibold) + .foregroundColor(.gray) + Spacer(minLength: 12) + } + Spacer() + HStack(spacing: 2) { + Text(viewModel.isLocationAllowed ? "Allowed" : "Denied") + .font(Font(UIFont.preferredFont(forTextStyle: .footnote))) + .fontWeight(.semibold) + Image("externalLink") + .resizable() + .renderingMode(.template) + .frame(width: 16, height: 16) + } + .foregroundColor(viewModel.isLocationAllowed ? Color(Colors.tcatBlue) : .gray) + } + } + .listRowSeparator(.visible, edges: .bottom) + + // Notifications + Button { + guard let url = URL(string: UIApplication.openSettingsURLString) else { + return + } + + UIApplication.shared.open(url, options: [:], completionHandler: nil) + + } label: { + HStack { + VStack(alignment: .leading, spacing: 4) { + Spacer(minLength: 12) + Text("Notifications Access") + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + .fontWeight(.semibold) + .foregroundColor(.black) + Text("Used to send device notifications") + .font(Font(UIFont.preferredFont(forTextStyle: .caption1))) + .fontWeight(.semibold) + .foregroundColor(.gray) + Spacer(minLength: 12) + } + Spacer() + HStack(spacing: 2) { + Text(viewModel.isNotificationsAllowed ? "Allowed" : "Denied") + .font(Font(UIFont.preferredFont(forTextStyle: .footnote))) + .fontWeight(.semibold) + Image("externalLink") + .resizable() + .renderingMode(.template) + .frame(width: 16, height: 16) + } + .foregroundColor(viewModel.isNotificationsAllowed ? Color(Colors.tcatBlue) : .gray) + } + } + .listRowSeparator(.hidden) + + // Custom header for Analytics + Text("Analytics") + .font(Font(UIFont.preferredFont(forTextStyle: .title2))) + .foregroundColor(.black) + .padding(.top, 12) + .listRowSeparator(.hidden) + + // Analytics toggle item + HStack { + VStack(alignment: .leading, spacing: 4) { + Spacer(minLength: 0) + Text("Share with Cornell AppDev") + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + .fontWeight(.semibold) + .foregroundColor(.black) + Text("Help us improve our products and services") + .font(Font(UIFont.preferredFont(forTextStyle: .caption1))) + .fontWeight(.semibold) + .foregroundColor(.gray) + Spacer(minLength: 0) + } + Spacer(minLength: 0) + Toggle("Analytics Enabled", isOn: $viewModel.isAnalyticsEnabled) + .labelsHidden() + .tint(Color(Colors.tcatBlue)) + } + + // Privacy policy link + Link(destination: URL(string: "https://www.cornellappdev.com/privacy")!) { + HStack { + Text("Privacy Policy") + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + .fontWeight(.semibold) + .foregroundColor(.black) + Spacer() + Image("externalLink") + .resizable() + .renderingMode(.template) + .foregroundColor(Color(Colors.tcatBlue)) + .frame(width: 16, height: 16) + } + } + .listRowSeparator(.hidden, edges: .bottom) + } + .listStyle(.plain) + } +} diff --git a/TCAT/Views/SettingsSupportView.swift b/TCAT/Views/SettingsSupportView.swift new file mode 100644 index 00000000..a4f2a6f3 --- /dev/null +++ b/TCAT/Views/SettingsSupportView.swift @@ -0,0 +1,69 @@ +// +// SettingsSupportView.swift +// Eatery Blue +// +// Created by William Ma on 1/26/22. +// + +import SwiftUI + +struct SettingsSupportView: View { + + var body: some View { + List { + Text("Report issues and contact Cornell AppDev") + .foregroundColor(.gray) + .listRowSeparator(.hidden) + + SwiftUI.Section { + sectionHeader(title: "Make Transit Better") + Text("Help us improve Transit by letting us know what’s wrong.") + .foregroundColor(.gray) + + Button { + guard let url = URL(string: "mailto:team@cornellappdev.com") else { + return + } + UIApplication.shared.open(url) + } label: { + HStack(spacing: 6) { + Spacer() + Image("report") + .renderingMode(.template) + .resizable() + .frame(width: 24, height: 24) + Text("Shoot us an email") + .padding(EdgeInsets(top: 14, leading: 0, bottom: 14, trailing: 0)) + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + Spacer() + } + } + .foregroundColor(.white) + .background(Color(Colors.tcatBlue)) + .clipShape(Capsule()) + } + .listRowSeparator(.hidden) + + SwiftUI.Section { + // TODO: Add FAQs + // sectionHeader(title: "Frequently Asked Questions") + } + } + .listStyle(.plain) + } + + private func sectionHeader(title: String) -> some View { + Text(title) + .font(Font(generateFont(for: .title2, weight: .semibold))) + .foregroundColor(Color(Colors.black)) + .padding(EdgeInsets(top: 12, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) + } + + private func generateFont(for style: UIFont.TextStyle, weight: UIFont.Weight) -> UIFont { + let metrics = UIFontMetrics(forTextStyle: style) + let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) + let font = UIFont.systemFont(ofSize: descriptor.pointSize, weight: weight) + return metrics.scaledFont(for: font) + } +}