From f5bebc41884f36f2c0e404753b0d476f1bbcf4a1 Mon Sep 17 00:00:00 2001 From: ako28 Date: Wed, 5 Mar 2025 17:28:04 -0500 Subject: [PATCH 1/8] new branch --- Podfile.lock | 2 +- TCAT.xcodeproj/project.pbxproj | 274 +++++++++++++++++- TCAT/Supporting/TransitEnvironment.swift | 2 +- .../Cells/NewInformationTableViewCell.swift | 24 ++ .../NewInformationViewController.swift | 125 ++++++++ 5 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift create mode 100644 TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift 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..6ef5a678 100644 --- a/TCAT.xcodeproj/project.pbxproj +++ b/TCAT.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1C0296AA2D77D9F5005B92FC /* NewInformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */; }; + 1C0296B62D77E578005B92FC /* NewInformationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.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 +148,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewInformationViewController.swift; sourceTree = ""; }; + 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewInformationTableViewCell.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 = ""; }; @@ -312,6 +316,271 @@ name = Frameworks; sourceTree = ""; }; + 1C0296AF2D77E28D005B92FC /* UI */ = { + isa = PBXGroup; + children = ( + 1C0296B72D77E59C005B92FC /* Template */, + 1C0296B02D77E2A6005B92FC /* InformationMainView */, + 1C0296C62D77E6A1005B92FC /* InformationAbout */, + 1C0296BE2D77E63B005B92FC /* InformationAppIcon */, + 1C0296C22D77E65D005B92FC /* InformationPrivacy */, + 1C0296CA2D77E6CB005B92FC /* InformationSupport */, + 1C0296CE2D77E6ED005B92FC /* InformationShowOnboard */, + 1C0296D22D77E822005B92FC /* InformationService */, + ); + path = UI; + sourceTree = ""; + }; + 1C0296B02D77E2A6005B92FC /* InformationMainView */ = { + isa = PBXGroup; + children = ( + 1C0296B32D77E2CB005B92FC /* Views */, + 1C0296B22D77E2C6005B92FC /* Controllers */, + 1C0296B12D77E2C1005B92FC /* Cells */, + ); + path = InformationMainView; + sourceTree = ""; + }; + 1C0296B12D77E2C1005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 1C0296B22D77E2C6005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */, + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296B32D77E2CB005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296B72D77E59C005B92FC /* Template */ = { + isa = PBXGroup; + children = ( + 1C0296B82D77E5A2005B92FC /* Views */, + 1C0296B92D77E5A7005B92FC /* Controllers */, + 1C0296BA2D77E5AD005B92FC /* Cells */, + ); + path = Template; + sourceTree = ""; + }; + 1C0296B82D77E5A2005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296B92D77E5A7005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296BA2D77E5AD005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296BB2D77E63B005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296BC2D77E63B005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296BD2D77E63B005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296BE2D77E63B005B92FC /* InformationAppIcon */ = { + isa = PBXGroup; + children = ( + 1C0296BD2D77E63B005B92FC /* Views */, + 1C0296BC2D77E63B005B92FC /* Controllers */, + 1C0296BB2D77E63B005B92FC /* Cells */, + ); + path = InformationAppIcon; + sourceTree = ""; + }; + 1C0296BF2D77E65D005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296C02D77E65D005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296C12D77E65D005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296C22D77E65D005B92FC /* InformationPrivacy */ = { + isa = PBXGroup; + children = ( + 1C0296BF2D77E65D005B92FC /* Cells */, + 1C0296C02D77E65D005B92FC /* Controllers */, + 1C0296C12D77E65D005B92FC /* Views */, + ); + path = InformationPrivacy; + sourceTree = ""; + }; + 1C0296C32D77E6A1005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296C42D77E6A1005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296C52D77E6A1005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296C62D77E6A1005B92FC /* InformationAbout */ = { + isa = PBXGroup; + children = ( + 1C0296C52D77E6A1005B92FC /* Views */, + 1C0296C42D77E6A1005B92FC /* Controllers */, + 1C0296C32D77E6A1005B92FC /* Cells */, + ); + path = InformationAbout; + sourceTree = ""; + }; + 1C0296C72D77E6CB005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296C82D77E6CB005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296C92D77E6CB005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296CA2D77E6CB005B92FC /* InformationSupport */ = { + isa = PBXGroup; + children = ( + 1C0296C72D77E6CB005B92FC /* Cells */, + 1C0296C82D77E6CB005B92FC /* Controllers */, + 1C0296C92D77E6CB005B92FC /* Views */, + ); + path = InformationSupport; + sourceTree = ""; + }; + 1C0296CB2D77E6ED005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296CC2D77E6ED005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296CD2D77E6ED005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296CE2D77E6ED005B92FC /* InformationShowOnboard */ = { + isa = PBXGroup; + children = ( + 1C0296CB2D77E6ED005B92FC /* Cells */, + 1C0296CC2D77E6ED005B92FC /* Controllers */, + 1C0296CD2D77E6ED005B92FC /* Views */, + ); + path = InformationShowOnboard; + sourceTree = ""; + }; + 1C0296CF2D77E822005B92FC /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; + 1C0296D02D77E822005B92FC /* Controllers */ = { + isa = PBXGroup; + children = ( + ); + path = Controllers; + sourceTree = ""; + }; + 1C0296D12D77E822005B92FC /* Cells */ = { + isa = PBXGroup; + children = ( + ); + path = Cells; + sourceTree = ""; + }; + 1C0296D22D77E822005B92FC /* InformationService */ = { + isa = PBXGroup; + children = ( + 1C0296CF2D77E822005B92FC /* Views */, + 1C0296D02D77E822005B92FC /* Controllers */, + 1C0296D12D77E822005B92FC /* Cells */, + ); + path = InformationService; + sourceTree = ""; + }; 2292486621B891790004279C /* Network */ = { isa = PBXGroup; children = ( @@ -591,14 +860,15 @@ 449A7C7F1D80D0E80019300C /* Assets.xcassets */, 2E70434D2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy */, 2E9416662BC615B0003DEB44 /* Base */, + 1C0296AF2D77E28D005B92FC /* UI */, 2E94166C2BC61604003DEB44 /* Cells */, + 2E9416FD2BC61CAE003DEB44 /* Views */, 2E9416822BC6168C003DEB44 /* Controllers */, 2E94165E2BC60A3B003DEB44 /* Ecosystem */, 2E9416AB2BC616DE003DEB44 /* Models */, FDE68D292C988CDB00024A69 /* Services */, 2E9416C72BC61763003DEB44 /* Supporting */, 2E9416E02BC618E6003DEB44 /* Utils */, - 2E9416FD2BC61CAE003DEB44 /* Views */, ); path = TCAT; sourceTree = ""; @@ -842,6 +1112,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1C0296AA2D77D9F5005B92FC /* NewInformationViewController.swift in Sources */, 2E9416982BC616B9003DEB44 /* RouteDetail+DrawerViewController.swift in Sources */, 2E94169D2BC616B9003DEB44 /* FavoritesTableViewController.swift in Sources */, 2E9416692BC615DF003DEB44 /* AppDelegate.swift in Sources */, @@ -855,6 +1126,7 @@ FDE68D1E2C97E24900024A69 /* NetworkManager.swift in Sources */, 2E9417162BC61CF1003DEB44 /* SearchBarView.swift in Sources */, 2E9FFA882BC673240051793C /* Amenity.graphql.swift in Sources */, + 1C0296B62D77E578005B92FC /* NewInformationTableViewCell.swift in Sources */, 2E9FFA852BC673240051793C /* AmenityType.graphql.swift in Sources */, 2E9416F02BC61984003DEB44 /* Extensions+Shared.swift in Sources */, 2E9FFA8F2BC673240051793C /* SchemaMetadata.graphql.swift in Sources */, diff --git a/TCAT/Supporting/TransitEnvironment.swift b/TCAT/Supporting/TransitEnvironment.swift index 468a3c19..2d7724c4 100644 --- a/TCAT/Supporting/TransitEnvironment.swift +++ b/TCAT/Supporting/TransitEnvironment.swift @@ -16,7 +16,7 @@ enum TransitEnvironment { #if DEBUG static let eateryURL = "EATERY_DEV_URL" static let googleMaps = "GOOGLE_MAPS_DEBUG" - static let transitURL = "TRANSIT_DEV_URL" + static let transitURL = "TRANSIT_PROD_URL" static let upliftURL = "UPLIFT_DEV_URL" #else static let eateryURL = "EATERY_PROD_URL" diff --git a/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift b/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift new file mode 100644 index 00000000..4ad9c694 --- /dev/null +++ b/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift @@ -0,0 +1,24 @@ +// +// NewInformationTableViewCell.swift +// TCAT +// +// Created by Asen Ou on 3/4/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class NewInformationTableViewCell: UITableViewCell { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift b/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift new file mode 100644 index 00000000..554ae992 --- /dev/null +++ b/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift @@ -0,0 +1,125 @@ +// +// NewInformationViewController.swift +// TCAT +// +// Created by Asen Ou on 3/4/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class NewInformationViewController: UIViewController { + + // Main View Properties + private let tableView = UITableView() + + // Table View Properties + struct RowItem { + let image: UIImage? + let title: String + let subtitle: String + let action: () -> Void + } + + private var rows: [RowItem] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + // Track Analytics + let payload = AboutPageOpenedPayload() + TransitAnalytics.shared.log(payload) + + // Populate row items + setUpRowItems() + + // Set up main view + setUpMainView() + setUpNavigationItem() + + // Set up subviews + setUpTableView() + } + private func setUpRowItems() { + rows = [ + RowItem( + image: <#T##UIImage?#>, + title: "About Transit", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + RowItem( + image: <#T##UIImage?#>, + title: "App Icon", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + RowItem( + image: <#T##UIImage?#>, + title: "Privacy", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + RowItem( + image: <#T##UIImage?#>, + title: "Support", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + RowItem( + image: <#T##UIImage?#>, + title: "Show Onboarding", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + RowItem( + image: <#T##UIImage?#>, + title: "TCAT Service Alerts", + subtitle: <#T##String#>, + action: <#T##() -> Void#>), + + ] + } + + // MARK: - Main view init + private func setUpMainView() { + // Initialize view defaults + title = Constants.Titles.aboutUs + view.backgroundColor = Colors.backgroundWash + navigationController?.navigationBar.tintColor = Colors.primaryText + } + + private func setUpNavigationItem() { + + } +} + +// MARK: - TableView init +extension NewInformationViewController: UITableViewDataSource, UITableViewDelegate, InfoHeaderViewDelegate { + // 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) + } + + private func setUpTableView() { + tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.Cells.informationCellIdentifier) + tableView.dataSource = self + tableView.delegate = self + tableView.backgroundColor = Colors.backgroundWash + tableView.separatorColor = Colors.dividerTextField + tableView.showsVerticalScrollIndicator = false + + let headerView = InformationTableHeaderView() + headerView.delegate = self + tableView.tableHeaderView = headerView + + view.addSubview(tableView) + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rows.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + <#code#> + } +} From 3daeb2af9424cdb63b2f542f936f33c655c14198 Mon Sep 17 00:00:00 2001 From: ako28 Date: Fri, 21 Mar 2025 13:49:54 -0400 Subject: [PATCH 2/8] Basic layout + completed 5 rows --- TCAT.xcodeproj/project.pbxproj | 300 ++++-------------- .../Settings Assets/Contents.json | 6 + .../Globe.imageset/Contents.json | 12 + .../Settings Assets/Globe.imageset/Globe.pdf | Bin 0 -> 2612 bytes .../Report.imageset/Contents.json | 12 + .../Report.imageset/Report.pdf | Bin 0 -> 3139 bytes .../appDevLogo.imageset/AppDevLogo.pdf | Bin 0 -> 3208 bytes .../appDevLogo.imageset/Contents.json | 12 + .../externalLink.imageset/Contents.json | 21 ++ .../externalLink.imageset/External Link.png | Bin 0 -> 255 bytes .../favStar.imageset/Contents.json | 21 ++ .../favStar.imageset/Vector-2.png | Bin 0 -> 1439 bytes .../lightBulb.imageset/Contents.json | 21 ++ .../lightBulb.imageset/Vector.png | Bin 0 -> 1035 bytes .../lock.imageset/Contents.json | 21 ++ .../lock.imageset/Vector-4.png | Bin 0 -> 898 bytes .../qMark.imageset/Contents.json | 21 ++ .../qMark.imageset/Vector-5.png | Bin 0 -> 1603 bytes .../separatorStar.imageset/Contents.json | 21 ++ .../separatorStar.imageset/Vector-6.png | Bin 0 -> 388 bytes .../settingsBus.imageset/Contents.json | 21 ++ .../settingsBus.imageset/Vector-3.png | Bin 0 -> 886 bytes .../tableSeparator.imageset/Contents.json | 21 ++ .../tableSeparator.imageset/Vector 1.png | Bin 0 -> 141 bytes .../HomeOptionsCardViewController.swift | 7 +- .../ServiceAlertsViewController.swift | 3 + TCAT/InformationAbout/In.swift | 8 + .../InformationAboutViewController.swift | 48 +++ .../SettingsAboutHeaderView.swift | 86 +++++ .../SettingsAboutMembersCarouselView.swift | 99 ++++++ .../SettingsAboutViewController.swift | 264 +++++++++++++++ ...foAppIconSheetPresentationController.swift | 13 + .../SettingsAppIconCollectionViewCell.swift | 60 ++++ .../SettingsAppIconViewController.swift | 104 ++++++ .../SettingsTableViewCell.swift | 108 +++++++ .../SettingsViewController.swift | 226 +++++++++++++ .../SettingsPrivacyView.swift | 158 +++++++++ .../SettingsPrivacyViewController.swift | 114 +++++++ TCAT/InformationPrivacy/Untitled.swift | 0 .../SettingsSupportView.swift | 215 +++++++++++++ .../SettingsSupportViewController.swift | 96 ++++++ .../SettingsFaveViewController.swift | 49 +++ TCAT/Supporting/Constants.swift | 6 + .../Cells/NewInformationTableViewCell.swift | 24 -- .../NewInformationViewController.swift | 125 -------- TCAT/Utils/Styles.swift | 1 + TCAT/Views/ButtonView.swift | 58 ++++ TCAT/Views/ContainerView.swift | 116 +++++++ TCAT/Views/PillButtonView.swift | 79 +++++ 49 files changed, 2193 insertions(+), 384 deletions(-) create mode 100644 TCAT/Assets.xcassets/Settings Assets/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/Globe.imageset/Globe.pdf create mode 100644 TCAT/Assets.xcassets/Settings Assets/Report.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/Report.imageset/Report.pdf create mode 100644 TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/AppDevLogo.pdf create mode 100644 TCAT/Assets.xcassets/Settings Assets/appDevLogo.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/externalLink.imageset/External Link.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/favStar.imageset/Vector-2.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/lightBulb.imageset/Vector.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/lock.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/lock.imageset/Vector-4.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/qMark.imageset/Vector-5.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/separatorStar.imageset/Vector-6.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/settingsBus.imageset/Vector-3.png create mode 100644 TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json create mode 100644 TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Vector 1.png create mode 100644 TCAT/InformationAbout/In.swift create mode 100644 TCAT/InformationAbout/InformationAboutViewController.swift create mode 100644 TCAT/InformationAbout/SettingsAboutHeaderView.swift create mode 100644 TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift create mode 100644 TCAT/InformationAbout/SettingsAboutViewController.swift create mode 100644 TCAT/InformationAppIcon/Controllers/InfoAppIconSheetPresentationController.swift create mode 100644 TCAT/InformationAppIcon/SettingsAppIconCollectionViewCell.swift create mode 100644 TCAT/InformationAppIcon/SettingsAppIconViewController.swift create mode 100644 TCAT/InformationMainView/SettingsTableViewCell.swift create mode 100644 TCAT/InformationMainView/SettingsViewController.swift create mode 100644 TCAT/InformationPrivacy/SettingsPrivacyView.swift create mode 100644 TCAT/InformationPrivacy/SettingsPrivacyViewController.swift create mode 100644 TCAT/InformationPrivacy/Untitled.swift create mode 100644 TCAT/InformationSupport/SettingsSupportView.swift create mode 100644 TCAT/InformationSupport/SettingsSupportViewController.swift create mode 100644 TCAT/SettingsFave/SettingsFaveViewController.swift delete mode 100644 TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift delete mode 100644 TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift create mode 100644 TCAT/Views/ButtonView.swift create mode 100644 TCAT/Views/ContainerView.swift create mode 100644 TCAT/Views/PillButtonView.swift diff --git a/TCAT.xcodeproj/project.pbxproj b/TCAT.xcodeproj/project.pbxproj index 6ef5a678..a5f1d6ef 100644 --- a/TCAT.xcodeproj/project.pbxproj +++ b/TCAT.xcodeproj/project.pbxproj @@ -7,8 +7,21 @@ objects = { /* Begin PBXBuildFile section */ - 1C0296AA2D77D9F5005B92FC /* NewInformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */; }; - 1C0296B62D77E578005B92FC /* NewInformationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.swift */; }; + 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 */; }; @@ -148,8 +161,21 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewInformationViewController.swift; sourceTree = ""; }; - 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewInformationTableViewCell.swift; sourceTree = ""; }; + 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 = ""; }; @@ -316,269 +342,58 @@ name = Frameworks; sourceTree = ""; }; - 1C0296AF2D77E28D005B92FC /* UI */ = { - isa = PBXGroup; - children = ( - 1C0296B72D77E59C005B92FC /* Template */, - 1C0296B02D77E2A6005B92FC /* InformationMainView */, - 1C0296C62D77E6A1005B92FC /* InformationAbout */, - 1C0296BE2D77E63B005B92FC /* InformationAppIcon */, - 1C0296C22D77E65D005B92FC /* InformationPrivacy */, - 1C0296CA2D77E6CB005B92FC /* InformationSupport */, - 1C0296CE2D77E6ED005B92FC /* InformationShowOnboard */, - 1C0296D22D77E822005B92FC /* InformationService */, - ); - path = UI; - sourceTree = ""; - }; 1C0296B02D77E2A6005B92FC /* InformationMainView */ = { isa = PBXGroup; children = ( - 1C0296B32D77E2CB005B92FC /* Views */, - 1C0296B22D77E2C6005B92FC /* Controllers */, - 1C0296B12D77E2C1005B92FC /* Cells */, + 1C0296A92D77D9F5005B92FC /* SettingsViewController.swift */, + 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */, ); path = InformationMainView; sourceTree = ""; }; - 1C0296B12D77E2C1005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - 1C0296B52D77E578005B92FC /* NewInformationTableViewCell.swift */, - ); - path = Cells; - sourceTree = ""; - }; - 1C0296B22D77E2C6005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - 1C0296A92D77D9F5005B92FC /* NewInformationViewController.swift */, - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296B32D77E2CB005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; - 1C0296B72D77E59C005B92FC /* Template */ = { - isa = PBXGroup; - children = ( - 1C0296B82D77E5A2005B92FC /* Views */, - 1C0296B92D77E5A7005B92FC /* Controllers */, - 1C0296BA2D77E5AD005B92FC /* Cells */, - ); - path = Template; - sourceTree = ""; - }; - 1C0296B82D77E5A2005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; - 1C0296B92D77E5A7005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296BA2D77E5AD005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296BB2D77E63B005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296BC2D77E63B005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296BD2D77E63B005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; 1C0296BE2D77E63B005B92FC /* InformationAppIcon */ = { isa = PBXGroup; children = ( - 1C0296BD2D77E63B005B92FC /* Views */, - 1C0296BC2D77E63B005B92FC /* Controllers */, - 1C0296BB2D77E63B005B92FC /* Cells */, + 1C0296D72D7FE055005B92FC /* SettingsAppIconViewController.swift */, + 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */, ); path = InformationAppIcon; sourceTree = ""; }; - 1C0296BF2D77E65D005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296C02D77E65D005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296C12D77E65D005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; 1C0296C22D77E65D005B92FC /* InformationPrivacy */ = { isa = PBXGroup; children = ( - 1C0296BF2D77E65D005B92FC /* Cells */, - 1C0296C02D77E65D005B92FC /* Controllers */, - 1C0296C12D77E65D005B92FC /* Views */, + 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, + 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */, ); path = InformationPrivacy; sourceTree = ""; }; - 1C0296C32D77E6A1005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296C42D77E6A1005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296C52D77E6A1005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; 1C0296C62D77E6A1005B92FC /* InformationAbout */ = { isa = PBXGroup; children = ( - 1C0296C52D77E6A1005B92FC /* Views */, - 1C0296C42D77E6A1005B92FC /* Controllers */, - 1C0296C32D77E6A1005B92FC /* Cells */, + 1C0296E82D87A3B4005B92FC /* SettingsAboutViewController.swift */, + 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */, + 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */, ); path = InformationAbout; sourceTree = ""; }; - 1C0296C72D77E6CB005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296C82D77E6CB005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296C92D77E6CB005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; 1C0296CA2D77E6CB005B92FC /* InformationSupport */ = { isa = PBXGroup; children = ( - 1C0296C72D77E6CB005B92FC /* Cells */, - 1C0296C82D77E6CB005B92FC /* Controllers */, - 1C0296C92D77E6CB005B92FC /* Views */, + 1C591B4E2D8D22BD00DDA71D /* SettingsSupportViewController.swift */, + 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */, ); path = InformationSupport; sourceTree = ""; }; - 1C0296CB2D77E6ED005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296CC2D77E6ED005B92FC /* Controllers */ = { - isa = PBXGroup; - children = ( - ); - path = Controllers; - sourceTree = ""; - }; - 1C0296CD2D77E6ED005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; - 1C0296CE2D77E6ED005B92FC /* InformationShowOnboard */ = { - isa = PBXGroup; - children = ( - 1C0296CB2D77E6ED005B92FC /* Cells */, - 1C0296CC2D77E6ED005B92FC /* Controllers */, - 1C0296CD2D77E6ED005B92FC /* Views */, - ); - path = InformationShowOnboard; - sourceTree = ""; - }; - 1C0296CF2D77E822005B92FC /* Views */ = { - isa = PBXGroup; - children = ( - ); - path = Views; - sourceTree = ""; - }; - 1C0296D02D77E822005B92FC /* Controllers */ = { + 1C0296DF2D82408F005B92FC /* SettingsFave */ = { isa = PBXGroup; children = ( + 1C0296E02D8240A7005B92FC /* SettingsFaveViewController.swift */, ); - path = Controllers; - sourceTree = ""; - }; - 1C0296D12D77E822005B92FC /* Cells */ = { - isa = PBXGroup; - children = ( - ); - path = Cells; - sourceTree = ""; - }; - 1C0296D22D77E822005B92FC /* InformationService */ = { - isa = PBXGroup; - children = ( - 1C0296CF2D77E822005B92FC /* Views */, - 1C0296D02D77E822005B92FC /* Controllers */, - 1C0296D12D77E822005B92FC /* Cells */, - ); - path = InformationService; + path = SettingsFave; sourceTree = ""; }; 2292486621B891790004279C /* Network */ = { @@ -746,6 +561,9 @@ 2E9417022BC61CF1003DEB44 /* SearchBarView.swift */, 2E9417062BC61CF1003DEB44 /* SummaryView.swift */, 2E94170C2BC61CF1003DEB44 /* WalkWithDistanceIcon.swift */, + 1C0296F32D8B609F005B92FC /* ButtonView.swift */, + 1C0296F12D8B603F005B92FC /* PillButtonView.swift */, + 1C0296EF2D8B5CF4005B92FC /* ContainerView.swift */, ); path = Views; sourceTree = ""; @@ -860,10 +678,15 @@ 449A7C7F1D80D0E80019300C /* Assets.xcassets */, 2E70434D2BB75E10003AC1D6 /* PrivacyInfo.xcprivacy */, 2E9416662BC615B0003DEB44 /* Base */, - 1C0296AF2D77E28D005B92FC /* UI */, 2E94166C2BC61604003DEB44 /* Cells */, 2E9416FD2BC61CAE003DEB44 /* Views */, 2E9416822BC6168C003DEB44 /* Controllers */, + 1C0296B02D77E2A6005B92FC /* InformationMainView */, + 1C0296C62D77E6A1005B92FC /* InformationAbout */, + 1C0296BE2D77E63B005B92FC /* InformationAppIcon */, + 1C0296C22D77E65D005B92FC /* InformationPrivacy */, + 1C0296CA2D77E6CB005B92FC /* InformationSupport */, + 1C0296DF2D82408F005B92FC /* SettingsFave */, 2E94165E2BC60A3B003DEB44 /* Ecosystem */, 2E9416AB2BC616DE003DEB44 /* Models */, FDE68D292C988CDB00024A69 /* Services */, @@ -1112,8 +935,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1C0296AA2D77D9F5005B92FC /* NewInformationViewController.swift in Sources */, + 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 */, @@ -1126,7 +951,7 @@ FDE68D1E2C97E24900024A69 /* NetworkManager.swift in Sources */, 2E9417162BC61CF1003DEB44 /* SearchBarView.swift in Sources */, 2E9FFA882BC673240051793C /* Amenity.graphql.swift in Sources */, - 1C0296B62D77E578005B92FC /* NewInformationTableViewCell.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 */, @@ -1138,6 +963,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 */, @@ -1145,6 +971,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 */, @@ -1156,24 +983,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 */, @@ -1187,6 +1020,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 */, @@ -1203,7 +1037,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/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 0000000000000000000000000000000000000000..e0a4ea7169e987e4d666bddfe758f67185812ca0 GIT binary patch literal 2612 zcmai0O^@3)5WVwP%w>V(5SgE%2m}F|-KHqoqFbl8pa)m0Y+T4vYdJ;QU*AW4%y2gb zs)Hdvd*;1291eMNfA{W{siIPbG>yOip@?3;rZ;a?H$3F8%9i-<$8LDMd?E*MpZ_&o z&fVdpc6Z(1$F|#l_m*zom*3h+{iRH$=ZEz6@`~U1H@p4DKeZ>NE9Zkw4I*8veT2x$ z8x!k5Q#&Z*w;;per;MhQV&ln}D#c`?rCymZ{}=BWO-_eE1(xIMvFsmEePP#gNE0R!_-d-cg+$LQk7c_!Ac?TIFiaOMe97k}K z8G=a$c~ww1Sd76q)NKp4{1&QYeN#6ikgNm}UQswJXCh`lDOg|RGR8FGWRp;`pnuo? z60}!>vPie7l>ipPP+106GK>D21--^#z0}we`zUI{G=h~pwjg4W0*V#WY>QD~-1bT)(-3AHj+NEi=}umrKLtu}chV?S$?@*MHr#|B$%Cbgz6 zW)FN_nJL&=Kuz>H3N0Fq@rZJj*Uo5+iB!c9bpoTVqH8b+$=a%p-dl%>bVcU8)FJ@O zvJ4-{XQ@pULa+u|*-@yhb1@~7ltLrqTn#8^mA;9+) z^^3{}{CawCM}0okmn(olJMs;{GmM+T?wes4FB5(E4L1cmyI=qLv!mVp;jvqRpSp+R z;qCZ^KFE2``_J-;EpJ<{<7i^vAvAI`uitWa@X$q-*- zc0U{+AeFL%J#9R<<7dR0uk=qqicIyBOxfVXgkt>ma5?nje(sOY=NH}rZsV%y zeCl@xwt49P-FN-=hxh#cWBp$@v45Cs#QboPU*6Cg{if>Qv~w!(?!ZLTY9oS&Y3rr!D0ssz?>-c@Wld5g-*4lM2pTSVp{Y2mCf7lJAaiJ6r%y1Kb^a%)QVL$R;e z3ocSF5~YMt@HIbN5`_bzicU+siD~IgSli>C!CUf6Y7vcS6)bvNmfnPQ4T^nDV``W8 zAMFo2v$P_$GzG;6w4%%QG@wx=rk9pwvF_U8yjR!q>zrKE2H_ogpKGkK=t!k!m5Ne1 za3rYADD84~PZr;!BQdHC~sR~ctKP&_`B9O%cd}KLB6I%WTC|Yesuz02lPR5 zT58_062H))rXC-aAWh3lKG7!K5K>fFU5=t?bwx_3`i_uNg>PglHR?}k(vdBwyM2yj zqgrpUc|}qAfFg&KO~vYs%qO@gjKXhJ?oh$%cHU~cI0|$#YQHzU&Q(JHS*{WFHB<@k zs_7J1`V)eY{cTEK*GGz~Fb6Y>0)n&K1nD>f(*ECsl{q>I?(BuvL;1K@OX-7O`y}I= zu(Ez(pV^W9#^St}hPgcX#UXE*2$c?=20~33gW~49<8eGs{L>#89Pw;^|L^afZytBg zeFDGqPy5~b@hks?At~(?y(q|Ak}Pl8)3%?+FQ=}bcpOc)$=&^UJoiULjB{y*kGm5X z$6z+2YI==`rrOO<`zJ`{F@r77aqh=2u$5Q(Cy>gMU3rQLP86^te!n~KhVglA`t{U* zW`aAmnoz8PQDoDrf?!N!^!#A+M Bdbj`p literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5e71b364108520f3e4433981fe625184fbe9f292 GIT binary patch literal 3208 zcmbW4+in{-5QgvfDR>hgDIkg*o`ygWps|~xXp6c^?}A>a^2SkNORc07X`a6SP`f03 zD0+|qL;jYWotTJl<%ddk{FJG!xuZD4VEq@Nw^W}Hr?&kDCIlz^y zI-U;W`fgZWj(==7f&wx*XB6B&5HUlDJ_T<%+tFjABOkCZus#(Yg(G(B3h*WS=5$w_Qsl^7SlEx z6=dBo>BI@=L0W^(25-viee*9LSmE4dNPJ#86ig<;01Ci4$s&oZCqw+01bkNaGmNPw3bk*EdJV3 z2oKvWq}*(Z%&;3dH20NZchEQy9IRSYYCWun5@@s7v*x?eF3e_9DmW$7yf?U!sw8b) zNX%;CbczoBk)rg}Clhes!Cb=9Br6Gr7tIHYF=nLKSSbnmiSf`;D8ptpgz7t8EMj-h zCf}uj^U9<>CpycetxZstiBJo?&wdzlf;|M#MO0adXpX&7tei9NEjBj!F1u%1Yg7~! zFmhz{s|98odB*HQ?ShUOqaA{(RN5Ru%Fq*G@?#=mO7<)T%q*Gi9fgRZd|+^^(> z<0PX4FN@h?W0UW)d!|7rAEd^pyw@|u5yD$1;wU#i$e1a~G-?r5R&36(SK8&+EVjI> zZ|rWg)~Jm4FaFnKRz|P#U^6xkrVu8}$IC9K{n>#>klcEu zel5zSak>%C*GIW2p0AZiHPu_CuNb$@<(IqN{&ZCDe#Y$+pXE<~{x+)R)%s?f!1v?z zcKv$)Nxf^AgC}kY?fpGlT8+p3$HQhkD!JdTCUY12-D%vxqg-xhc(p!&sW74L3M{Bz zjhEkUuMw(}3|4CPbFu%3z4lT3N081EUelc=N$^-5w!|;ir}gdr=Kj`Chw=SjR2t5A zPWi1OZhAohPahoL@=fh3a)?tt6;TZ75#om%33Kb=stkG0woZrj_I5lB(!FmkQI6-w v{oVLNJ>OoxI2UEP+V4+F5-tT^zTJKv5q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..849becd9cfaa1ed1318ea58fe105925382bc8046 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRt!3HF+tk*dLq&N#aB8wRqxP?KOkzv*x37{Zj zage(c!@6@aFM%AEbVpxD28NCO+x%?ofn;I>pHu})v^LReL;DJvH_rfy+a^`Lq zRqT`e@KyIo{vN0Ib2ZLbTTaTjwj!i-DvOk|%)_r*=e`*oVx4#M_sZXM*=H2Yt^2*$ z^%375>jzwGQuYe^+~iqr`jCHF(GzjGn^6px_FJbG{aSqk=spHdS3j3^P6qw5Dz= zWUJI`OWojPD^$(Vin_tc`l^O}ZzM2+l69#HVkegDG1lQ&0wO3`hpH27BnAx230QZ7 z&fQ>St5jWuy1~dUQFX!}CY+Qrgj6>OS>L(Zwz@&cR;ik!ZFL(byF}GfTmClHZJew_ z)eI_*ZTZ_&wlkFCa+*WEg>;aMOVKe$$^HHP7#(sU4M25xr7!%ddETGS9QX`g)x7`5f>7U*LlC0%gA7p=U6Y`O=a}12FXr zigC0fG8cs2NjmB_jn(%S3HdMASP=@Ji+isAi;OA4X;Xxn1l_X;HZU5F@Sd+7*(m7C z4lNdc$Xp>Cen)5g{5=vS=y&dRyPH)+`TiYKATxwr4(k+_GPoz94)*u=-(cgBI+8iv zwsWBtHWg+_)BL;ULV~4aB;N@LmF$=TUjAM^hisT z=vs46r(dz&{SAd*;qR}OEU$KUcI5p0{FAf{bo4RE{N+e?5+y1R0x(P|0;_RCXAH0n z^V<710Yh|POGEq!B$H+3yHWwDJl@{k=Kj_;hvT*selAg>k}R=m!c7n*yvPzBguj4r z;}^^3pz_|9rXfnG+%i~l5(KA?fx50#n~@6j)k|iHi_95U z1vOc7ek?=f<EkNWw8(Ubm!P)D>gx|Q`hPhd8x3nch9objVyT8xX;-^kZ#9NdHD`Ohp4XB? zlJxF_sK&#eEB%q>iwR9xWYsXnaI%qoG#tC9a2q4e*=F=S{+HSw`c$W002ovPDHLkV1h&Wsoeko literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bb33871e9183aeaa644f5d222f188f8cef56c5be GIT binary patch literal 1035 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 B^P>O& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a44ca4c076dd57a119ffc0e8bb2de3bdf77b6d11 GIT binary patch literal 388 zcmV-~0ek+5P)5r00009a7bBm000XU z000XU0RWnu7ytkO0drDELIAGL9O(c600d`2O+f$vv5yPsR(Z0Tr~)u?qZAT5LUWgCyZ*agG2Wt zLYIrqLCGEKno<%%ayaIvPpE)siU>Ip$5d88Azt`-OFdVIn7T8}-x6;&0BwrA|KS>G-m3 zOGokS6bOQiWgP)$TH_1P`F-x-{NJzy8r<761U_vRN2Avy9(~5qLuQ0HmA8dAcM>ka ijG&5t_X15#FTVp_Q*8A!+wZ*q0000EInb-JL&iPio^AKt^Bm@cB_JAEX!K*75rVTR+ppENYgv8 zOa)lfYPD)2dbQi_>IiLIivAsb2@fyeAza`kyzzMKQChLBR?DtbDmM^!E8wN>l6={og(~ViB>m-efYlr!#>W>K+4X5Kb?6a0F?W{lc;$rzK&&9y`jjM(T~48J^g( zzv%6E*yf2E-{^DrXCvozq|ey%ZHwpolt)Nuc|HiK$N)OI1=GMs1w3x5{KNCS-}QPO z2DTb>VaS5%8^?oFmqV9{<469#^{k+$)9FXbm^6Nia(9~-L9t6pUZyQ52V%aUL0&Yy zh&9Z&IHq)$ M07*qoM6N<$f<=axxc~qF literal 0 HcmV?d00001 diff --git a/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json b/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json new file mode 100644 index 00000000..684cc99d --- /dev/null +++ b/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "Vector 1.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/tableSeparator.imageset/Vector 1.png b/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Vector 1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ccfea5380c22eaeb88e6e5a83c8798e608a1010 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0y~yVA>31GjXs1N$+&U>cv7h@-A}f%u8bLO)?$9f(BRWo?H`njxgN@xNAaNQ-7 literal 0 HcmV?d00001 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/ServiceAlertsViewController.swift b/TCAT/Controllers/ServiceAlertsViewController.swift index b20d08b4..5a34ccc7 100644 --- a/TCAT/Controllers/ServiceAlertsViewController.swift +++ b/TCAT/Controllers/ServiceAlertsViewController.swift @@ -43,6 +43,9 @@ class ServiceAlertsViewController: UIViewController { super.viewDidLoad() 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 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/InformationAbout/SettingsAboutHeaderView.swift b/TCAT/InformationAbout/SettingsAboutHeaderView.swift new file mode 100644 index 00000000..b965460d --- /dev/null +++ b/TCAT/InformationAbout/SettingsAboutHeaderView.swift @@ -0,0 +1,86 @@ +// +// 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") +// logoView.tintColor = UIColor + } + + 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/InformationAbout/SettingsAboutMembersCarouselView.swift b/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift new file mode 100644 index 00000000..61ccf700 --- /dev/null +++ b/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift @@ -0,0 +1,99 @@ +// +// 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 + + stackView.addArrangedSubview(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 + + stackView.addArrangedSubview(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) + } + + stackView.addArrangedSubview(imageView) + } + + override func layoutMarginsDidChange() { + super.layoutMarginsDidChange() + + scrollView.contentInset = layoutMargins + } + +} diff --git a/TCAT/InformationAbout/SettingsAboutViewController.swift b/TCAT/InformationAbout/SettingsAboutViewController.swift new file mode 100644 index 00000000..6e171e42 --- /dev/null +++ b/TCAT/InformationAbout/SettingsAboutViewController.swift @@ -0,0 +1,264 @@ +// +// SettingsAboutViewController.swift +// TCAT +// +// Created by Asen Ou on 3/16/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// + +import UIKit + +class SettingsAboutViewController: UIViewController { + + private let subtitleLabel = UILabel() + + private let headerView = SettingsAboutHeaderView() + + private let scrollView = UIScrollView() + private let stackView = UIStackView() + + private let websiteButton = ButtonView(content: PillButtonView()) + + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setUpView() + setUpConstraints() + + print("setting up carousel") + setUpCarouselViews() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + +// RootViewController.setStatusBarStyle(.darkContent) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + scrollView.flashScrollIndicators() + } + + private func setUpNavigationItem() { +// let appearance = UINavigationBarAppearance() +// appearance.titleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.black as Any, +// .font: UIFont.eateryNavigationBarTitleFont +// ] +// appearance.largeTitleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.blue as Any, +// .font: UIFont.eateryNavigationBarLargeTitleFont +// ] + + navigationItem.title = "About Transit" + +// let standardAppearance = appearance.copy() +// standardAppearance.configureWithDefaultBackground() +// navigationItem.standardAppearance = standardAppearance +// +// let scrollEdgeAppearance = appearance.copy() +// scrollEdgeAppearance.configureWithTransparentBackground() +// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance + } + + private func setUpView() { + view.backgroundColor = .white + + view.addSubview(subtitleLabel) + setUpSubtitleLabel() + + view.addSubview(headerView) + setUpHeaderView() + + 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 setUpHeaderView() { + } + + 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) + } + } + + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } + + func addCarouselView(_ configure: (SettingsAboutMembersCarouselView) -> Void) { + let carouselView = SettingsAboutMembersCarouselView() + configure(carouselView) + stackView.addArrangedSubview(carouselView) + } + + 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 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() + } + } + } + } + +} 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/InformationAppIcon/SettingsAppIconCollectionViewCell.swift b/TCAT/InformationAppIcon/SettingsAppIconCollectionViewCell.swift new file mode 100644 index 00000000..d8c95f09 --- /dev/null +++ b/TCAT/InformationAppIcon/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/InformationAppIcon/SettingsAppIconViewController.swift b/TCAT/InformationAppIcon/SettingsAppIconViewController.swift new file mode 100644 index 00000000..06df54ae --- /dev/null +++ b/TCAT/InformationAppIcon/SettingsAppIconViewController.swift @@ -0,0 +1,104 @@ +// +// 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/InformationMainView/SettingsTableViewCell.swift b/TCAT/InformationMainView/SettingsTableViewCell.swift new file mode 100644 index 00000000..2a532be3 --- /dev/null +++ b/TCAT/InformationMainView/SettingsTableViewCell.swift @@ -0,0 +1,108 @@ +// +// 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: - Add Separator + func addSeparator(width: Int) { + let separatorImageView = UIImageView(image: UIImage(named: "tableSeparator")) + contentView.addSubview(separatorImageView) + + separatorImageView.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(width) + make.centerX.equalToSuperview() + make.centerY.equalTo(contentView.snp.bottom) + } + } + + // 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/InformationMainView/SettingsViewController.swift b/TCAT/InformationMainView/SettingsViewController.swift new file mode 100644 index 00000000..c13bcac3 --- /dev/null +++ b/TCAT/InformationMainView/SettingsViewController.swift @@ -0,0 +1,226 @@ +// +// 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 = AboutPageOpenedPayload() + 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()]) + ), +// RowItem( +// image: UIImage(named: "favStar"), +// title: "Favorites", +// subtitle: "Manage your favorite stops", +// navAction: .push(SettingsFaveViewController()) +// ), + RowItem( + image: UIImage(named: "settingsBus"), + title: "App Icon", + 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 + + // a very complicated way of making the nav bar large title blue + let appearance = UINavigationBarAppearance() + appearance.largeTitleTextAttributes = [ + .foregroundColor: Colors.tcatBlue as Any + ] + + let scrollEdgeAppearance = appearance.copy() + scrollEdgeAppearance.configureWithTransparentBackground() + navigationItem.scrollEdgeAppearance = scrollEdgeAppearance + + navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = .clear + 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.separatorStyle = .none + + 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) + } + + private func handleNavigation(action: NavigationAction) { + switch action { + case .push(let viewController): + 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.addSeparator(width: 360) + } + + 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/InformationPrivacy/SettingsPrivacyView.swift b/TCAT/InformationPrivacy/SettingsPrivacyView.swift new file mode 100644 index 00000000..cc2ed18f --- /dev/null +++ b/TCAT/InformationPrivacy/SettingsPrivacyView.swift @@ -0,0 +1,158 @@ +// +// 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 = false + @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/InformationPrivacy/SettingsPrivacyViewController.swift b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift new file mode 100644 index 00000000..d0e2c9a7 --- /dev/null +++ b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift @@ -0,0 +1,114 @@ +// +// SettingsPrivacyViewController.swift +// TCAT +// +// Created by Asen Ou on 3/12/25. +// Copyright © 2025 Cornell AppDev. All rights reserved. +// +import Combine +import Foundation +import SwiftUI + +class SettingsPrivacyViewController: UIViewController { + + private lazy var hostingController: UIHostingController = { + let hostingController = UIHostingController(rootView: SettingsPrivacyView()) + return hostingController + }() + + private var cancellables: Set = [] + + override func viewDidLoad() { + super.viewDidLoad() + + setUpNavigationItem() + setUpView() + setUpConstraints() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + +// RootViewController.setStatusBarStyle(.darkContent) + + updateView() + } + + private func setUpNavigationItem() { +// let appearance = UINavigationBarAppearance() +// appearance.titleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.black as Any, +// .font: UIFont.eateryNavigationBarTitleFont +// ] +// appearance.largeTitleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.blue as Any, +// .font: UIFont.eateryNavigationBarLargeTitleFont +// ] + + navigationItem.title = "Notifications & Privacy" + +// let standardAppearance = appearance.copy() +// standardAppearance.configureWithDefaultBackground() +// navigationItem.standardAppearance = standardAppearance +// +// let scrollEdgeAppearance = appearance.copy() +// scrollEdgeAppearance.configureWithTransparentBackground() +// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance + +// let backButton = UIBarButtonItem( +// image: UIImage(named: "ArrowLeft"), +// style: .plain, +// target: self, +// action: #selector(didTapBackButton) +// ) +// backButton.tintColor = UIColor.Eatery.black +// navigationItem.leftBarButtonItem = backButton + } + + 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) + } + + private func setUpConstraints() { + hostingController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } + + private func updateView() { +// let isLocationAllowed: Bool +// switch LocationManager.shared.authorizationStatus { +// case .authorizedWhenInUse, .authorizedAlways: +// isLocationAllowed = true +// default: +// isLocationAllowed = false +// } + + let isLocationAllowed = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isLocationAllowed) + hostingController.rootView.viewModel.isLocationAllowed = isLocationAllowed + + let isAnalyticsEnabled = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isAnalyticsEnabled) + hostingController.rootView.viewModel.isAnalyticsEnabled = isAnalyticsEnabled + } + +} diff --git a/TCAT/InformationPrivacy/Untitled.swift b/TCAT/InformationPrivacy/Untitled.swift new file mode 100644 index 00000000..e69de29b diff --git a/TCAT/InformationSupport/SettingsSupportView.swift b/TCAT/InformationSupport/SettingsSupportView.swift new file mode 100644 index 00000000..70506dc1 --- /dev/null +++ b/TCAT/InformationSupport/SettingsSupportView.swift @@ -0,0 +1,215 @@ +// +// SettingsSupportView.swift +// Eatery Blue +// +// Created by William Ma on 1/26/22. +// + +import SwiftUI + +//protocol SettingsSupportViewDelegate: AnyObject { +// +// func openReportIssue(preselectedIssueType: ReportIssueViewController.IssueType?) +// +//} + +struct SettingsSupportView: View { + + struct FAQItem { + let title: String + let body: Text + var isExpanded: Bool = false + + var isReportIssueButtonShown: Bool = false +// var preselectedIssueType: ReportIssueViewController.IssueType? + } + +// var delegate: SettingsSupportViewDelegate? + +// @State var faqItems: [FAQItem] = [ +// FAQItem( +// title: "Why do I see wrong or empty menus?", +// body: Text(""" +// We work with Cornell Dining to get the most accurate menus to students. Sometimes, eateries change menus on the fly or run out of a certain item and have to serve something different. +// +// If you see inaccurate menus, help us improve Eatery by letting us know what’s wrong. +// """), +// isReportIssueButtonShown: true, +// preselectedIssueType: .inaccurateItem +// ), +// FAQItem( +// title: "Why is an eatery closed when it says it should be open?", +// body: Text(""" +// We work with Cornell Dining to get the most accurate hours to students. However, sometimes hours change suddenly because of special events or weather. +// +// If you see incorrect hours, help us improve Eatery by letting us know the correct hours. +// """), +// isReportIssueButtonShown: true, +// preselectedIssueType: .incorrectHours +// ), +// // TODO: Temporarily remove wait time FAQ +//// FAQItem( +//// title: "Why is the wait time longer?", +//// body: Text(""" +//// We work with Cornell Dining to get the most accurate wait times to students. Sometimes, wait times can grow when classes or events end around meal times. +//// +//// If you see inaccurate wait times, help us improve Eatery by letting us know how long you waited. +//// """), +//// isReportIssueButtonShown: true, +//// preselectedIssueType: .inaccurateWaitTime +//// ), +// FAQItem( +// title: "Why can’t I order food on Eatery?", +// body: Text(""" +// We would love to develop an ordering app for Cornell. Unfortunately, Cornell works with GET™ App instead of us. +// +// If you’d like them to change their mind, you can [send them an email](mailto:dining@cornell.edu) :-) +// """), +// isReportIssueButtonShown: false +// ) +// ] + + 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 { + // Do nothing for now +// delegate?.openReportIssue(preselectedIssueType: nil) + + } label: { + HStack(spacing: 6) { + Spacer() + Image("report") + .renderingMode(.template) + .resizable() + .frame(width: 24, height: 24) + Text("Report an issue") + .padding(EdgeInsets(top: 14, leading: 0, bottom: 14, trailing: 0)) +// .font(Font(UIFont.preferredFont(for: .body, weight: .semibold))) + .font(Font(UIFont.preferredFont(forTextStyle: .body))) + Spacer() + } + } + .foregroundColor(.white) + .background(Color(Colors.tcatBlue)) + .clipShape(Capsule()) + + Button { + guard let url = URL(string: "mailto:team@cornellappdev.com") else { + return + } + + UIApplication.shared.open(url) + + } label: { + HStack(alignment: .center, spacing: 2) { + Spacer() + Text("Shoot us an email") +// .font(Font(UIFont.preferredFont(for: .subheadline, weight: .semibold))) + .font(Font(UIFont.preferredFont(forTextStyle: .subheadline))) + Image("externalLink") + .renderingMode(.template) + .resizable() + .frame(width: 16, height: 16) + Spacer() + } + .foregroundColor(Color(Colors.tcatBlue)) + } + .buttonStyle(.plain) + } + .listRowSeparator(.hidden) + + SwiftUI.Section { + sectionHeader(title: "Frequently Asked Questions") +// +// ForEach(0.. some View { + Text(title) +// .font(Font(UIFont.preferredFont(for: .title2, weight: .semibold))) + .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) + } +} diff --git a/TCAT/InformationSupport/SettingsSupportViewController.swift b/TCAT/InformationSupport/SettingsSupportViewController.swift new file mode 100644 index 00000000..7b990861 --- /dev/null +++ b/TCAT/InformationSupport/SettingsSupportViewController.swift @@ -0,0 +1,96 @@ +// +// 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 viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + +// RootViewController.setStatusBarStyle(.darkContent) + } + + private func setUpNavigationItem() { +// let appearance = UINavigationBarAppearance() +// appearance.titleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.black as Any, +// .font: UIFont.eateryNavigationBarTitleFont +// ] +// appearance.largeTitleTextAttributes = [ +// .foregroundColor: UIColor.Eatery.blue as Any, +// .font: UIFont.eateryNavigationBarLargeTitleFont +// ] + + navigationItem.title = "Support" + +// let standardAppearance = appearance.copy() +// standardAppearance.configureWithDefaultBackground() +// navigationItem.standardAppearance = standardAppearance +// +// let scrollEdgeAppearance = appearance.copy() +// scrollEdgeAppearance.configureWithTransparentBackground() +// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance +// +// let backButton = UIBarButtonItem( +// image: UIImage(named: "ArrowLeft"), +// style: .plain, +// target: self, +// action: #selector(didTapBackButton) +// ) +// backButton.tintColor = UIColor.Eatery.black +// navigationItem.leftBarButtonItem = backButton + } + + private func setUpView() { + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) + +// hostingController.rootView.delegate = self + } + + private func setUpConstraints() { + hostingController.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + @objc private func didTapBackButton() { + navigationController?.popViewController(animated: true) + } + +} + +//extension SettingsSupportViewController: SettingsSupportViewDelegate { +// +// func openReportIssue(preselectedIssueType: ReportIssueViewController.IssueType?) { +// let viewController = ReportIssueViewController(eateryId: 0) +// if let issueType = preselectedIssueType { +// viewController.setSelectedIssueType(issueType) +// } +// tabBarController?.present(viewController, animated: true) +// } +// +//} diff --git a/TCAT/SettingsFave/SettingsFaveViewController.swift b/TCAT/SettingsFave/SettingsFaveViewController.swift new file mode 100644 index 00000000..e8860ca9 --- /dev/null +++ b/TCAT/SettingsFave/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/Supporting/Constants.swift b/TCAT/Supporting/Constants.swift index 56033122..20cf4f61 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 hasLoggedIn = "hasLoggedIn" + static let isAnalyticsEnabled = "isAnalyticsEnabled" + static let isLocationAllowed = "isLocationAllowed" + static let activeIcon = "activeIcon" } struct Values { diff --git a/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift b/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift deleted file mode 100644 index 4ad9c694..00000000 --- a/TCAT/UI/InformationMainView/Cells/NewInformationTableViewCell.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// NewInformationTableViewCell.swift -// TCAT -// -// Created by Asen Ou on 3/4/25. -// Copyright © 2025 Cornell AppDev. All rights reserved. -// - -import UIKit - -class NewInformationTableViewCell: UITableViewCell { - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - // Configure the view for the selected state - } - -} diff --git a/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift b/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift deleted file mode 100644 index 554ae992..00000000 --- a/TCAT/UI/InformationMainView/Controllers/NewInformationViewController.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// NewInformationViewController.swift -// TCAT -// -// Created by Asen Ou on 3/4/25. -// Copyright © 2025 Cornell AppDev. All rights reserved. -// - -import UIKit - -class NewInformationViewController: UIViewController { - - // Main View Properties - private let tableView = UITableView() - - // Table View Properties - struct RowItem { - let image: UIImage? - let title: String - let subtitle: String - let action: () -> Void - } - - private var rows: [RowItem] = [] - - override func viewDidLoad() { - super.viewDidLoad() - - // Track Analytics - let payload = AboutPageOpenedPayload() - TransitAnalytics.shared.log(payload) - - // Populate row items - setUpRowItems() - - // Set up main view - setUpMainView() - setUpNavigationItem() - - // Set up subviews - setUpTableView() - } - private func setUpRowItems() { - rows = [ - RowItem( - image: <#T##UIImage?#>, - title: "About Transit", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - RowItem( - image: <#T##UIImage?#>, - title: "App Icon", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - RowItem( - image: <#T##UIImage?#>, - title: "Privacy", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - RowItem( - image: <#T##UIImage?#>, - title: "Support", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - RowItem( - image: <#T##UIImage?#>, - title: "Show Onboarding", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - RowItem( - image: <#T##UIImage?#>, - title: "TCAT Service Alerts", - subtitle: <#T##String#>, - action: <#T##() -> Void#>), - - ] - } - - // MARK: - Main view init - private func setUpMainView() { - // Initialize view defaults - title = Constants.Titles.aboutUs - view.backgroundColor = Colors.backgroundWash - navigationController?.navigationBar.tintColor = Colors.primaryText - } - - private func setUpNavigationItem() { - - } -} - -// MARK: - TableView init -extension NewInformationViewController: UITableViewDataSource, UITableViewDelegate, InfoHeaderViewDelegate { - // 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) - } - - private func setUpTableView() { - tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.Cells.informationCellIdentifier) - tableView.dataSource = self - tableView.delegate = self - tableView.backgroundColor = Colors.backgroundWash - tableView.separatorColor = Colors.dividerTextField - tableView.showsVerticalScrollIndicator = false - - let headerView = InformationTableHeaderView() - headerView.delegate = self - tableView.tableHeaderView = headerView - - view.addSubview(tableView) - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return rows.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - <#code#> - } -} 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 + } + +} From 8e490f28b94edae6a4806f1f7fc5d5e287fb5f58 Mon Sep 17 00:00:00 2001 From: ako28 Date: Mon, 24 Mar 2025 23:05:11 -0400 Subject: [PATCH 3/8] More progess (integrated analytics tracking toggle + fine tuning UI) --- TCAT.xcodeproj/project.pbxproj | 2 +- .../AppIcon.appiconset/Contents.json | 156 +++++++++--------- TCAT/Assets.xcassets/AppIcons/Contents.json | 6 + .../Default.imageset}/Contents.json | 2 +- .../ItunesArtwork@2x copy.png | Bin 0 -> 79138 bytes .../tableSeparator.imageset/Vector 1.png | Bin 141 -> 0 bytes .../InformationViewController.swift | 2 +- .../ServiceAlertsViewController.swift | 2 +- .../SettingsAboutHeaderView.swift | 1 - .../SettingsAboutMembersCarouselView.swift | 15 +- .../SettingsAboutViewController.swift | 80 +++++---- .../SettingsTableViewCell.swift | 13 -- .../SettingsViewController.swift | 44 +++-- .../SettingsPrivacyView.swift | 1 + .../SettingsPrivacyViewController.swift | 35 +--- .../SettingsSupportView.swift | 156 +----------------- .../SettingsSupportViewController.swift | 52 +----- TCAT/Supporting/Constants.swift | 3 +- TCAT/Utils/Analytics.swift | 37 ++++- 19 files changed, 215 insertions(+), 392 deletions(-) create mode 100644 TCAT/Assets.xcassets/AppIcons/Contents.json rename TCAT/Assets.xcassets/{Settings Assets/tableSeparator.imageset => AppIcons/Default.imageset}/Contents.json (84%) create mode 100644 TCAT/Assets.xcassets/AppIcons/Default.imageset/ItunesArtwork@2x copy.png delete mode 100644 TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Vector 1.png diff --git a/TCAT.xcodeproj/project.pbxproj b/TCAT.xcodeproj/project.pbxproj index a5f1d6ef..07e67543 100644 --- a/TCAT.xcodeproj/project.pbxproj +++ b/TCAT.xcodeproj/project.pbxproj @@ -363,8 +363,8 @@ 1C0296C22D77E65D005B92FC /* InformationPrivacy */ = { isa = PBXGroup; children = ( - 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */, + 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, ); path = InformationPrivacy; sourceTree = ""; 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/Settings Assets/tableSeparator.imageset/Contents.json b/TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json similarity index 84% rename from TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json rename to TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json index 684cc99d..8c99c360 100644 --- a/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Contents.json +++ b/TCAT/Assets.xcassets/AppIcons/Default.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Vector 1.png", + "filename" : "ItunesArtwork@2x copy.png", "idiom" : "universal", "scale" : "1x" }, 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 0000000000000000000000000000000000000000..2cca64bfcfe8459ddae6efe2e17fff6a151baf6d GIT binary patch literal 79138 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�N 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%0AFu@9Ni>kNY&Qr6|4Fk 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 literal 0 HcmV?d00001 diff --git a/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Vector 1.png b/TCAT/Assets.xcassets/Settings Assets/tableSeparator.imageset/Vector 1.png deleted file mode 100644 index 4ccfea5380c22eaeb88e6e5a83c8798e608a1010..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141 zcmeAS@N?(olHy`uVBq!ia0y~yVA>31GjXs1N$+&U>cv7h@-A}f%u8bLO)?$9f(BRWo?H`njxgN@xNAaNQ-7 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 5a34ccc7..42d57875 100644 --- a/TCAT/Controllers/ServiceAlertsViewController.swift +++ b/TCAT/Controllers/ServiceAlertsViewController.swift @@ -43,7 +43,7 @@ class ServiceAlertsViewController: UIViewController { super.viewDidLoad() title = Constants.Titles.serviceAlerts - + // Temporary change for settings page (make nav title prefer large to fit settings page theme) navigationController?.navigationBar.prefersLargeTitles = true diff --git a/TCAT/InformationAbout/SettingsAboutHeaderView.swift b/TCAT/InformationAbout/SettingsAboutHeaderView.swift index b965460d..cdad5b61 100644 --- a/TCAT/InformationAbout/SettingsAboutHeaderView.swift +++ b/TCAT/InformationAbout/SettingsAboutHeaderView.swift @@ -50,7 +50,6 @@ class SettingsAboutHeaderView: UIView { private func setUpLogoView() { logoView.image = UIImage(named: "appDevLogo") -// logoView.tintColor = UIColor } private func setUpSubtitleLabel() { diff --git a/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift b/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift index 61ccf700..decdb438 100644 --- a/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift +++ b/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift @@ -63,7 +63,7 @@ class SettingsAboutMembersCarouselView: UIView { label.font = .preferredFont(forTextStyle: .footnote) label.text = title - stackView.addArrangedSubview(label) + addViewAnimated(label) } func addMemberView(name: String) { @@ -76,7 +76,7 @@ class SettingsAboutMembersCarouselView: UIView { container.layoutMargins = UIEdgeInsets(top: 8, left: 10, bottom: 8, right: 10) container.backgroundColor = Colors.carouselGray - stackView.addArrangedSubview(container) + addViewAnimated(container) } func addSeparator() { @@ -87,7 +87,16 @@ class SettingsAboutMembersCarouselView: UIView { make.width.height.equalTo(8) } - stackView.addArrangedSubview(imageView) + 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() { diff --git a/TCAT/InformationAbout/SettingsAboutViewController.swift b/TCAT/InformationAbout/SettingsAboutViewController.swift index 6e171e42..5103190b 100644 --- a/TCAT/InformationAbout/SettingsAboutViewController.swift +++ b/TCAT/InformationAbout/SettingsAboutViewController.swift @@ -10,58 +10,42 @@ 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() + // Track Analytics + let payload = SettingsAboutPageOpenedPayload() + TransitAnalytics.shared.log(payload) + setUpNavigationItem() setUpView() setUpConstraints() - - print("setting up carousel") setUpCarouselViews() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - -// RootViewController.setStatusBarStyle(.darkContent) - } - override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) scrollView.flashScrollIndicators() + if !firstTimeLoading { + reshuffle() + } else { + firstTimeLoading = false + } } private func setUpNavigationItem() { -// let appearance = UINavigationBarAppearance() -// appearance.titleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.black as Any, -// .font: UIFont.eateryNavigationBarTitleFont -// ] -// appearance.largeTitleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.blue as Any, -// .font: UIFont.eateryNavigationBarLargeTitleFont -// ] - navigationItem.title = "About Transit" - -// let standardAppearance = appearance.copy() -// standardAppearance.configureWithDefaultBackground() -// navigationItem.standardAppearance = standardAppearance -// -// let scrollEdgeAppearance = appearance.copy() -// scrollEdgeAppearance.configureWithTransparentBackground() -// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance } private func setUpView() { @@ -71,7 +55,6 @@ class SettingsAboutViewController: UIViewController { setUpSubtitleLabel() view.addSubview(headerView) - setUpHeaderView() view.addSubview(scrollView) setUpScrollView() @@ -88,9 +71,6 @@ class SettingsAboutViewController: UIViewController { // subtitleLabel.font = .preferredFont(for: .body, weight: .medium) } - private func setUpHeaderView() { - } - private func setUpScrollView() { scrollView.addSubview(stackView) setUpStackView() @@ -142,16 +122,6 @@ class SettingsAboutViewController: UIViewController { } } - @objc private func didTapBackButton() { - navigationController?.popViewController(animated: true) - } - - func addCarouselView(_ configure: (SettingsAboutMembersCarouselView) -> Void) { - let carouselView = SettingsAboutMembersCarouselView() - configure(carouselView) - stackView.addArrangedSubview(carouselView) - } - private struct HSection { let title: String let members: [String] @@ -239,6 +209,12 @@ class SettingsAboutViewController: UIViewController { ]) ] + 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 { @@ -261,4 +237,22 @@ class SettingsAboutViewController: UIViewController { } } + 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/InformationMainView/SettingsTableViewCell.swift b/TCAT/InformationMainView/SettingsTableViewCell.swift index 2a532be3..e985de10 100644 --- a/TCAT/InformationMainView/SettingsTableViewCell.swift +++ b/TCAT/InformationMainView/SettingsTableViewCell.swift @@ -36,19 +36,6 @@ class SettingsTableViewCell: UITableViewCell { subtitleLabel.text = subtitle } - // MARK: - Add Separator - func addSeparator(width: Int) { - let separatorImageView = UIImageView(image: UIImage(named: "tableSeparator")) - contentView.addSubview(separatorImageView) - - separatorImageView.snp.makeConstraints { make in - make.height.equalTo(1) - make.width.equalTo(width) - make.centerX.equalToSuperview() - make.centerY.equalTo(contentView.snp.bottom) - } - } - // MARK: - View setup private func setUpUI() { diff --git a/TCAT/InformationMainView/SettingsViewController.swift b/TCAT/InformationMainView/SettingsViewController.swift index c13bcac3..67b70d8c 100644 --- a/TCAT/InformationMainView/SettingsViewController.swift +++ b/TCAT/InformationMainView/SettingsViewController.swift @@ -32,7 +32,7 @@ class SettingsViewController: UIViewController { super.viewDidLoad() // Track Analytics - let payload = AboutPageOpenedPayload() + let payload = SettingsPageOpenedPayload() TransitAnalytics.shared.log(payload) // Populate row items @@ -59,18 +59,19 @@ class SettingsViewController: UIViewController { 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 Icon", - subtitle: "Choose your adventure", - navAction: .present(SettingsAppIconViewController(), [.medium()]) - ), +// RowItem( +// image: UIImage(named: "settingsBus"), +// title: "App Icons", +// subtitle: "Choose your adventure", +// navAction: .present(SettingsAppIconViewController(), [.medium()]) +// ), RowItem( image: UIImage(named: "lock"), title: "Notifications & Privacy", @@ -121,17 +122,20 @@ class SettingsViewController: UIViewController { backButton.tintColor = .black navigationItem.leftBarButtonItem = backButton - // a very complicated way of making the nav bar large title blue + // navigation bar appearance let appearance = UINavigationBarAppearance() appearance.largeTitleTextAttributes = [ .foregroundColor: Colors.tcatBlue as Any ] let scrollEdgeAppearance = appearance.copy() - scrollEdgeAppearance.configureWithTransparentBackground() + scrollEdgeAppearance.backgroundColor = Colors.white + scrollEdgeAppearance.shadowColor = .clear navigationItem.scrollEdgeAppearance = scrollEdgeAppearance - navigationController?.navigationBar.scrollEdgeAppearance?.shadowColor = .clear + let standardAppearance = appearance.copy() + navigationItem.standardAppearance = standardAppearance + navigationController?.navigationBar.prefersLargeTitles = true } } @@ -145,8 +149,7 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate, In tableView.backgroundColor = Colors.white tableView.separatorColor = Colors.dividerTextField tableView.showsVerticalScrollIndicator = false - - tableView.separatorStyle = .none + tableView.separatorInset = .zero let headerView = InformationTableHeaderView() headerView.delegate = self @@ -162,9 +165,21 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate, In 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) @@ -205,10 +220,11 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate, In subtitle: rowItem.subtitle ) - if indexPath.row < (rows.count - 1) { - cell.addSeparator(width: 360) + if indexPath.row == (rows.count - 1) { + cell.separatorInset.left = .infinity } + cell.selectionStyle = .none return cell } diff --git a/TCAT/InformationPrivacy/SettingsPrivacyView.swift b/TCAT/InformationPrivacy/SettingsPrivacyView.swift index cc2ed18f..5dfd4cd7 100644 --- a/TCAT/InformationPrivacy/SettingsPrivacyView.swift +++ b/TCAT/InformationPrivacy/SettingsPrivacyView.swift @@ -72,6 +72,7 @@ struct SettingsPrivacyView: View { } } .listRowSeparator(.visible, edges: .bottom) + // Notifications Button { guard let url = URL(string: UIApplication.openSettingsURLString) else { diff --git a/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift index d0e2c9a7..5a8a1d81 100644 --- a/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift +++ b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift @@ -21,6 +21,10 @@ class SettingsPrivacyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + // Track Analytics + let payload = SettingsNotifPrivacyPageOpenedPayload() + TransitAnalytics.shared.log(payload) + setUpNavigationItem() setUpView() setUpConstraints() @@ -29,40 +33,11 @@ class SettingsPrivacyViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) -// RootViewController.setStatusBarStyle(.darkContent) - updateView() } private func setUpNavigationItem() { -// let appearance = UINavigationBarAppearance() -// appearance.titleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.black as Any, -// .font: UIFont.eateryNavigationBarTitleFont -// ] -// appearance.largeTitleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.blue as Any, -// .font: UIFont.eateryNavigationBarLargeTitleFont -// ] - navigationItem.title = "Notifications & Privacy" - -// let standardAppearance = appearance.copy() -// standardAppearance.configureWithDefaultBackground() -// navigationItem.standardAppearance = standardAppearance -// -// let scrollEdgeAppearance = appearance.copy() -// scrollEdgeAppearance.configureWithTransparentBackground() -// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance - -// let backButton = UIBarButtonItem( -// image: UIImage(named: "ArrowLeft"), -// style: .plain, -// target: self, -// action: #selector(didTapBackButton) -// ) -// backButton.tintColor = UIColor.Eatery.black -// navigationItem.leftBarButtonItem = backButton } private func setUpView() { @@ -76,7 +51,7 @@ class SettingsPrivacyViewController: UIViewController { UserDefaults.standard.set(isAnalyticsEnabled, forKey: Constants.UserDefaults.isAnalyticsEnabled) } .store(in: &cancellables) - + hostingController.rootView.viewModel.$isLocationAllowed .dropFirst() .sink { isLocationAllowed in diff --git a/TCAT/InformationSupport/SettingsSupportView.swift b/TCAT/InformationSupport/SettingsSupportView.swift index 70506dc1..3eaa2803 100644 --- a/TCAT/InformationSupport/SettingsSupportView.swift +++ b/TCAT/InformationSupport/SettingsSupportView.swift @@ -7,68 +7,8 @@ import SwiftUI -//protocol SettingsSupportViewDelegate: AnyObject { -// -// func openReportIssue(preselectedIssueType: ReportIssueViewController.IssueType?) -// -//} - struct SettingsSupportView: View { - struct FAQItem { - let title: String - let body: Text - var isExpanded: Bool = false - - var isReportIssueButtonShown: Bool = false -// var preselectedIssueType: ReportIssueViewController.IssueType? - } - -// var delegate: SettingsSupportViewDelegate? - -// @State var faqItems: [FAQItem] = [ -// FAQItem( -// title: "Why do I see wrong or empty menus?", -// body: Text(""" -// We work with Cornell Dining to get the most accurate menus to students. Sometimes, eateries change menus on the fly or run out of a certain item and have to serve something different. -// -// If you see inaccurate menus, help us improve Eatery by letting us know what’s wrong. -// """), -// isReportIssueButtonShown: true, -// preselectedIssueType: .inaccurateItem -// ), -// FAQItem( -// title: "Why is an eatery closed when it says it should be open?", -// body: Text(""" -// We work with Cornell Dining to get the most accurate hours to students. However, sometimes hours change suddenly because of special events or weather. -// -// If you see incorrect hours, help us improve Eatery by letting us know the correct hours. -// """), -// isReportIssueButtonShown: true, -// preselectedIssueType: .incorrectHours -// ), -// // TODO: Temporarily remove wait time FAQ -//// FAQItem( -//// title: "Why is the wait time longer?", -//// body: Text(""" -//// We work with Cornell Dining to get the most accurate wait times to students. Sometimes, wait times can grow when classes or events end around meal times. -//// -//// If you see inaccurate wait times, help us improve Eatery by letting us know how long you waited. -//// """), -//// isReportIssueButtonShown: true, -//// preselectedIssueType: .inaccurateWaitTime -//// ), -// FAQItem( -// title: "Why can’t I order food on Eatery?", -// body: Text(""" -// We would love to develop an ordering app for Cornell. Unfortunately, Cornell works with GET™ App instead of us. -// -// If you’d like them to change their mind, you can [send them an email](mailto:dining@cornell.edu) :-) -// """), -// isReportIssueButtonShown: false -// ) -// ] - var body: some View { List { Text("Report issues and contact Cornell AppDev") @@ -81,9 +21,10 @@ struct SettingsSupportView: View { .foregroundColor(.gray) Button { - // Do nothing for now -// delegate?.openReportIssue(preselectedIssueType: nil) - + guard let url = URL(string: "mailto:team@cornellappdev.com") else { + return + } + UIApplication.shared.open(url) } label: { HStack(spacing: 6) { Spacer() @@ -91,7 +32,7 @@ struct SettingsSupportView: View { .renderingMode(.template) .resizable() .frame(width: 24, height: 24) - Text("Report an issue") + Text("Shoot us an email") .padding(EdgeInsets(top: 14, leading: 0, bottom: 14, trailing: 0)) // .font(Font(UIFont.preferredFont(for: .body, weight: .semibold))) .font(Font(UIFont.preferredFont(forTextStyle: .body))) @@ -101,97 +42,11 @@ struct SettingsSupportView: View { .foregroundColor(.white) .background(Color(Colors.tcatBlue)) .clipShape(Capsule()) - - Button { - guard let url = URL(string: "mailto:team@cornellappdev.com") else { - return - } - - UIApplication.shared.open(url) - - } label: { - HStack(alignment: .center, spacing: 2) { - Spacer() - Text("Shoot us an email") -// .font(Font(UIFont.preferredFont(for: .subheadline, weight: .semibold))) - .font(Font(UIFont.preferredFont(forTextStyle: .subheadline))) - Image("externalLink") - .renderingMode(.template) - .resizable() - .frame(width: 16, height: 16) - Spacer() - } - .foregroundColor(Color(Colors.tcatBlue)) - } - .buttonStyle(.plain) } .listRowSeparator(.hidden) SwiftUI.Section { sectionHeader(title: "Frequently Asked Questions") -// -// ForEach(0.. some View { Text(title) -// .font(Font(UIFont.preferredFont(for: .title2, weight: .semibold))) .font(Font(generateFont(for: .title2, weight: .semibold))) .foregroundColor(Color(Colors.black)) .padding(EdgeInsets(top: 12, leading: 0, bottom: 0, trailing: 0)) diff --git a/TCAT/InformationSupport/SettingsSupportViewController.swift b/TCAT/InformationSupport/SettingsSupportViewController.swift index 7b990861..10bef537 100644 --- a/TCAT/InformationSupport/SettingsSupportViewController.swift +++ b/TCAT/InformationSupport/SettingsSupportViewController.swift @@ -21,54 +21,23 @@ class SettingsSupportViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + // Track Analytics + let payload = SettingsSupportPageOpenedPayload() + TransitAnalytics.shared.log(payload) + setUpNavigationItem() setUpView() setUpConstraints() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - -// RootViewController.setStatusBarStyle(.darkContent) - } - private func setUpNavigationItem() { -// let appearance = UINavigationBarAppearance() -// appearance.titleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.black as Any, -// .font: UIFont.eateryNavigationBarTitleFont -// ] -// appearance.largeTitleTextAttributes = [ -// .foregroundColor: UIColor.Eatery.blue as Any, -// .font: UIFont.eateryNavigationBarLargeTitleFont -// ] - navigationItem.title = "Support" - -// let standardAppearance = appearance.copy() -// standardAppearance.configureWithDefaultBackground() -// navigationItem.standardAppearance = standardAppearance -// -// let scrollEdgeAppearance = appearance.copy() -// scrollEdgeAppearance.configureWithTransparentBackground() -// navigationItem.scrollEdgeAppearance = scrollEdgeAppearance -// -// let backButton = UIBarButtonItem( -// image: UIImage(named: "ArrowLeft"), -// style: .plain, -// target: self, -// action: #selector(didTapBackButton) -// ) -// backButton.tintColor = UIColor.Eatery.black -// navigationItem.leftBarButtonItem = backButton } private func setUpView() { addChild(hostingController) view.addSubview(hostingController.view) hostingController.didMove(toParent: self) - -// hostingController.rootView.delegate = self } private func setUpConstraints() { @@ -80,17 +49,4 @@ class SettingsSupportViewController: UIViewController { @objc private func didTapBackButton() { navigationController?.popViewController(animated: true) } - } - -//extension SettingsSupportViewController: SettingsSupportViewDelegate { -// -// func openReportIssue(preselectedIssueType: ReportIssueViewController.IssueType?) { -// let viewController = ReportIssueViewController(eateryId: 0) -// if let issueType = preselectedIssueType { -// viewController.setSelectedIssueType(issueType) -// } -// tabBarController?.present(viewController, animated: true) -// } -// -//} diff --git a/TCAT/Supporting/Constants.swift b/TCAT/Supporting/Constants.swift index 20cf4f61..375033d1 100644 --- a/TCAT/Supporting/Constants.swift +++ b/TCAT/Supporting/Constants.swift @@ -304,9 +304,8 @@ struct Constants { static let promotionDismissed = "promotionDismissed" static let recentSearch = "recentSearch" static let servicedRoutes = "servicedRoutes" - + /// Analytics variables for Settings Page / Privacy -// static let hasLoggedIn = "hasLoggedIn" static let isAnalyticsEnabled = "isAnalyticsEnabled" static let isLocationAllowed = "isLocationAllowed" static let activeIcon = "activeIcon" 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() } From 4b265b0f785e78dbce5b85189919ba154eaf83b6 Mon Sep 17 00:00:00 2001 From: ako28 Date: Mon, 24 Mar 2025 23:52:06 -0400 Subject: [PATCH 4/8] Minor changes for analytics in Settings page --- TCAT/Controllers/ServiceAlertsViewController.swift | 6 +++++- .../SettingsAboutViewController.swift | 7 +++---- .../SettingsAppIconViewController.swift | 3 +-- .../SettingsPrivacyViewController.swift | 7 +++---- .../SettingsSupportViewController.swift | 12 ++++++++---- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/TCAT/Controllers/ServiceAlertsViewController.swift b/TCAT/Controllers/ServiceAlertsViewController.swift index 42d57875..01153571 100644 --- a/TCAT/Controllers/ServiceAlertsViewController.swift +++ b/TCAT/Controllers/ServiceAlertsViewController.swift @@ -65,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/InformationAbout/SettingsAboutViewController.swift b/TCAT/InformationAbout/SettingsAboutViewController.swift index 5103190b..00d322b0 100644 --- a/TCAT/InformationAbout/SettingsAboutViewController.swift +++ b/TCAT/InformationAbout/SettingsAboutViewController.swift @@ -23,10 +23,6 @@ class SettingsAboutViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Track Analytics - let payload = SettingsAboutPageOpenedPayload() - TransitAnalytics.shared.log(payload) - setUpNavigationItem() setUpView() setUpConstraints() @@ -35,6 +31,9 @@ class SettingsAboutViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Track Analytics + let payload = SettingsAboutPageOpenedPayload() + TransitAnalytics.shared.log(payload) scrollView.flashScrollIndicators() if !firstTimeLoading { diff --git a/TCAT/InformationAppIcon/SettingsAppIconViewController.swift b/TCAT/InformationAppIcon/SettingsAppIconViewController.swift index 06df54ae..2bdab1fb 100644 --- a/TCAT/InformationAppIcon/SettingsAppIconViewController.swift +++ b/TCAT/InformationAppIcon/SettingsAppIconViewController.swift @@ -16,14 +16,13 @@ struct AppIcon { } 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()) diff --git a/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift index 5a8a1d81..64cd97ad 100644 --- a/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift +++ b/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift @@ -21,10 +21,6 @@ class SettingsPrivacyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Track Analytics - let payload = SettingsNotifPrivacyPageOpenedPayload() - TransitAnalytics.shared.log(payload) - setUpNavigationItem() setUpView() setUpConstraints() @@ -32,6 +28,9 @@ class SettingsPrivacyViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + // Track Analytics + let payload = SettingsNotifPrivacyPageOpenedPayload() + TransitAnalytics.shared.log(payload) updateView() } diff --git a/TCAT/InformationSupport/SettingsSupportViewController.swift b/TCAT/InformationSupport/SettingsSupportViewController.swift index 10bef537..8863b752 100644 --- a/TCAT/InformationSupport/SettingsSupportViewController.swift +++ b/TCAT/InformationSupport/SettingsSupportViewController.swift @@ -21,15 +21,19 @@ class SettingsSupportViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - // Track Analytics - let payload = SettingsSupportPageOpenedPayload() - TransitAnalytics.shared.log(payload) - 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" } From b3f1a6c34841b2e9fe825dab84fe69b2cd7d5b9b Mon Sep 17 00:00:00 2001 From: ako28 Date: Wed, 9 Apr 2025 13:45:11 -0400 Subject: [PATCH 5/8] File rearrangement --- TCAT.xcodeproj/project.pbxproj | 72 ++++--------------- .../SettingsAppIconCollectionViewCell.swift | 0 .../SettingsTableViewCell.swift | 0 .../SettingsAboutViewController.swift | 0 .../SettingsAppIconViewController.swift | 0 .../SettingsFaveViewController.swift | 0 .../SettingsPrivacyViewController.swift | 0 .../SettingsSupportViewController.swift | 0 .../SettingsViewController.swift | 0 .../SettingsAboutHeaderView.swift | 0 .../SettingsAboutMembersCarouselView.swift | 0 .../SettingsPrivacyView.swift | 0 .../SettingsSupportView.swift | 4 +- 13 files changed, 14 insertions(+), 62 deletions(-) rename TCAT/{InformationAppIcon => Cells}/SettingsAppIconCollectionViewCell.swift (100%) rename TCAT/{InformationMainView => Cells}/SettingsTableViewCell.swift (100%) rename TCAT/{InformationAbout => Controllers}/SettingsAboutViewController.swift (100%) rename TCAT/{InformationAppIcon => Controllers}/SettingsAppIconViewController.swift (100%) rename TCAT/{SettingsFave => Controllers}/SettingsFaveViewController.swift (100%) rename TCAT/{InformationPrivacy => Controllers}/SettingsPrivacyViewController.swift (100%) rename TCAT/{InformationSupport => Controllers}/SettingsSupportViewController.swift (100%) rename TCAT/{InformationMainView => Controllers}/SettingsViewController.swift (100%) rename TCAT/{InformationAbout => Views}/SettingsAboutHeaderView.swift (100%) rename TCAT/{InformationAbout => Views}/SettingsAboutMembersCarouselView.swift (100%) rename TCAT/{InformationPrivacy => Views}/SettingsPrivacyView.swift (100%) rename TCAT/{InformationSupport => Views}/SettingsSupportView.swift (93%) diff --git a/TCAT.xcodeproj/project.pbxproj b/TCAT.xcodeproj/project.pbxproj index 07e67543..38d64079 100644 --- a/TCAT.xcodeproj/project.pbxproj +++ b/TCAT.xcodeproj/project.pbxproj @@ -342,60 +342,6 @@ name = Frameworks; sourceTree = ""; }; - 1C0296B02D77E2A6005B92FC /* InformationMainView */ = { - isa = PBXGroup; - children = ( - 1C0296A92D77D9F5005B92FC /* SettingsViewController.swift */, - 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */, - ); - path = InformationMainView; - sourceTree = ""; - }; - 1C0296BE2D77E63B005B92FC /* InformationAppIcon */ = { - isa = PBXGroup; - children = ( - 1C0296D72D7FE055005B92FC /* SettingsAppIconViewController.swift */, - 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */, - ); - path = InformationAppIcon; - sourceTree = ""; - }; - 1C0296C22D77E65D005B92FC /* InformationPrivacy */ = { - isa = PBXGroup; - children = ( - 1C0296D92D823F8D005B92FC /* SettingsPrivacyViewController.swift */, - 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, - ); - path = InformationPrivacy; - sourceTree = ""; - }; - 1C0296C62D77E6A1005B92FC /* InformationAbout */ = { - isa = PBXGroup; - children = ( - 1C0296E82D87A3B4005B92FC /* SettingsAboutViewController.swift */, - 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */, - 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */, - ); - path = InformationAbout; - sourceTree = ""; - }; - 1C0296CA2D77E6CB005B92FC /* InformationSupport */ = { - isa = PBXGroup; - children = ( - 1C591B4E2D8D22BD00DDA71D /* SettingsSupportViewController.swift */, - 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */, - ); - path = InformationSupport; - sourceTree = ""; - }; - 1C0296DF2D82408F005B92FC /* SettingsFave */ = { - isa = PBXGroup; - children = ( - 1C0296E02D8240A7005B92FC /* SettingsFaveViewController.swift */, - ); - path = SettingsFave; - sourceTree = ""; - }; 2292486621B891790004279C /* Network */ = { isa = PBXGroup; children = ( @@ -441,6 +387,8 @@ 2E9416762BC61679003DEB44 /* RouteTableViewCell.swift */, 2E94166D2BC61678003DEB44 /* ServiceAlertTableViewCell.swift */, 2E9416722BC61679003DEB44 /* SmallDetailTableViewCell.swift */, + 1C0296B52D77E578005B92FC /* SettingsTableViewCell.swift */, + 1C591B592D8DD1B500DDA71D /* SettingsAppIconCollectionViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -467,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 = ""; @@ -564,6 +518,10 @@ 1C0296F32D8B609F005B92FC /* ButtonView.swift */, 1C0296F12D8B603F005B92FC /* PillButtonView.swift */, 1C0296EF2D8B5CF4005B92FC /* ContainerView.swift */, + 1C0296E62D87A370005B92FC /* SettingsAboutMembersCarouselView.swift */, + 1C0296E42D87A2E3005B92FC /* SettingsAboutHeaderView.swift */, + 1C0296EC2D87A690005B92FC /* SettingsPrivacyView.swift */, + 1C591B4A2D8D226300DDA71D /* SettingsSupportView.swift */, ); path = Views; sourceTree = ""; @@ -681,12 +639,6 @@ 2E94166C2BC61604003DEB44 /* Cells */, 2E9416FD2BC61CAE003DEB44 /* Views */, 2E9416822BC6168C003DEB44 /* Controllers */, - 1C0296B02D77E2A6005B92FC /* InformationMainView */, - 1C0296C62D77E6A1005B92FC /* InformationAbout */, - 1C0296BE2D77E63B005B92FC /* InformationAppIcon */, - 1C0296C22D77E65D005B92FC /* InformationPrivacy */, - 1C0296CA2D77E6CB005B92FC /* InformationSupport */, - 1C0296DF2D82408F005B92FC /* SettingsFave */, 2E94165E2BC60A3B003DEB44 /* Ecosystem */, 2E9416AB2BC616DE003DEB44 /* Models */, FDE68D292C988CDB00024A69 /* Services */, diff --git a/TCAT/InformationAppIcon/SettingsAppIconCollectionViewCell.swift b/TCAT/Cells/SettingsAppIconCollectionViewCell.swift similarity index 100% rename from TCAT/InformationAppIcon/SettingsAppIconCollectionViewCell.swift rename to TCAT/Cells/SettingsAppIconCollectionViewCell.swift diff --git a/TCAT/InformationMainView/SettingsTableViewCell.swift b/TCAT/Cells/SettingsTableViewCell.swift similarity index 100% rename from TCAT/InformationMainView/SettingsTableViewCell.swift rename to TCAT/Cells/SettingsTableViewCell.swift diff --git a/TCAT/InformationAbout/SettingsAboutViewController.swift b/TCAT/Controllers/SettingsAboutViewController.swift similarity index 100% rename from TCAT/InformationAbout/SettingsAboutViewController.swift rename to TCAT/Controllers/SettingsAboutViewController.swift diff --git a/TCAT/InformationAppIcon/SettingsAppIconViewController.swift b/TCAT/Controllers/SettingsAppIconViewController.swift similarity index 100% rename from TCAT/InformationAppIcon/SettingsAppIconViewController.swift rename to TCAT/Controllers/SettingsAppIconViewController.swift diff --git a/TCAT/SettingsFave/SettingsFaveViewController.swift b/TCAT/Controllers/SettingsFaveViewController.swift similarity index 100% rename from TCAT/SettingsFave/SettingsFaveViewController.swift rename to TCAT/Controllers/SettingsFaveViewController.swift diff --git a/TCAT/InformationPrivacy/SettingsPrivacyViewController.swift b/TCAT/Controllers/SettingsPrivacyViewController.swift similarity index 100% rename from TCAT/InformationPrivacy/SettingsPrivacyViewController.swift rename to TCAT/Controllers/SettingsPrivacyViewController.swift diff --git a/TCAT/InformationSupport/SettingsSupportViewController.swift b/TCAT/Controllers/SettingsSupportViewController.swift similarity index 100% rename from TCAT/InformationSupport/SettingsSupportViewController.swift rename to TCAT/Controllers/SettingsSupportViewController.swift diff --git a/TCAT/InformationMainView/SettingsViewController.swift b/TCAT/Controllers/SettingsViewController.swift similarity index 100% rename from TCAT/InformationMainView/SettingsViewController.swift rename to TCAT/Controllers/SettingsViewController.swift diff --git a/TCAT/InformationAbout/SettingsAboutHeaderView.swift b/TCAT/Views/SettingsAboutHeaderView.swift similarity index 100% rename from TCAT/InformationAbout/SettingsAboutHeaderView.swift rename to TCAT/Views/SettingsAboutHeaderView.swift diff --git a/TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift b/TCAT/Views/SettingsAboutMembersCarouselView.swift similarity index 100% rename from TCAT/InformationAbout/SettingsAboutMembersCarouselView.swift rename to TCAT/Views/SettingsAboutMembersCarouselView.swift diff --git a/TCAT/InformationPrivacy/SettingsPrivacyView.swift b/TCAT/Views/SettingsPrivacyView.swift similarity index 100% rename from TCAT/InformationPrivacy/SettingsPrivacyView.swift rename to TCAT/Views/SettingsPrivacyView.swift diff --git a/TCAT/InformationSupport/SettingsSupportView.swift b/TCAT/Views/SettingsSupportView.swift similarity index 93% rename from TCAT/InformationSupport/SettingsSupportView.swift rename to TCAT/Views/SettingsSupportView.swift index 3eaa2803..a4f2a6f3 100644 --- a/TCAT/InformationSupport/SettingsSupportView.swift +++ b/TCAT/Views/SettingsSupportView.swift @@ -34,7 +34,6 @@ struct SettingsSupportView: View { .frame(width: 24, height: 24) Text("Shoot us an email") .padding(EdgeInsets(top: 14, leading: 0, bottom: 14, trailing: 0)) -// .font(Font(UIFont.preferredFont(for: .body, weight: .semibold))) .font(Font(UIFont.preferredFont(forTextStyle: .body))) Spacer() } @@ -46,7 +45,8 @@ struct SettingsSupportView: View { .listRowSeparator(.hidden) SwiftUI.Section { - sectionHeader(title: "Frequently Asked Questions") + // TODO: Add FAQs + // sectionHeader(title: "Frequently Asked Questions") } } .listStyle(.plain) From ed0a26cf922b297b2e9459c1105070526676fdd5 Mon Sep 17 00:00:00 2001 From: ako28 Date: Wed, 9 Apr 2025 14:17:08 -0400 Subject: [PATCH 6/8] fixed location implementation to use CLLocationManager --- .../SettingsPrivacyViewController.swift | 19 ++++++++++--------- TCAT/Views/SettingsPrivacyView.swift | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/TCAT/Controllers/SettingsPrivacyViewController.swift b/TCAT/Controllers/SettingsPrivacyViewController.swift index 64cd97ad..6f0b05b1 100644 --- a/TCAT/Controllers/SettingsPrivacyViewController.swift +++ b/TCAT/Controllers/SettingsPrivacyViewController.swift @@ -7,6 +7,7 @@ // import Combine import Foundation +import CoreLocation import SwiftUI class SettingsPrivacyViewController: UIViewController { @@ -17,6 +18,8 @@ class SettingsPrivacyViewController: UIViewController { }() private var cancellables: Set = [] + + private let locationManager = CLLocationManager() override func viewDidLoad() { super.viewDidLoad() @@ -70,15 +73,13 @@ class SettingsPrivacyViewController: UIViewController { } private func updateView() { -// let isLocationAllowed: Bool -// switch LocationManager.shared.authorizationStatus { -// case .authorizedWhenInUse, .authorizedAlways: -// isLocationAllowed = true -// default: -// isLocationAllowed = false -// } - - let isLocationAllowed = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isLocationAllowed) + let isLocationAllowed: Bool + switch locationManager.authorizationStatus { + case .authorizedWhenInUse, .authorizedAlways: + isLocationAllowed = true + default: + isLocationAllowed = false + } hostingController.rootView.viewModel.isLocationAllowed = isLocationAllowed let isAnalyticsEnabled = UserDefaults.standard.bool(forKey: Constants.UserDefaults.isAnalyticsEnabled) diff --git a/TCAT/Views/SettingsPrivacyView.swift b/TCAT/Views/SettingsPrivacyView.swift index 5dfd4cd7..c74f81aa 100644 --- a/TCAT/Views/SettingsPrivacyView.swift +++ b/TCAT/Views/SettingsPrivacyView.swift @@ -11,7 +11,7 @@ import SwiftUI class SettingsPrivacyViewModel: ObservableObject { @Published var isLocationAllowed: Bool = false - @Published var isAnalyticsEnabled: Bool = false + @Published var isAnalyticsEnabled: Bool = true @Published var isNotificationsAllowed: Bool = false } From 9d733310015351e3c8000c99ee930386f8b95506 Mon Sep 17 00:00:00 2001 From: ako28 Date: Wed, 9 Apr 2025 14:47:35 -0400 Subject: [PATCH 7/8] implemented privacy view update on notification authorization status change --- .../SettingsPrivacyViewController.swift | 26 ++++++++++++++++++- TCAT/Supporting/Constants.swift | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/TCAT/Controllers/SettingsPrivacyViewController.swift b/TCAT/Controllers/SettingsPrivacyViewController.swift index 6f0b05b1..a39d9436 100644 --- a/TCAT/Controllers/SettingsPrivacyViewController.swift +++ b/TCAT/Controllers/SettingsPrivacyViewController.swift @@ -18,7 +18,7 @@ class SettingsPrivacyViewController: UIViewController { }() private var cancellables: Set = [] - + private let locationManager = CLLocationManager() override func viewDidLoad() { @@ -60,6 +60,13 @@ class SettingsPrivacyViewController: UIViewController { 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() { @@ -73,6 +80,7 @@ class SettingsPrivacyViewController: UIViewController { } private func updateView() { + // location update let isLocationAllowed: Bool switch locationManager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways: @@ -82,6 +90,22 @@ class SettingsPrivacyViewController: UIViewController { } 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/Supporting/Constants.swift b/TCAT/Supporting/Constants.swift index 375033d1..36ba1486 100644 --- a/TCAT/Supporting/Constants.swift +++ b/TCAT/Supporting/Constants.swift @@ -308,6 +308,7 @@ struct Constants { /// Analytics variables for Settings Page / Privacy static let isAnalyticsEnabled = "isAnalyticsEnabled" static let isLocationAllowed = "isLocationAllowed" + static let isNotificationsAllowed = "isNotificationsAllowed" static let activeIcon = "activeIcon" } From 132b454c6bfd91e50684b9cedadf7f9bc3ce71fc Mon Sep 17 00:00:00 2001 From: ako28 Date: Wed, 9 Apr 2025 17:55:19 -0400 Subject: [PATCH 8/8] Minor change from PROD to DEV server --- TCAT/Supporting/TransitEnvironment.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TCAT/Supporting/TransitEnvironment.swift b/TCAT/Supporting/TransitEnvironment.swift index 2d7724c4..468a3c19 100644 --- a/TCAT/Supporting/TransitEnvironment.swift +++ b/TCAT/Supporting/TransitEnvironment.swift @@ -16,7 +16,7 @@ enum TransitEnvironment { #if DEBUG static let eateryURL = "EATERY_DEV_URL" static let googleMaps = "GOOGLE_MAPS_DEBUG" - static let transitURL = "TRANSIT_PROD_URL" + static let transitURL = "TRANSIT_DEV_URL" static let upliftURL = "UPLIFT_DEV_URL" #else static let eateryURL = "EATERY_PROD_URL"