From cf9e1fdcd25bb07d4545c7a702166b50853431ba Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Wed, 30 Oct 2024 17:11:37 -0400 Subject: [PATCH 1/7] Updated and created a new settings page with IOS like dropdown menus --- .../Settings/SettingsViewController.swift | 166 ++++++++++++------ 1 file changed, 108 insertions(+), 58 deletions(-) diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index ffd31035..399167c2 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -7,11 +7,10 @@ // import UIKit - import AppCenterAnalytics class SettingsViewController: BaseTableViewController { - + private enum Section: Int, CaseIterable { case general = 0 case audio = 1 @@ -37,7 +36,7 @@ class SettingsViewController: BaseTableViewController { IndexPath(row: 3, section: Section.general.rawValue): "volumeSettings", IndexPath(row: 4, section: Section.general.rawValue): "manageDevices", IndexPath(row: 5, section: Section.general.rawValue): "siriShortcuts", - + IndexPath(row: 0, section: Section.audio.rawValue): "mixAudio", IndexPath(row: CalloutsRow.all.rawValue, section: Section.callouts.rawValue): "allCallouts", @@ -45,7 +44,7 @@ class SettingsViewController: BaseTableViewController { IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue): "mobilityCallouts", IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue): "beaconCallouts", IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue): "shakeCallouts", - + IndexPath(row: 0, section: Section.streetPreview.rawValue): "streetPreview", IndexPath(row: 0, section: Section.troubleshooting.rawValue): "troubleshooting", IndexPath(row: 0, section: Section.about.rawValue): "about", @@ -62,6 +61,19 @@ class SettingsViewController: BaseTableViewController { // MARK: Properties @IBOutlet weak var largeBannerContainerView: UIView! + + private var expandedSections: Set = [] + + // Section Descriptions + private static let sectionDescriptions: [Section: String] = [ + .general: "General settings for the app.", + .audio: "Control how audio interacts with other media.", + .callouts: "Manage the callouts that help navigate.", + .streetPreview: "Settings for including unnamed roads.", + .troubleshooting: "Options for troubleshooting the app.", + .about: "Information about the app.", + .telemetry: "Manage data collection and privacy." + ] // MARK: View Life Cycle @@ -73,6 +85,7 @@ class SettingsViewController: BaseTableViewController { GDATelemetry.trackScreenView("settings") self.title = GDLocalizedString("settings.screen_title") + expandedSections = [] } override func numberOfSections(in tableView: UITableView) -> Int { @@ -83,59 +96,60 @@ class SettingsViewController: BaseTableViewController { guard let sectionType = Section(rawValue: section) else { return 0 } switch sectionType { - case .general: return 6 - case .audio: return 1 - case .callouts: return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 1 - case .streetPreview: return 1 - case .troubleshooting: return 1 - case .about: return 1 - case .telemetry: return 1 + case .general: return expandedSections.contains(section) ? 6 : 0 + case .audio: return expandedSections.contains(section) ? 1 : 0 + case .callouts: + return SettingsContext.shared.automaticCalloutsEnabled && expandedSections.contains(section) ? 5 : 0 + case .streetPreview, .troubleshooting, .about, .telemetry: + return expandedSections.contains(section) ? 1 : 0 } } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return expandedSections.contains(indexPath.section) ? UITableView.automaticDimension : 0 + } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard expandedSections.contains(indexPath.section) else { + return UITableViewCell() + } + let identifier = SettingsViewController.cellIdentifiers[indexPath] + let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) - guard let sectionType = Section(rawValue: indexPath.section) else { - return tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) - } - - switch sectionType { + switch Section(rawValue: indexPath.section) { case .callouts: - let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) as! CalloutSettingsCellView - cell.delegate = self - - if let rowType = CalloutsRow(rawValue: indexPath.row) { - switch rowType { - case .all: cell.type = .all - case .poi: cell.type = .poi - case .mobility: cell.type = .mobility - case .beacon: cell.type = .beacon - case .shake: cell.type = .shake - } - } - - return cell - + configureCalloutCell(cell as! CalloutSettingsCellView, at: indexPath) case .telemetry: - let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) as! TelemetrySettingsTableViewCell - cell.parent = self - - return cell - + (cell as! TelemetrySettingsTableViewCell).parent = self case .audio: - let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) as! MixAudioSettingCell - cell.delegate = self - return cell - + (cell as! MixAudioSettingCell).delegate = self default: - return tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) + break } + return cell } - // MARK: UITableViewDataSource + private func configureCalloutCell(_ cell: CalloutSettingsCellView, at indexPath: IndexPath) { + cell.delegate = self + if let rowType = CalloutsRow(rawValue: indexPath.row) { + switch rowType { + case .all: + cell.type = .all + case .poi: + cell.type = .poi + case .mobility: + cell.type = .mobility + case .beacon: + cell.type = .beacon + case .shake: + cell.type = .shake + } + } + } + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { guard let sectionType = Section(rawValue: section) else { return nil } @@ -153,6 +167,11 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { guard let sectionType = Section(rawValue: section) else { return nil } + + if expandedSections.contains(section) { + return SettingsViewController.sectionDescriptions[sectionType] + } + switch sectionType { case .audio: return GDLocalizedString("settings.audio.mix_with_others.description") case .streetPreview: return GDLocalizedString("preview.include_unnamed_roads.subtitle") @@ -160,30 +179,66 @@ class SettingsViewController: BaseTableViewController { default: return nil } } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + guard let header = view as? UITableViewHeaderFooterView else { return } + header.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTap(_:)))) + header.tag = section + + + header.textLabel?.textColor = .white + header.textLabel?.font = UIFont.boldSystemFont(ofSize: 18) + + + header.contentView.backgroundColor = UIColor(named: "HeaderBackgroundColor") + header.layer.borderColor = UIColor.clear.cgColor + header.layer.borderWidth = 0.0 + header.layer.cornerRadius = 8.0 + header.layer.masksToBounds = true + + header.contentView.layoutMargins = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) + + // ">" icon + let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) + chevronImageView.tintColor = .white + chevronImageView.translatesAutoresizingMaskIntoConstraints = false + + header.contentView.addSubview(chevronImageView) + + NSLayoutConstraint.activate([ + chevronImageView.trailingAnchor.constraint(equalTo: header.contentView.trailingAnchor, constant: -15), + chevronImageView.centerYAnchor.constraint(equalTo: header.contentView.centerYAnchor), + chevronImageView.widthAnchor.constraint(equalToConstant: 20), + chevronImageView.heightAnchor.constraint(equalToConstant: 20) + ]) + } + + @objc private func handleHeaderTap(_ gesture: UITapGestureRecognizer) { + guard let header = gesture.view as? UITableViewHeaderFooterView else { return } + let section = header.tag + + if expandedSections.contains(section) { + expandedSections.remove(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + } else { + expandedSections.insert(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + } + } } extension SettingsViewController: MixAudioSettingCellDelegate { func onSettingValueChanged(_ cell: MixAudioSettingCell, settingSwitch: UISwitch) { - // Note: The UI for this setting is "Enable Media Controls" but the setting is stored as - // "Mixes with Others" (the inverse of "Enable Media Controls") - guard settingSwitch.isOn else { - // If the setting switch is now off, the user disabled media controls. This doesn't - // require a warning alert, so just set mixesWithOthers to true and return. updateSetting(true) return } - - // Otherwise, the user is turning on media controls, so we need to show a warning to make sure - // they understand what this change means in terms of how other audio apps will stop Soundscape - // from playing. This warning was added based on bug bash feedback on 12/3/20. - // Show an alert indicating that the user can download an enhanced version of the voice in Settings + let alert = UIAlertController(title: GDLocalizedString("general.alert.confirmation_title"), message: GDLocalizedString("setting.audio.mix_with_others.confirmation"), preferredStyle: .alert) let mixAction = UIAlertAction(title: GDLocalizedString("settings.audio.mix_with_others.title"), style: .default) { [weak self] (_) in - // Make the setting switch - turn off mixesWithOthers self?.updateSetting(false) self?.focusOnCell(cell) } @@ -191,12 +246,8 @@ extension SettingsViewController: MixAudioSettingCellDelegate { alert.preferredAction = mixAction alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { [weak self] (_) in - // Toggle the setting back off settingSwitch.isOn = false - - // Track that the user decided not to enable media controls GDATelemetry.track("settings.mix_audio.cancel", with: ["context": "app_settings"]) - self?.focusOnCell(cell) })) @@ -241,5 +292,4 @@ extension SettingsViewController: LargeBannerContainerView { largeBannerContainerView.setHeight(height) tableView.reloadData() } - } From 3309887518a6c6a6c9d7cb4b99bea5f77c995c5a Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Tue, 12 Nov 2024 20:07:18 -0500 Subject: [PATCH 2/7] Localized the Sections strings to EN-USA --- .../en-US.lproj/Localizable.strings | 42 +++++++++++-------- .../Settings/SettingsViewController.swift | 15 +++---- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings index c99c624e..88d6fc9e 100644 --- a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings +++ b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings @@ -380,21 +380,9 @@ /* Settings title, About Soundscape {NumberedPlaceholder="Soundscape"} */ "settings.about_app" = "About Soundscape"; -/* Settings title,General Settings */ -"settings.section.general" = "General Settings"; - /* Settings title,Units of Measure */ "settings.section.units" = "Units of Measure"; -/* Settings title, Troubleshooting */ -"settings.section.troubleshooting" = "Troubleshooting"; - -/* Settings title, About */ -"settings.section.about" = "About"; - -/* Settings title, Telemetry */ -"settings.section.telemetry" = "Telemetry"; - /* Settings title, Share Usage Data */ "settings.section.share_usage_data" = "Share Usage Data"; @@ -417,11 +405,34 @@ "settings.language.language_name" = "%1$@ (%2$@)"; //------------------------------------------------------------------------------ -// MARK: Settings (Audio) +// MARK: Settings (Sections) //------------------------------------------------------------------------------ +/* Settings title, General settings for the app */ +"settings.section.general" = "General settings for the app."; + /* The title for the screen which configures media controls (play/pause/next/etc) related settings */ -"settings.audio.media_controls" = "Media Controls"; +"settings.audio.media_controls" = "Control how audio interacts with other media."; + +/* Settings title, Manage the callouts that help navigate */ +"menu.manage_callouts" = "Manage the callouts that help navigate."; + +/* Settings title, Settings for including unnamed roads */ +"settings.section.street_preview" = "Settings for including unnamed roads."; + +/* Settings title, Options for troubleshooting the app */ +"settings.section.troubleshooting" = "Options for troubleshooting the app."; + +/* Settings title, Information about the app */ +"settings.section.about" = "Information about the app."; + +/* Settings title, Manage data collection and privacy */ +"settings.section.telemetry" = "Manage data collection and privacy."; + + +//------------------------------------------------------------------------------ +// MARK: Settings (Audio) +//------------------------------------------------------------------------------ /* A title for a toggle for an audio setting which determines if the current app's audio will be mixed with others. If ON, this app will output audio which could be played at the same time as other apps that play audio, such as another music player app. */ "settings.audio.mix_with_others.title" = "Enable Media Controls"; @@ -2150,9 +2161,6 @@ /* Title, Head Tracking Headphones, "head tracking headphones" is a term that refers to headphones which include sensors that track a person's head movements */ "menu.devices" = "Head Tracking Headphones"; -/* Title, Manage Callouts */ -"menu.manage_callouts" = "Manage Callouts"; - /* Title, Help and Tutorials */ "menu.help_and_tutorials" = "Help & Tutorials"; diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index 399167c2..746b5801 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -66,13 +66,13 @@ class SettingsViewController: BaseTableViewController { // Section Descriptions private static let sectionDescriptions: [Section: String] = [ - .general: "General settings for the app.", - .audio: "Control how audio interacts with other media.", - .callouts: "Manage the callouts that help navigate.", - .streetPreview: "Settings for including unnamed roads.", - .troubleshooting: "Options for troubleshooting the app.", - .about: "Information about the app.", - .telemetry: "Manage data collection and privacy." + .general: GDLocalizedString("settings.section.general"), + .audio: GDLocalizedString("settings.audio.media_controls"), + .callouts: GDLocalizedString("menu.manage_callouts"), + .streetPreview: GDLocalizedString("settings.section.street_preview"), + .troubleshooting: GDLocalizedString("settings.section.troubleshooting"), + .about: GDLocalizedString("settings.section.about"), + .telemetry: GDLocalizedString("settings.section.telemetry") ] // MARK: View Life Cycle @@ -293,3 +293,4 @@ extension SettingsViewController: LargeBannerContainerView { tableView.reloadData() } } + From 598f1094435fc0823cf289187e83fe1a2814866f Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Wed, 13 Nov 2024 19:14:39 -0500 Subject: [PATCH 3/7] Made new changes around to have each section on its own --- apps/ios/GuideDogs.xcodeproj/project.pbxproj | 32 +++++ .../AboutSettingsViewController.swift | 113 +++++++++++++++ .../AudioSettingsViewController.swift | 81 +++++++++++ .../CalloutSettingsViewController.swift | 122 ++++++++++++++++ .../Code/App/Settings/SettingsContext.swift | 1 + .../Settings/SettingsViewController.swift | 130 +++++++++++++++++- .../GeneralSettingsViewController.swift | 87 ++++++++++++ .../StreetPreviewSettingsViewController.swift | 61 ++++++++ .../TelemetrySettingsViewController.swift | 76 ++++++++++ ...roubleshootingSettingsViewController.swift | 109 +++++++++++++++ apps/ios/GuideDogs/WebViewController.swift | 27 ++++ 11 files changed, 832 insertions(+), 7 deletions(-) create mode 100644 apps/ios/GuideDogs/AboutSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/AudioSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/CalloutSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/GeneralSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/TelemetrySettingsViewController.swift create mode 100644 apps/ios/GuideDogs/TroubleshootingSettingsViewController.swift create mode 100644 apps/ios/GuideDogs/WebViewController.swift diff --git a/apps/ios/GuideDogs.xcodeproj/project.pbxproj b/apps/ios/GuideDogs.xcodeproj/project.pbxproj index c55f55c7..79c02b6b 100644 --- a/apps/ios/GuideDogs.xcodeproj/project.pbxproj +++ b/apps/ios/GuideDogs.xcodeproj/project.pbxproj @@ -662,6 +662,14 @@ 62F7A30C27B6080900C62390 /* InteractiveBeaconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F7A30B27B6080900C62390 /* InteractiveBeaconView.swift */; }; 62F7A30E27B6082A00C62390 /* InteractiveBeaconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F7A30D27B6082A00C62390 /* InteractiveBeaconViewModel.swift */; }; 6A4891BB2A5E66DE0002D146 /* ExternalNavigationApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4891BA2A5E66DE0002D146 /* ExternalNavigationApps.swift */; }; + 7FB733C82CE555D500C99D46 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */; }; + 7FB733CA2CE556EC00C99D46 /* AudioSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */; }; + 7FB733CC2CE5570A00C99D46 /* CalloutSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */; }; + 7FB733CE2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */; }; + 7FB733D02CE5575400C99D46 /* TroubleshootingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */; }; + 7FB733D22CE5577200C99D46 /* AboutSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */; }; + 7FB733D42CE5579500C99D46 /* TelemetrySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */; }; + 7FB733D62CE5650C00C99D46 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D52CE5650C00C99D46 /* WebViewController.swift */; }; 91172A732AD8D56D00E6E8E9 /* CoreGPX in Frameworks */ = {isa = PBXBuildFile; productRef = 91172A722AD8D56D00E6E8E9 /* CoreGPX */; }; 914BAAF32AD745E400CB2171 /* DestinationManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */; }; 914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */; }; @@ -1582,6 +1590,14 @@ 62F7A30B27B6080900C62390 /* InteractiveBeaconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveBeaconView.swift; sourceTree = ""; }; 62F7A30D27B6082A00C62390 /* InteractiveBeaconViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveBeaconViewModel.swift; sourceTree = ""; }; 6A4891BA2A5E66DE0002D146 /* ExternalNavigationApps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExternalNavigationApps.swift; path = Code/App/ExternalNavigationApps.swift; sourceTree = ""; }; + 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = ""; }; + 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSettingsViewController.swift; sourceTree = ""; }; + 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalloutSettingsViewController.swift; sourceTree = ""; }; + 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreetPreviewSettingsViewController.swift; sourceTree = ""; }; + 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TroubleshootingSettingsViewController.swift; sourceTree = ""; }; + 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettingsViewController.swift; sourceTree = ""; }; + 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetrySettingsViewController.swift; sourceTree = ""; }; + 7FB733D52CE5650C00C99D46 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DestinationManagerTest.swift; sourceTree = ""; }; 914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioEngineTest.swift; sourceTree = ""; }; 914DEBCD2A3CE6B9007B161C /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1902,6 +1918,14 @@ 287D3E8C22DE50340084B92B /* StatusTableViewController.swift */, C30EF61E2089475D00BEA785 /* VoiceSettingsTableViewController.swift */, B92EA05D27AC80E900127A9A /* SiriShortcutsTableViewController.swift */, + 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */, + 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */, + 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */, + 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */, + 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */, + 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */, + 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */, + 7FB733D52CE5650C00C99D46 /* WebViewController.swift */, ); name = Settings; sourceTree = ""; @@ -5730,6 +5754,7 @@ 62B11A3727ACAAC50094FE66 /* RoundedBackground.swift in Sources */, C37E33C423677EC00033D640 /* AlertType.swift in Sources */, 28E60D441FE4905F00EAB5C0 /* CompassDirection.swift in Sources */, + 7FB733D42CE5579500C99D46 /* TelemetrySettingsViewController.swift in Sources */, 319A3C152257C94C0098E94E /* StringDebug.swift in Sources */, 28F94F5E2485AE0A0010D2B3 /* BehaviorBase.swift in Sources */, 28A16134283D86E70081CFA4 /* TourWaypointDetail.swift in Sources */, @@ -5872,6 +5897,7 @@ 6202A2A6284A85FD00CC51DF /* LeftAccessoryText.swift in Sources */, 28AA35F026C1E82800CBD680 /* MarkerLoader.swift in Sources */, B93629B01FCDE25000BAF3A6 /* UIViewController+Extensions.swift in Sources */, + 7FB733D02CE5575400C99D46 /* TroubleshootingSettingsViewController.swift in Sources */, 28246D1A21ED372A004C8F09 /* LinkedList.swift in Sources */, C38841D52268FCC400F6DFA5 /* POI+Similarity.swift in Sources */, C363227D22DFB6B700715374 /* Heading+Order.swift in Sources */, @@ -5961,6 +5987,7 @@ 62E8BB2D24AE6BCA00DDBCB4 /* LocationDetailViewController.swift in Sources */, 28F0F9281F86A43000B6A64F /* DestinationTutorialBeaconPage.swift in Sources */, 62791781248EBCA4001D72F3 /* IntersectionDecisionPoint.swift in Sources */, + 7FB733C82CE555D500C99D46 /* GeneralSettingsViewController.swift in Sources */, 6249500526FBE26C008D842B /* MarkerEditViewRepresentable.swift in Sources */, 62E2C04D267055BE00F7CBE1 /* WaypointDetail.swift in Sources */, C37E33B223610A370033D640 /* GeolocationManagerSnoozeDelegate.swift in Sources */, @@ -6045,6 +6072,7 @@ 62CCED4B26E2CF3900A91E5A /* SoundscapeDocumentAlert.swift in Sources */, 28F94F68248EB2F40010D2B3 /* CalibratableDevice.swift in Sources */, C3B331242011615A00CD20F9 /* Address.swift in Sources */, + 7FB733D62CE5650C00C99D46 /* WebViewController.swift in Sources */, 3153765C22013CDA008445AD /* DistanceUnit.swift in Sources */, 288E5FDC268647C000F7D4CD /* WaypointDepartureCallout.swift in Sources */, 62CE14C925C0DB75001CBC0B /* KalmanFilter+HeadphoneCalibration.swift in Sources */, @@ -6092,6 +6120,7 @@ 2872D15A26DEE8B3000BE465 /* IntersectionGenerator.swift in Sources */, 62B9B413268A8C6F007F9DB9 /* BeaconView.swift in Sources */, 286DA3A81F9E6B3100C34C9C /* BorderedIcon.swift in Sources */, + 7FB733D22CE5577200C99D46 /* AboutSettingsViewController.swift in Sources */, 28D07BFA20B7103700F314C5 /* GenericLocationSearchProvider.swift in Sources */, 62EACA2126697ED400DBDECC /* WaypointDetailAnnotation.swift in Sources */, 2873D64B211E05250034A297 /* LaunchHelper.swift in Sources */, @@ -6176,6 +6205,7 @@ B9E63D0B26FE735600CCE4ED /* CloudKeyValueStore+Routes.swift in Sources */, 625F8524243BDAD10085AE05 /* UserActivityManager.swift in Sources */, 624DF68B27BED80C000A634C /* AuthorizationStatus.swift in Sources */, + 7FB733CC2CE5570A00C99D46 /* CalloutSettingsViewController.swift in Sources */, 2896378526D70CE7001694C0 /* TableHeaderCell.swift in Sources */, 3153765521FF1DFB008445AD /* CodeableDirection.swift in Sources */, 620BC33F25F2CC98007DBA29 /* HeadphoneMotionManagerStatus.swift in Sources */, @@ -6198,6 +6228,7 @@ 288E5FE0269504D500F7D4CD /* ConditionalAccessibilityAction.swift in Sources */, 624C96BF285299AD001F84F8 /* IdentifiableAnnotation.swift in Sources */, 2817389E22BBD7AF00EFDD62 /* Result+Extensions.swift in Sources */, + 7FB733CA2CE556EC00C99D46 /* AudioSettingsViewController.swift in Sources */, C3DA72C2224D41EC0062DB81 /* RequestToken.swift in Sources */, 28863A7024C8DA3100194458 /* LayeredSound.swift in Sources */, 282A807023F7240A00993B7D /* ObjCExceptionHandling.m in Sources */, @@ -6223,6 +6254,7 @@ 283F160B202233A0009C2FFA /* POICalloutProtocol.swift in Sources */, 620FB1D0260D35A600B9E0E5 /* ThreadSafeHeadphoneCalibrator.swift in Sources */, 626BB58E2681554400E5355A /* View+Extensions.swift in Sources */, + 7FB733CE2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift in Sources */, 2800ABF12654427300B2622F /* AuthoredActivityCell.swift in Sources */, 624DF68727BED7CE000A634C /* AuthorizationProvider.swift in Sources */, 283C058023DB6DBF0086C01F /* SynchronouslyGeneratedSound.swift in Sources */, diff --git a/apps/ios/GuideDogs/AboutSettingsViewController.swift b/apps/ios/GuideDogs/AboutSettingsViewController.swift new file mode 100644 index 00000000..7766b11f --- /dev/null +++ b/apps/ios/GuideDogs/AboutSettingsViewController.swift @@ -0,0 +1,113 @@ +/* +import UIKit + +class AboutSettingsViewController: UITableViewController { + + private enum AboutRow: Int, CaseIterable { + case whatsNew = 0 + case privacyPolicy = 1 + case serviceAgreement = 2 + case thirdPartyNotices = 3 + case copyrightNotices = 4 + } + + private static let cellIdentifiers: [AboutRow: String] = [ + .whatsNew: "whatsNew", + .privacyPolicy: "privacyPolicy", + .serviceAgreement: "serviceAgreement", + .thirdPartyNotices: "thirdPartyNotices", + .copyrightNotices: "copyrightNotices" + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.section.about") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "default") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return AboutRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let rowType = AboutRow(rawValue: indexPath.row) else { + return UITableViewCell() + } + + let identifier = AboutSettingsViewController.cellIdentifiers[rowType] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) + cell.accessoryType = .disclosureIndicator + + switch rowType { + case .whatsNew: + cell.textLabel?.text = GDLocalizedString("settings.about.title.whats_new") + case .privacyPolicy: + cell.textLabel?.text = GDLocalizedString("settings.about.title.privacy") + case .serviceAgreement: + cell.textLabel?.text = GDLocalizedString("settings.about.title.service_agreement") + case .thirdPartyNotices: + cell.textLabel?.text = GDLocalizedString("settings.about.title.third_party") + case .copyrightNotices: + cell.textLabel?.text = GDLocalizedString("settings.about.title.copyright") + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let rowType = AboutRow(rawValue: indexPath.row) else { return } + + switch rowType { + case .whatsNew: + navigateToWebView(withTitle: GDLocalizedString("settings.about.title.whats_new"), url: "https://example.com/whats-new") + case .privacyPolicy: + navigateToWebView(withTitle: GDLocalizedString("settings.about.title.privacy"), url: "https://example.com/privacy-policy") + case .serviceAgreement: + navigateToWebView(withTitle: GDLocalizedString("settings.about.title.service_agreement"), url: "https://example.com/service-agreement") + case .thirdPartyNotices: + navigateToWebView(withTitle: GDLocalizedString("settings.about.title.third_party"), url: "https://example.com/third-party-notices") + case .copyrightNotices: + navigateToWebView(withTitle: GDLocalizedString("settings.about.title.copyright"), url: "https://example.com/copyright-notices") + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + // MARK: - Navigation + + private func navigateToWebView(withTitle title: String, url: String) { + let webViewController = WebViewController() + webViewController.title = title + webViewController.urlString = url + navigationController?.pushViewController(webViewController, animated: true) + } +} +*/ +import UIKit + +class AboutSettingsViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Register the "whatsNew" cell identifier to avoid dequeuing errors + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "whatsNew") + + self.title = GDLocalizedString("settings.section.about") + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 // Assuming one row for demonstration; adjust based on your content + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "whatsNew", for: indexPath) + cell.textLabel?.text = GDLocalizedString("about.whats_new") // Customize cell content as needed + return cell + } +} + diff --git a/apps/ios/GuideDogs/AudioSettingsViewController.swift b/apps/ios/GuideDogs/AudioSettingsViewController.swift new file mode 100644 index 00000000..8dffca3b --- /dev/null +++ b/apps/ios/GuideDogs/AudioSettingsViewController.swift @@ -0,0 +1,81 @@ +import UIKit + +class AudioSettingsViewController: UITableViewController { + + // MARK: - Audio Settings Enum + private enum AudioRow: Int, CaseIterable { + case mixAudio = 0 + } + + private let audioItems = [ + "Mix Audio" // Placeholder for the Mix Audio setting + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.audio.media_controls") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "AudioCell") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 // Only one section for audio settings + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return AudioRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "AudioCell", for: indexPath) + + if let rowType = AudioRow(rawValue: indexPath.row) { + cell.textLabel?.text = audioItems[indexPath.row] + + switch rowType { + case .mixAudio: + // Add a switch control to the Mix Audio row + let switchControl = UISwitch() + switchControl.isOn = SettingsContext.shared.audioSessionMixesWithOthers + switchControl.addTarget(self, action: #selector(mixAudioSwitchToggled(_:)), for: .valueChanged) + cell.accessoryView = switchControl + } + } + + return cell + } + + // MARK: - Mix Audio Toggle Logic + + @objc private func mixAudioSwitchToggled(_ sender: UISwitch) { + if sender.isOn { + // Show confirmation alert if turning on "Mix Audio" + let alert = UIAlertController(title: GDLocalizedString("general.alert.confirmation_title"), + message: GDLocalizedString("setting.audio.mix_with_others.confirmation"), + preferredStyle: .alert) + + let mixAction = UIAlertAction(title: GDLocalizedString("settings.audio.mix_with_others.title"), style: .default) { [weak self] (_) in + self?.updateMixAudioSetting(true) + } + alert.addAction(mixAction) + alert.preferredAction = mixAction + + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { (_) in + sender.isOn = false + })) + + present(alert, animated: true) + } else { + updateMixAudioSetting(false) + } + } + + private func updateMixAudioSetting(_ newValue: Bool) { + SettingsContext.shared.audioSessionMixesWithOthers = newValue + AppContext.shared.audioEngine.mixWithOthers = newValue + + GDATelemetry.track("settings.mix_audio", + with: ["value": "\(SettingsContext.shared.audioSessionMixesWithOthers)", + "context": "app_settings"]) + } +} + diff --git a/apps/ios/GuideDogs/CalloutSettingsViewController.swift b/apps/ios/GuideDogs/CalloutSettingsViewController.swift new file mode 100644 index 00000000..f54188d9 --- /dev/null +++ b/apps/ios/GuideDogs/CalloutSettingsViewController.swift @@ -0,0 +1,122 @@ +import UIKit + +class CalloutSettingsViewController: UITableViewController { + + private enum CalloutsRow: Int, CaseIterable { + case all = 0 + case poi = 1 + case mobility = 2 + case beacon = 3 + case shake = 4 + } + + private static let cellIdentifiers: [CalloutsRow: String] = [ + .all: "allCallouts", + .poi: "poiCallouts", + .mobility: "mobilityCallouts", + .beacon: "beaconCallouts", + .shake: "shakeCallouts" + ] + + // Properties to store the states of each callout setting + private var automaticCalloutsEnabled = SettingsContext.shared.automaticCalloutsEnabled + private var poiCalloutsEnabled = true + private var mobilityCalloutsEnabled = true + private var beaconCalloutsEnabled = true + private var shakeCalloutsEnabled = true + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("menu.manage_callouts") + + // Register the cells + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "allCallouts") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "poiCallouts") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "mobilityCallouts") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "beaconCallouts") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "shakeCallouts") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return CalloutsRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let rowType = CalloutsRow(rawValue: indexPath.row) else { + return UITableViewCell() + } + + let identifier = CalloutSettingsViewController.cellIdentifiers[rowType] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) + cell.selectionStyle = .none + + switch rowType { + case .all: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.all") + let switchView = UISwitch(frame: .zero) + switchView.setOn(automaticCalloutsEnabled, animated: true) + switchView.tag = CalloutsRow.all.rawValue + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + cell.accessoryView = switchView + case .poi: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.poi") + let switchView = UISwitch(frame: .zero) + switchView.setOn(poiCalloutsEnabled, animated: true) + switchView.tag = CalloutsRow.poi.rawValue + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + cell.accessoryView = switchView + case .mobility: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.mobility") + let switchView = UISwitch(frame: .zero) + switchView.setOn(mobilityCalloutsEnabled, animated: true) + switchView.tag = CalloutsRow.mobility.rawValue + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + cell.accessoryView = switchView + case .beacon: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.beacon") + let switchView = UISwitch(frame: .zero) + switchView.setOn(beaconCalloutsEnabled, animated: true) + switchView.tag = CalloutsRow.beacon.rawValue + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + cell.accessoryView = switchView + case .shake: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.shake") + let switchView = UISwitch(frame: .zero) + switchView.setOn(shakeCalloutsEnabled, animated: true) + switchView.tag = CalloutsRow.shake.rawValue + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + cell.accessoryView = switchView + } + + return cell + } + + // Toggle actions for each callout setting switch + @objc private func toggleCalloutSetting(_ sender: UISwitch) { + switch CalloutsRow(rawValue: sender.tag) { + case .all: + automaticCalloutsEnabled = sender.isOn + SettingsContext.shared.automaticCalloutsEnabled = sender.isOn + case .poi: + poiCalloutsEnabled = sender.isOn + case .mobility: + mobilityCalloutsEnabled = sender.isOn + case .beacon: + beaconCalloutsEnabled = sender.isOn + case .shake: + shakeCalloutsEnabled = sender.isOn + case .none: + break + } + + // Update table view based on automatic callouts state + if sender.tag == CalloutsRow.all.rawValue { + tableView.reloadData() + } + } +} + diff --git a/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift b/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift index 8f4e54ae..d2c1f09c 100644 --- a/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift +++ b/apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift @@ -487,3 +487,4 @@ extension SettingsContext: AutoCalloutSettingsProvider { } } } + diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index 746b5801..e1f3d8c5 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -6,6 +6,114 @@ // Licensed under the MIT License. // +// +// SettingsViewController.swift +// Soundscape +// +import UIKit +import AppCenterAnalytics + +class SettingsViewController: BaseTableViewController { + + @IBOutlet weak var largeBannerContainerView: UIView! // IBOutlet for the banner container view + + private enum Section: Int, CaseIterable { + case general = 0 + case audio + case callouts + case streetPreview + case troubleshooting + case about + case telemetry + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.title = GDLocalizedString("settings.screen_title") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell") + + GDLogActionInfo("Opened 'Settings'") + GDATelemetry.trackScreenView("settings") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Section.allCases.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 // Each section has one row to navigate to its own view controller + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell", for: indexPath) + cell.accessoryType = .disclosureIndicator // Show an arrow to indicate navigation + + switch Section(rawValue: indexPath.section) { + case .general: + cell.textLabel?.text = GDLocalizedString("settings.section.general") + case .audio: + cell.textLabel?.text = GDLocalizedString("settings.audio.media_controls") + case .callouts: + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts") + case .streetPreview: + cell.textLabel?.text = GDLocalizedString("settings.section.street_preview") + case .troubleshooting: + cell.textLabel?.text = GDLocalizedString("settings.section.troubleshooting") + case .about: + cell.textLabel?.text = GDLocalizedString("settings.section.about") + case .telemetry: + cell.textLabel?.text = GDLocalizedString("settings.section.telemetry") + default: + cell.textLabel?.text = "" + } + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + let section = Section(rawValue: indexPath.section) + let viewController: UIViewController + + switch section { + case .general: + viewController = GeneralSettingsViewController() + case .audio: + viewController = AudioSettingsViewController() + case .callouts: + viewController = CalloutSettingsViewController() + case .streetPreview: + viewController = StreetPreviewViewController() + case .troubleshooting: + viewController = TroubleshootingViewController() + case .about: + viewController = AboutSettingsViewController() // Navigates to AboutSettingsViewController + case .telemetry: + viewController = TelemetrySettingsViewController() + default: + return + } + + navigationController?.pushViewController(viewController, animated: true) + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch Section(rawValue: section) { + case .general: return GDLocalizedString("settings.section.general") + case .audio: return GDLocalizedString("settings.audio.media_controls") + case .callouts: return GDLocalizedString("menu.manage_callouts") + case .about: return GDLocalizedString("settings.section.about") + case .streetPreview: return GDLocalizedString("preview.title") + case .troubleshooting: return GDLocalizedString("settings.section.troubleshooting") + case .telemetry: return GDLocalizedString("settings.section.telemetry") + default: return nil + } + } +} + + +/* import UIKit import AppCenterAnalytics @@ -95,16 +203,20 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let sectionType = Section(rawValue: section) else { return 0 } - switch sectionType { - case .general: return expandedSections.contains(section) ? 6 : 0 - case .audio: return expandedSections.contains(section) ? 1 : 0 - case .callouts: - return SettingsContext.shared.automaticCalloutsEnabled && expandedSections.contains(section) ? 5 : 0 - case .streetPreview, .troubleshooting, .about, .telemetry: - return expandedSections.contains(section) ? 1 : 0 + if expandedSections.contains(section) { + switch sectionType { + case .general: return 6 + case .audio: return 1 + case .callouts: + return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 0 + case .streetPreview, .troubleshooting, .about, .telemetry: + return 1 + } } + return 0 } + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return expandedSections.contains(indexPath.section) ? UITableView.automaticDimension : 0 } @@ -220,11 +332,14 @@ class SettingsViewController: BaseTableViewController { if expandedSections.contains(section) { expandedSections.remove(section) tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section collapsed") } else { expandedSections.insert(section) tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section expanded") } } + } extension SettingsViewController: MixAudioSettingCellDelegate { @@ -294,3 +409,4 @@ extension SettingsViewController: LargeBannerContainerView { } } +*/ diff --git a/apps/ios/GuideDogs/GeneralSettingsViewController.swift b/apps/ios/GuideDogs/GeneralSettingsViewController.swift new file mode 100644 index 00000000..ba622b37 --- /dev/null +++ b/apps/ios/GuideDogs/GeneralSettingsViewController.swift @@ -0,0 +1,87 @@ +import UIKit + +class GeneralSettingsViewController: UITableViewController { + + private enum GeneralRow: Int, CaseIterable { + case languageAndRegion = 0 + case voice + case beaconSettings + case volumeSettings + case manageDevices + case siriShortcuts + } + + // Row labels for each setting option in the General section + private let generalItems = [ + "Language & Region", + "Voice", + "Beacon Settings", + "Volume Settings", + "Manage Devices", + "Siri Shortcuts" + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.section.general") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "GeneralCell") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 // Only one section for general items + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return GeneralRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "GeneralCell", for: indexPath) + + if let rowType = GeneralRow(rawValue: indexPath.row) { + cell.textLabel?.text = generalItems[indexPath.row] + + // Customize cells if needed (e.g., add switches or controls) + switch rowType { + case .languageAndRegion, .voice, .beaconSettings, .manageDevices, .siriShortcuts: + cell.accessoryType = .disclosureIndicator // These would open additional settings screens + case .volumeSettings: + // Example: Adding a slider for volume control + let volumeSlider = UISlider() + volumeSlider.value = 0.5 // Default value; adjust based on saved settings + cell.accessoryView = volumeSlider + } + } + + return cell + } + + // Handle row selection + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + if let rowType = GeneralRow(rawValue: indexPath.row) { + switch rowType { + case .languageAndRegion: + // Navigate to Language & Region settings screen + print("Open Language & Region settings") + case .voice: + // Navigate to Voice settings screen + print("Open Voice settings") + case .beaconSettings: + // Navigate to Beacon Settings screen + print("Open Beacon Settings") + case .manageDevices: + // Navigate to Manage Devices settings screen + print("Open Manage Devices settings") + case .siriShortcuts: + // Navigate to Siri Shortcuts settings screen + print("Open Siri Shortcuts settings") + case .volumeSettings: + // Volume setting already has a slider; no further action required here + break + } + } + } +} + diff --git a/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift b/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift new file mode 100644 index 00000000..54fdc136 --- /dev/null +++ b/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift @@ -0,0 +1,61 @@ +import UIKit + +class StreetPreviewViewController: UITableViewController { + + private enum StreetPreviewRow: Int, CaseIterable { + case includeUnnamedRoads = 0 + } + + private static let cellIdentifiers: [StreetPreviewRow: String] = [ + .includeUnnamedRoads: "streetPreviewIncludeUnnamedRoads" + ] + + // Property to hold the current state of the "Include Unnamed Roads" setting + private var includeUnnamedRoads = SettingsContext.shared.previewIntersectionsIncludeUnnamedRoads + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.section.street_preview") + + // Register the specific cell identifier + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "streetPreviewIncludeUnnamedRoads") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return StreetPreviewRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let rowType = StreetPreviewRow(rawValue: indexPath.row) else { + return UITableViewCell() + } + + let identifier = StreetPreviewViewController.cellIdentifiers[rowType] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) + cell.selectionStyle = .none + + switch rowType { + case .includeUnnamedRoads: + cell.textLabel?.text = GDLocalizedString("preview.include_unnamed_roads.subtitle") + let switchView = UISwitch(frame: .zero) + switchView.setOn(includeUnnamedRoads, animated: true) + switchView.tag = StreetPreviewRow.includeUnnamedRoads.rawValue + switchView.addTarget(self, action: #selector(toggleIncludeUnnamedRoads(_:)), for: .valueChanged) + cell.accessoryView = switchView + } + + return cell + } + + // Action for the "Include Unnamed Roads" switch + @objc private func toggleIncludeUnnamedRoads(_ sender: UISwitch) { + includeUnnamedRoads = sender.isOn + SettingsContext.shared.previewIntersectionsIncludeUnnamedRoads = sender.isOn + // Optionally, post a notification or perform any additional actions when this setting changes + } +} + diff --git a/apps/ios/GuideDogs/TelemetrySettingsViewController.swift b/apps/ios/GuideDogs/TelemetrySettingsViewController.swift new file mode 100644 index 00000000..3187f4e3 --- /dev/null +++ b/apps/ios/GuideDogs/TelemetrySettingsViewController.swift @@ -0,0 +1,76 @@ +import UIKit + +class TelemetrySettingsViewController: UITableViewController { + + private enum TelemetryRow: Int, CaseIterable { + case telemetryOptOut = 0 + } + + private static let cellIdentifiers: [TelemetryRow: String] = [ + .telemetryOptOut: "telemetryOptOut" + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.section.telemetry") + + // Register the specific cell identifier + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "telemetryOptOut") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return TelemetryRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let rowType = TelemetryRow(rawValue: indexPath.row) else { + return UITableViewCell() + } + + let identifier = TelemetrySettingsViewController.cellIdentifiers[rowType] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) + + switch rowType { + case .telemetryOptOut: + cell.textLabel?.text = GDLocalizedString("settings.section.share_usage_data") + cell.accessoryType = .none + + let telemetrySwitch = UISwitch() + telemetrySwitch.isOn = !SettingsContext.shared.telemetryOptout + telemetrySwitch.addTarget(self, action: #selector(telemetrySwitchChanged(_:)), for: .valueChanged) + cell.accessoryView = telemetrySwitch + } + + return cell + } + + // MARK: - Action + + @objc private func telemetrySwitchChanged(_ sender: UISwitch) { + let isOptedOut = !sender.isOn + SettingsContext.shared.telemetryOptout = isOptedOut + + let alertTitle = GDLocalizedString("settings.telemetry.optout.alert_title") + let alertMessage = GDLocalizedString("settings.telemetry.optout.alert_message") + + if isOptedOut { + let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.confirm"), style: .default, handler: { _ in + SettingsContext.shared.telemetryOptout = true + sender.isOn = false + })) + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { _ in + sender.isOn = true + SettingsContext.shared.telemetryOptout = false + })) + self.present(alert, animated: true, completion: nil) + } else { + SettingsContext.shared.telemetryOptout = false + } + } +} + diff --git a/apps/ios/GuideDogs/TroubleshootingSettingsViewController.swift b/apps/ios/GuideDogs/TroubleshootingSettingsViewController.swift new file mode 100644 index 00000000..f0b39035 --- /dev/null +++ b/apps/ios/GuideDogs/TroubleshootingSettingsViewController.swift @@ -0,0 +1,109 @@ +import UIKit + +class TroubleshootingViewController: UITableViewController { + + private enum TroubleshootingRow: Int, CaseIterable { + case checkAudio = 0 + case clearMapData = 1 + } + + private static let cellIdentifiers: [TroubleshootingRow: String] = [ + .checkAudio: "checkAudio", + .clearMapData: "clearMapData" + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.title = GDLocalizedString("settings.section.troubleshooting") + + // Register the specific cell identifiers + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "checkAudio") + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "clearMapData") + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return TroubleshootingRow.allCases.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let rowType = TroubleshootingRow(rawValue: indexPath.row) else { + return UITableViewCell() + } + + let identifier = TroubleshootingViewController.cellIdentifiers[rowType] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) + cell.selectionStyle = .default + + switch rowType { + case .checkAudio: + cell.textLabel?.text = GDLocalizedString("troubleshooting.check_audio") + cell.accessoryType = .disclosureIndicator + case .clearMapData: + cell.textLabel?.text = GDLocalizedString("settings.clear_data") + cell.accessoryType = .disclosureIndicator + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let rowType = TroubleshootingRow(rawValue: indexPath.row) else { + return + } + + switch rowType { + case .checkAudio: + checkAudioStatus() + case .clearMapData: + clearMapDataConfirmation() + } + + tableView.deselectRow(at: indexPath, animated: true) + } + + // MARK: - Actions + + private func checkAudioStatus() { + // Here, implement the functionality to check audio status. + // This could involve presenting an alert or a new screen with audio information. + let alert = UIAlertController( + title: GDLocalizedString("troubleshooting.check_audio"), + message: GDLocalizedString("troubleshooting.check_audio.explanation"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.ok"), style: .default)) + present(alert, animated: true) + } + + private func clearMapDataConfirmation() { + // Display a confirmation alert before clearing the map data + let alert = UIAlertController( + title: GDLocalizedString("settings.clear_cache.alert_title"), + message: GDLocalizedString("settings.clear_cache.alert_message"), + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel)) + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.confirm"), style: .destructive) { _ in + self.clearMapData() + }) + present(alert, animated: true) + } + + private func clearMapData() { + // Logic to clear stored map data goes here + // For example, removing cached files or data from UserDefaults if relevant. + // Notify the user after clearing data. + let confirmationAlert = UIAlertController( + title: GDLocalizedString("settings.clear_cache.alert_title"), + message: GDLocalizedString("settings.clear_cache.no_service.message"), + preferredStyle: .alert + ) + confirmationAlert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.ok"), style: .default)) + present(confirmationAlert, animated: true) + } +} + diff --git a/apps/ios/GuideDogs/WebViewController.swift b/apps/ios/GuideDogs/WebViewController.swift new file mode 100644 index 00000000..740824af --- /dev/null +++ b/apps/ios/GuideDogs/WebViewController.swift @@ -0,0 +1,27 @@ +import UIKit +import WebKit + +class WebViewController: UIViewController, WKNavigationDelegate { + + var urlString: String? + private var webView: WKWebView! + + override func loadView() { + webView = WKWebView() + webView.navigationDelegate = self + view = webView + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard let urlString = urlString, let url = URL(string: urlString) else { + print("Invalid URL.") + return + } + + webView.load(URLRequest(url: url)) + webView.allowsBackForwardNavigationGestures = true + } +} + From 2d848f0834613982ab020ae34437cb31a5c1e5d4 Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Thu, 14 Nov 2024 18:34:16 -0500 Subject: [PATCH 4/7] More changes to the new settings page --- apps/ios/GuideDogs.xcodeproj/project.pbxproj | 20 -- .../AboutSettingsViewController.swift | 113 ------ .../AudioSettingsViewController.swift | 81 ----- .../CalloutSettingsViewController.swift | 60 ++-- .../Settings/SettingsViewController.swift | 333 +++++++++++++++++- .../GeneralSettingsViewController.swift | 58 ++- .../StreetPreviewSettingsViewController.swift | 61 ---- .../TelemetrySettingsViewController.swift | 76 ---- apps/ios/GuideDogs/WebViewController.swift | 27 -- 9 files changed, 389 insertions(+), 440 deletions(-) delete mode 100644 apps/ios/GuideDogs/AboutSettingsViewController.swift delete mode 100644 apps/ios/GuideDogs/AudioSettingsViewController.swift delete mode 100644 apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift delete mode 100644 apps/ios/GuideDogs/TelemetrySettingsViewController.swift delete mode 100644 apps/ios/GuideDogs/WebViewController.swift diff --git a/apps/ios/GuideDogs.xcodeproj/project.pbxproj b/apps/ios/GuideDogs.xcodeproj/project.pbxproj index 79c02b6b..732e4960 100644 --- a/apps/ios/GuideDogs.xcodeproj/project.pbxproj +++ b/apps/ios/GuideDogs.xcodeproj/project.pbxproj @@ -663,13 +663,8 @@ 62F7A30E27B6082A00C62390 /* InteractiveBeaconViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F7A30D27B6082A00C62390 /* InteractiveBeaconViewModel.swift */; }; 6A4891BB2A5E66DE0002D146 /* ExternalNavigationApps.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A4891BA2A5E66DE0002D146 /* ExternalNavigationApps.swift */; }; 7FB733C82CE555D500C99D46 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */; }; - 7FB733CA2CE556EC00C99D46 /* AudioSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */; }; 7FB733CC2CE5570A00C99D46 /* CalloutSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */; }; - 7FB733CE2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */; }; 7FB733D02CE5575400C99D46 /* TroubleshootingSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */; }; - 7FB733D22CE5577200C99D46 /* AboutSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */; }; - 7FB733D42CE5579500C99D46 /* TelemetrySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */; }; - 7FB733D62CE5650C00C99D46 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB733D52CE5650C00C99D46 /* WebViewController.swift */; }; 91172A732AD8D56D00E6E8E9 /* CoreGPX in Frameworks */ = {isa = PBXBuildFile; productRef = 91172A722AD8D56D00E6E8E9 /* CoreGPX */; }; 914BAAF32AD745E400CB2171 /* DestinationManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */; }; 914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */; }; @@ -1591,13 +1586,8 @@ 62F7A30D27B6082A00C62390 /* InteractiveBeaconViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveBeaconViewModel.swift; sourceTree = ""; }; 6A4891BA2A5E66DE0002D146 /* ExternalNavigationApps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExternalNavigationApps.swift; path = Code/App/ExternalNavigationApps.swift; sourceTree = ""; }; 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = ""; }; - 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSettingsViewController.swift; sourceTree = ""; }; 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalloutSettingsViewController.swift; sourceTree = ""; }; - 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreetPreviewSettingsViewController.swift; sourceTree = ""; }; 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TroubleshootingSettingsViewController.swift; sourceTree = ""; }; - 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutSettingsViewController.swift; sourceTree = ""; }; - 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetrySettingsViewController.swift; sourceTree = ""; }; - 7FB733D52CE5650C00C99D46 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = ""; }; 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DestinationManagerTest.swift; sourceTree = ""; }; 914BAAFC2AD7483300CB2171 /* AudioEngineTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioEngineTest.swift; sourceTree = ""; }; 914DEBCD2A3CE6B9007B161C /* UnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1919,13 +1909,8 @@ C30EF61E2089475D00BEA785 /* VoiceSettingsTableViewController.swift */, B92EA05D27AC80E900127A9A /* SiriShortcutsTableViewController.swift */, 7FB733C72CE555D500C99D46 /* GeneralSettingsViewController.swift */, - 7FB733C92CE556EB00C99D46 /* AudioSettingsViewController.swift */, 7FB733CB2CE5570A00C99D46 /* CalloutSettingsViewController.swift */, - 7FB733CD2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift */, 7FB733CF2CE5575400C99D46 /* TroubleshootingSettingsViewController.swift */, - 7FB733D12CE5577200C99D46 /* AboutSettingsViewController.swift */, - 7FB733D32CE5579500C99D46 /* TelemetrySettingsViewController.swift */, - 7FB733D52CE5650C00C99D46 /* WebViewController.swift */, ); name = Settings; sourceTree = ""; @@ -5754,7 +5739,6 @@ 62B11A3727ACAAC50094FE66 /* RoundedBackground.swift in Sources */, C37E33C423677EC00033D640 /* AlertType.swift in Sources */, 28E60D441FE4905F00EAB5C0 /* CompassDirection.swift in Sources */, - 7FB733D42CE5579500C99D46 /* TelemetrySettingsViewController.swift in Sources */, 319A3C152257C94C0098E94E /* StringDebug.swift in Sources */, 28F94F5E2485AE0A0010D2B3 /* BehaviorBase.swift in Sources */, 28A16134283D86E70081CFA4 /* TourWaypointDetail.swift in Sources */, @@ -6072,7 +6056,6 @@ 62CCED4B26E2CF3900A91E5A /* SoundscapeDocumentAlert.swift in Sources */, 28F94F68248EB2F40010D2B3 /* CalibratableDevice.swift in Sources */, C3B331242011615A00CD20F9 /* Address.swift in Sources */, - 7FB733D62CE5650C00C99D46 /* WebViewController.swift in Sources */, 3153765C22013CDA008445AD /* DistanceUnit.swift in Sources */, 288E5FDC268647C000F7D4CD /* WaypointDepartureCallout.swift in Sources */, 62CE14C925C0DB75001CBC0B /* KalmanFilter+HeadphoneCalibration.swift in Sources */, @@ -6120,7 +6103,6 @@ 2872D15A26DEE8B3000BE465 /* IntersectionGenerator.swift in Sources */, 62B9B413268A8C6F007F9DB9 /* BeaconView.swift in Sources */, 286DA3A81F9E6B3100C34C9C /* BorderedIcon.swift in Sources */, - 7FB733D22CE5577200C99D46 /* AboutSettingsViewController.swift in Sources */, 28D07BFA20B7103700F314C5 /* GenericLocationSearchProvider.swift in Sources */, 62EACA2126697ED400DBDECC /* WaypointDetailAnnotation.swift in Sources */, 2873D64B211E05250034A297 /* LaunchHelper.swift in Sources */, @@ -6228,7 +6210,6 @@ 288E5FE0269504D500F7D4CD /* ConditionalAccessibilityAction.swift in Sources */, 624C96BF285299AD001F84F8 /* IdentifiableAnnotation.swift in Sources */, 2817389E22BBD7AF00EFDD62 /* Result+Extensions.swift in Sources */, - 7FB733CA2CE556EC00C99D46 /* AudioSettingsViewController.swift in Sources */, C3DA72C2224D41EC0062DB81 /* RequestToken.swift in Sources */, 28863A7024C8DA3100194458 /* LayeredSound.swift in Sources */, 282A807023F7240A00993B7D /* ObjCExceptionHandling.m in Sources */, @@ -6254,7 +6235,6 @@ 283F160B202233A0009C2FFA /* POICalloutProtocol.swift in Sources */, 620FB1D0260D35A600B9E0E5 /* ThreadSafeHeadphoneCalibrator.swift in Sources */, 626BB58E2681554400E5355A /* View+Extensions.swift in Sources */, - 7FB733CE2CE5572D00C99D46 /* StreetPreviewSettingsViewController.swift in Sources */, 2800ABF12654427300B2622F /* AuthoredActivityCell.swift in Sources */, 624DF68727BED7CE000A634C /* AuthorizationProvider.swift in Sources */, 283C058023DB6DBF0086C01F /* SynchronouslyGeneratedSound.swift in Sources */, diff --git a/apps/ios/GuideDogs/AboutSettingsViewController.swift b/apps/ios/GuideDogs/AboutSettingsViewController.swift deleted file mode 100644 index 7766b11f..00000000 --- a/apps/ios/GuideDogs/AboutSettingsViewController.swift +++ /dev/null @@ -1,113 +0,0 @@ -/* -import UIKit - -class AboutSettingsViewController: UITableViewController { - - private enum AboutRow: Int, CaseIterable { - case whatsNew = 0 - case privacyPolicy = 1 - case serviceAgreement = 2 - case thirdPartyNotices = 3 - case copyrightNotices = 4 - } - - private static let cellIdentifiers: [AboutRow: String] = [ - .whatsNew: "whatsNew", - .privacyPolicy: "privacyPolicy", - .serviceAgreement: "serviceAgreement", - .thirdPartyNotices: "thirdPartyNotices", - .copyrightNotices: "copyrightNotices" - ] - - override func viewDidLoad() { - super.viewDidLoad() - self.title = GDLocalizedString("settings.section.about") - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "default") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return AboutRow.allCases.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let rowType = AboutRow(rawValue: indexPath.row) else { - return UITableViewCell() - } - - let identifier = AboutSettingsViewController.cellIdentifiers[rowType] ?? "default" - let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) - cell.accessoryType = .disclosureIndicator - - switch rowType { - case .whatsNew: - cell.textLabel?.text = GDLocalizedString("settings.about.title.whats_new") - case .privacyPolicy: - cell.textLabel?.text = GDLocalizedString("settings.about.title.privacy") - case .serviceAgreement: - cell.textLabel?.text = GDLocalizedString("settings.about.title.service_agreement") - case .thirdPartyNotices: - cell.textLabel?.text = GDLocalizedString("settings.about.title.third_party") - case .copyrightNotices: - cell.textLabel?.text = GDLocalizedString("settings.about.title.copyright") - } - - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let rowType = AboutRow(rawValue: indexPath.row) else { return } - - switch rowType { - case .whatsNew: - navigateToWebView(withTitle: GDLocalizedString("settings.about.title.whats_new"), url: "https://example.com/whats-new") - case .privacyPolicy: - navigateToWebView(withTitle: GDLocalizedString("settings.about.title.privacy"), url: "https://example.com/privacy-policy") - case .serviceAgreement: - navigateToWebView(withTitle: GDLocalizedString("settings.about.title.service_agreement"), url: "https://example.com/service-agreement") - case .thirdPartyNotices: - navigateToWebView(withTitle: GDLocalizedString("settings.about.title.third_party"), url: "https://example.com/third-party-notices") - case .copyrightNotices: - navigateToWebView(withTitle: GDLocalizedString("settings.about.title.copyright"), url: "https://example.com/copyright-notices") - } - - tableView.deselectRow(at: indexPath, animated: true) - } - - // MARK: - Navigation - - private func navigateToWebView(withTitle title: String, url: String) { - let webViewController = WebViewController() - webViewController.title = title - webViewController.urlString = url - navigationController?.pushViewController(webViewController, animated: true) - } -} -*/ -import UIKit - -class AboutSettingsViewController: UITableViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - // Register the "whatsNew" cell identifier to avoid dequeuing errors - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "whatsNew") - - self.title = GDLocalizedString("settings.section.about") - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 1 // Assuming one row for demonstration; adjust based on your content - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "whatsNew", for: indexPath) - cell.textLabel?.text = GDLocalizedString("about.whats_new") // Customize cell content as needed - return cell - } -} - diff --git a/apps/ios/GuideDogs/AudioSettingsViewController.swift b/apps/ios/GuideDogs/AudioSettingsViewController.swift deleted file mode 100644 index 8dffca3b..00000000 --- a/apps/ios/GuideDogs/AudioSettingsViewController.swift +++ /dev/null @@ -1,81 +0,0 @@ -import UIKit - -class AudioSettingsViewController: UITableViewController { - - // MARK: - Audio Settings Enum - private enum AudioRow: Int, CaseIterable { - case mixAudio = 0 - } - - private let audioItems = [ - "Mix Audio" // Placeholder for the Mix Audio setting - ] - - override func viewDidLoad() { - super.viewDidLoad() - self.title = GDLocalizedString("settings.audio.media_controls") - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "AudioCell") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 // Only one section for audio settings - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return AudioRow.allCases.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "AudioCell", for: indexPath) - - if let rowType = AudioRow(rawValue: indexPath.row) { - cell.textLabel?.text = audioItems[indexPath.row] - - switch rowType { - case .mixAudio: - // Add a switch control to the Mix Audio row - let switchControl = UISwitch() - switchControl.isOn = SettingsContext.shared.audioSessionMixesWithOthers - switchControl.addTarget(self, action: #selector(mixAudioSwitchToggled(_:)), for: .valueChanged) - cell.accessoryView = switchControl - } - } - - return cell - } - - // MARK: - Mix Audio Toggle Logic - - @objc private func mixAudioSwitchToggled(_ sender: UISwitch) { - if sender.isOn { - // Show confirmation alert if turning on "Mix Audio" - let alert = UIAlertController(title: GDLocalizedString("general.alert.confirmation_title"), - message: GDLocalizedString("setting.audio.mix_with_others.confirmation"), - preferredStyle: .alert) - - let mixAction = UIAlertAction(title: GDLocalizedString("settings.audio.mix_with_others.title"), style: .default) { [weak self] (_) in - self?.updateMixAudioSetting(true) - } - alert.addAction(mixAction) - alert.preferredAction = mixAction - - alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { (_) in - sender.isOn = false - })) - - present(alert, animated: true) - } else { - updateMixAudioSetting(false) - } - } - - private func updateMixAudioSetting(_ newValue: Bool) { - SettingsContext.shared.audioSessionMixesWithOthers = newValue - AppContext.shared.audioEngine.mixWithOthers = newValue - - GDATelemetry.track("settings.mix_audio", - with: ["value": "\(SettingsContext.shared.audioSessionMixesWithOthers)", - "context": "app_settings"]) - } -} - diff --git a/apps/ios/GuideDogs/CalloutSettingsViewController.swift b/apps/ios/GuideDogs/CalloutSettingsViewController.swift index f54188d9..8e0eed16 100644 --- a/apps/ios/GuideDogs/CalloutSettingsViewController.swift +++ b/apps/ios/GuideDogs/CalloutSettingsViewController.swift @@ -4,10 +4,10 @@ class CalloutSettingsViewController: UITableViewController { private enum CalloutsRow: Int, CaseIterable { case all = 0 - case poi = 1 - case mobility = 2 - case beacon = 3 - case shake = 4 + case poi + case mobility + case beacon + case shake } private static let cellIdentifiers: [CalloutsRow: String] = [ @@ -30,11 +30,9 @@ class CalloutSettingsViewController: UITableViewController { self.title = GDLocalizedString("menu.manage_callouts") // Register the cells - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "allCallouts") - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "poiCallouts") - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "mobilityCallouts") - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "beaconCallouts") - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "shakeCallouts") + for identifier in CalloutSettingsViewController.cellIdentifiers.values { + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier) + } } override func numberOfSections(in tableView: UITableView) -> Int { @@ -42,7 +40,7 @@ class CalloutSettingsViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return CalloutsRow.allCases.count + return automaticCalloutsEnabled ? CalloutsRow.allCases.count : 1 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -54,42 +52,28 @@ class CalloutSettingsViewController: UITableViewController { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) cell.selectionStyle = .none + // Configure the cell based on the row type + let switchView = UISwitch(frame: .zero) + switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) + switchView.tag = rowType.rawValue + cell.accessoryView = switchView + switch rowType { case .all: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.all") - let switchView = UISwitch(frame: .zero) + cell.textLabel?.text = GDLocalizedString("Allow Callouts") switchView.setOn(automaticCalloutsEnabled, animated: true) - switchView.tag = CalloutsRow.all.rawValue - switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) - cell.accessoryView = switchView case .poi: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.poi") - let switchView = UISwitch(frame: .zero) + cell.textLabel?.text = GDLocalizedString("Places and Landmarks") switchView.setOn(poiCalloutsEnabled, animated: true) - switchView.tag = CalloutsRow.poi.rawValue - switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) - cell.accessoryView = switchView case .mobility: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.mobility") - let switchView = UISwitch(frame: .zero) + cell.textLabel?.text = GDLocalizedString("Mobility") switchView.setOn(mobilityCalloutsEnabled, animated: true) - switchView.tag = CalloutsRow.mobility.rawValue - switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) - cell.accessoryView = switchView case .beacon: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.beacon") - let switchView = UISwitch(frame: .zero) + cell.textLabel?.text = GDLocalizedString("Distance to the Audio Beacon") switchView.setOn(beaconCalloutsEnabled, animated: true) - switchView.tag = CalloutsRow.beacon.rawValue - switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) - cell.accessoryView = switchView case .shake: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.shake") - let switchView = UISwitch(frame: .zero) + cell.textLabel?.text = GDLocalizedString("Repeat Callouts") switchView.setOn(shakeCalloutsEnabled, animated: true) - switchView.tag = CalloutsRow.shake.rawValue - switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) - cell.accessoryView = switchView } return cell @@ -101,6 +85,7 @@ class CalloutSettingsViewController: UITableViewController { case .all: automaticCalloutsEnabled = sender.isOn SettingsContext.shared.automaticCalloutsEnabled = sender.isOn + tableView.reloadData() // Refresh the table to show/hide rows based on the "all" toggle case .poi: poiCalloutsEnabled = sender.isOn case .mobility: @@ -112,11 +97,6 @@ class CalloutSettingsViewController: UITableViewController { case .none: break } - - // Update table view based on automatic callouts state - if sender.tag == CalloutsRow.all.rawValue { - tableView.reloadData() - } } } diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index e1f3d8c5..2721b0af 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -10,6 +10,7 @@ // SettingsViewController.swift // Soundscape // +/* import UIKit import AppCenterAnalytics @@ -111,9 +112,9 @@ class SettingsViewController: BaseTableViewController { } } } +*/ -/* import UIKit import AppCenterAnalytics @@ -216,7 +217,6 @@ class SettingsViewController: BaseTableViewController { return 0 } - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return expandedSections.contains(indexPath.section) ? UITableView.automaticDimension : 0 } @@ -329,17 +329,29 @@ class SettingsViewController: BaseTableViewController { guard let header = gesture.view as? UITableViewHeaderFooterView else { return } let section = header.tag - if expandedSections.contains(section) { - expandedSections.remove(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section collapsed") + if section == Section.general.rawValue { + // Navigate to GeneralSettingsViewController + let generalSettingsVC = GeneralSettingsViewController() + self.navigationController?.pushViewController(generalSettingsVC, animated: true) + } else if section == Section.callouts.rawValue { + // Navigate to CalloutsSettingsViewController + let calloutsSettingsVC = CalloutSettingsViewController() + self.navigationController?.pushViewController(calloutsSettingsVC, animated: true) } else { - expandedSections.insert(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section expanded") + // Toggle expand/collapse for other sections + tableView.beginUpdates() + if expandedSections.contains(section) { + expandedSections.remove(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section collapsed") + } else { + expandedSections.insert(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section expanded") + } + tableView.endUpdates() } } - } extension SettingsViewController: MixAudioSettingCellDelegate { @@ -393,11 +405,13 @@ extension SettingsViewController: CalloutSettingsCellViewDelegate { let indexPaths = SettingsViewController.collapsibleCalloutIndexPaths + tableView.beginUpdates() if SettingsContext.shared.automaticCalloutsEnabled && !tableView.contains(indexPaths: indexPaths) { tableView.insertRows(at: indexPaths, with: .automatic) } else if !SettingsContext.shared.automaticCalloutsEnabled && tableView.contains(indexPaths: indexPaths) { tableView.deleteRows(at: indexPaths, with: .automatic) } + tableView.endUpdates() } } @@ -408,5 +422,302 @@ extension SettingsViewController: LargeBannerContainerView { tableView.reloadData() } } +/* + import UIKit + import AppCenterAnalytics + + class SettingsViewController: BaseTableViewController { + + private enum Section: Int, CaseIterable { + case general = 0 + case audio = 1 + case callouts = 2 + case streetPreview = 3 + case troubleshooting = 4 + case about = 5 + case telemetry = 6 + } + + private enum CalloutsRow: Int, CaseIterable { + case all = 0 + case poi = 1 + case mobility = 2 + case beacon = 3 + case shake = 4 + } + + private static let cellIdentifiers: [IndexPath: String] = [ + IndexPath(row: 0, section: Section.general.rawValue): "languageAndRegion", + IndexPath(row: 1, section: Section.general.rawValue): "voice", + IndexPath(row: 2, section: Section.general.rawValue): "beaconSettings", + IndexPath(row: 3, section: Section.general.rawValue): "volumeSettings", + IndexPath(row: 4, section: Section.general.rawValue): "manageDevices", + IndexPath(row: 5, section: Section.general.rawValue): "siriShortcuts", + + IndexPath(row: 0, section: Section.audio.rawValue): "mixAudio", + + IndexPath(row: CalloutsRow.all.rawValue, section: Section.callouts.rawValue): "allCallouts", + IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue): "poiCallouts", + IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue): "mobilityCallouts", + IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue): "beaconCallouts", + IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue): "shakeCallouts", + + IndexPath(row: 0, section: Section.streetPreview.rawValue): "streetPreview", + IndexPath(row: 0, section: Section.troubleshooting.rawValue): "troubleshooting", + IndexPath(row: 0, section: Section.about.rawValue): "about", + IndexPath(row: 0, section: Section.telemetry.rawValue): "telemetry" + ] + + private static let collapsibleCalloutIndexPaths: [IndexPath] = [ + IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue), + IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue), + IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue), + IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue) + ] + + // MARK: Properties + + @IBOutlet weak var largeBannerContainerView: UIView! + + private var expandedSections: Set = [] + + // Section Descriptions + private static let sectionDescriptions: [Section: String] = [ + .general: GDLocalizedString("settings.section.general"), + .audio: GDLocalizedString("settings.audio.media_controls"), + .callouts: GDLocalizedString("menu.manage_callouts"), + .streetPreview: GDLocalizedString("settings.section.street_preview"), + .troubleshooting: GDLocalizedString("settings.section.troubleshooting"), + .about: GDLocalizedString("settings.section.about"), + .telemetry: GDLocalizedString("settings.section.telemetry") + ] + + // MARK: View Life Cycle + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + GDLogActionInfo("Opened 'Settings'") + + GDATelemetry.trackScreenView("settings") + + self.title = GDLocalizedString("settings.screen_title") + expandedSections = [] + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return Section.allCases.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + guard let sectionType = Section(rawValue: section) else { return 0 } + + if expandedSections.contains(section) { + switch sectionType { + case .general: return 6 + case .audio: return 1 + case .callouts: + return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 0 + case .streetPreview, .troubleshooting, .about, .telemetry: + return 1 + } + } + return 0 + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return expandedSections.contains(indexPath.section) ? UITableView.automaticDimension : 0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard expandedSections.contains(indexPath.section) else { + return UITableViewCell() + } + + let identifier = SettingsViewController.cellIdentifiers[indexPath] + let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) + + switch Section(rawValue: indexPath.section) { + case .callouts: + configureCalloutCell(cell as! CalloutSettingsCellView, at: indexPath) + case .telemetry: + (cell as! TelemetrySettingsTableViewCell).parent = self + case .audio: + (cell as! MixAudioSettingCell).delegate = self + default: + break + } + + return cell + } + + private func configureCalloutCell(_ cell: CalloutSettingsCellView, at indexPath: IndexPath) { + cell.delegate = self + if let rowType = CalloutsRow(rawValue: indexPath.row) { + switch rowType { + case .all: + cell.type = .all + case .poi: + cell.type = .poi + case .mobility: + cell.type = .mobility + case .beacon: + cell.type = .beacon + case .shake: + cell.type = .shake + } + } + } + + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + guard let sectionType = Section(rawValue: section) else { return nil } + + switch sectionType { + case .general: return GDLocalizedString("settings.section.general") + case .audio: return GDLocalizedString("settings.audio.media_controls") + case .callouts: return GDLocalizedString("menu.manage_callouts") + case .about: return GDLocalizedString("settings.section.about") + case .streetPreview: return GDLocalizedString("preview.title") + case .troubleshooting: return GDLocalizedString("settings.section.troubleshooting") + case .telemetry: return GDLocalizedString("settings.section.telemetry") + } + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + guard let sectionType = Section(rawValue: section) else { return nil } + + + if expandedSections.contains(section) { + return SettingsViewController.sectionDescriptions[sectionType] + } + + switch sectionType { + case .audio: return GDLocalizedString("settings.audio.mix_with_others.description") + case .streetPreview: return GDLocalizedString("preview.include_unnamed_roads.subtitle") + case .telemetry: return GDLocalizedString("settings.section.telemetry.footer") + default: return nil + } + } + + override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { + guard let header = view as? UITableViewHeaderFooterView else { return } + header.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTap(_:)))) + header.tag = section + + + header.textLabel?.textColor = .white + header.textLabel?.font = UIFont.boldSystemFont(ofSize: 18) -*/ + + header.contentView.backgroundColor = UIColor(named: "HeaderBackgroundColor") + header.layer.borderColor = UIColor.clear.cgColor + header.layer.borderWidth = 0.0 + header.layer.cornerRadius = 8.0 + header.layer.masksToBounds = true + + header.contentView.layoutMargins = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) + + // ">" icon + let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) + chevronImageView.tintColor = .white + chevronImageView.translatesAutoresizingMaskIntoConstraints = false + + header.contentView.addSubview(chevronImageView) + + NSLayoutConstraint.activate([ + chevronImageView.trailingAnchor.constraint(equalTo: header.contentView.trailingAnchor, constant: -15), + chevronImageView.centerYAnchor.constraint(equalTo: header.contentView.centerYAnchor), + chevronImageView.widthAnchor.constraint(equalToConstant: 20), + chevronImageView.heightAnchor.constraint(equalToConstant: 20) + ]) + } + + @objc private func handleHeaderTap(_ gesture: UITapGestureRecognizer) { + guard let header = gesture.view as? UITableViewHeaderFooterView else { return } + let section = header.tag + + tableView.beginUpdates() + if expandedSections.contains(section) { + expandedSections.remove(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section collapsed") + } else { + expandedSections.insert(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section expanded") + } + tableView.endUpdates() + } + + } + + extension SettingsViewController: MixAudioSettingCellDelegate { + func onSettingValueChanged(_ cell: MixAudioSettingCell, settingSwitch: UISwitch) { + guard settingSwitch.isOn else { + updateSetting(true) + return + } + + let alert = UIAlertController(title: GDLocalizedString("general.alert.confirmation_title"), + message: GDLocalizedString("setting.audio.mix_with_others.confirmation"), + preferredStyle: .alert) + + let mixAction = UIAlertAction(title: GDLocalizedString("settings.audio.mix_with_others.title"), style: .default) { [weak self] (_) in + self?.updateSetting(false) + self?.focusOnCell(cell) + } + alert.addAction(mixAction) + alert.preferredAction = mixAction + + alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { [weak self] (_) in + settingSwitch.isOn = false + GDATelemetry.track("settings.mix_audio.cancel", with: ["context": "app_settings"]) + self?.focusOnCell(cell) + })) + + present(alert, animated: true) + } + + private func updateSetting(_ newValue: Bool) { + SettingsContext.shared.audioSessionMixesWithOthers = newValue + AppContext.shared.audioEngine.mixWithOthers = newValue + + GDATelemetry.track("settings.mix_audio", + with: ["value": "\(SettingsContext.shared.audioSessionMixesWithOthers)", + "context": "app_settings"]) + } + + private func focusOnCell(_ cell: MixAudioSettingCell) { + DispatchQueue.main.async { + UIAccessibility.post(notification: .layoutChanged, argument: cell) + } + } + } + + extension SettingsViewController: CalloutSettingsCellViewDelegate { + func onCalloutSettingChanged(_ type: CalloutSettingCellType) { + guard type == .all else { + return + } + + let indexPaths = SettingsViewController.collapsibleCalloutIndexPaths + + tableView.beginUpdates() + if SettingsContext.shared.automaticCalloutsEnabled && !tableView.contains(indexPaths: indexPaths) { + tableView.insertRows(at: indexPaths, with: .automatic) + } else if !SettingsContext.shared.automaticCalloutsEnabled && tableView.contains(indexPaths: indexPaths) { + tableView.deleteRows(at: indexPaths, with: .automatic) + } + tableView.endUpdates() + } + } + + extension SettingsViewController: LargeBannerContainerView { + + func setLargeBannerHeight(_ height: CGFloat) { + largeBannerContainerView.setHeight(height) + tableView.reloadData() + } + } + */ diff --git a/apps/ios/GuideDogs/GeneralSettingsViewController.swift b/apps/ios/GuideDogs/GeneralSettingsViewController.swift index ba622b37..2952e252 100644 --- a/apps/ios/GuideDogs/GeneralSettingsViewController.swift +++ b/apps/ios/GuideDogs/GeneralSettingsViewController.swift @@ -46,9 +46,10 @@ class GeneralSettingsViewController: UITableViewController { case .languageAndRegion, .voice, .beaconSettings, .manageDevices, .siriShortcuts: cell.accessoryType = .disclosureIndicator // These would open additional settings screens case .volumeSettings: - // Example: Adding a slider for volume control + // Example: Adding a slider for volume control directly in the cell let volumeSlider = UISlider() volumeSlider.value = 0.5 // Default value; adjust based on saved settings + volumeSlider.addTarget(self, action: #selector(volumeSliderChanged(_:)), for: .valueChanged) cell.accessoryView = volumeSlider } } @@ -63,25 +64,60 @@ class GeneralSettingsViewController: UITableViewController { if let rowType = GeneralRow(rawValue: indexPath.row) { switch rowType { case .languageAndRegion: - // Navigate to Language & Region settings screen - print("Open Language & Region settings") + openLanguageAndRegionSettings() case .voice: - // Navigate to Voice settings screen - print("Open Voice settings") + openVoiceSettings() case .beaconSettings: - // Navigate to Beacon Settings screen - print("Open Beacon Settings") + openBeaconSettings() case .manageDevices: - // Navigate to Manage Devices settings screen - print("Open Manage Devices settings") + openManageDevicesSettings() case .siriShortcuts: - // Navigate to Siri Shortcuts settings screen - print("Open Siri Shortcuts settings") + openSiriShortcutsSettings() case .volumeSettings: // Volume setting already has a slider; no further action required here break } } } + + // Helper methods for each setting action + private func openLanguageAndRegionSettings() { + // Logic to load or modify Language & Region settings. + showActionAlert(title: "Language & Region", message: "This opens Language & Region settings.") + } + + private func openVoiceSettings() { + // Logic to load or modify Voice settings. + showActionAlert(title: "Voice", message: "This opens Voice settings.") + } + + private func openBeaconSettings() { + // Logic to load or modify Beacon settings. + showActionAlert(title: "Beacon Settings", message: "This opens Beacon Settings.") + } + + private func openManageDevicesSettings() { + // Logic to load or modify Manage Devices settings. + showActionAlert(title: "Manage Devices", message: "This opens Manage Devices settings.") + } + + private func openSiriShortcutsSettings() { + // Logic to load or modify Siri Shortcuts settings. + showActionAlert(title: "Siri Shortcuts", message: "This opens Siri Shortcuts settings.") + } + + // Volume slider action + @objc private func volumeSliderChanged(_ sender: UISlider) { + let volume = sender.value + // Logic to save or update volume settings in SettingsContext or similar + print("Volume changed to: \(volume)") + } + + // Helper method to show action alerts as placeholders + private func showActionAlert(title: String, message: String) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + present(alertController, animated: true, completion: nil) + } } diff --git a/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift b/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift deleted file mode 100644 index 54fdc136..00000000 --- a/apps/ios/GuideDogs/StreetPreviewSettingsViewController.swift +++ /dev/null @@ -1,61 +0,0 @@ -import UIKit - -class StreetPreviewViewController: UITableViewController { - - private enum StreetPreviewRow: Int, CaseIterable { - case includeUnnamedRoads = 0 - } - - private static let cellIdentifiers: [StreetPreviewRow: String] = [ - .includeUnnamedRoads: "streetPreviewIncludeUnnamedRoads" - ] - - // Property to hold the current state of the "Include Unnamed Roads" setting - private var includeUnnamedRoads = SettingsContext.shared.previewIntersectionsIncludeUnnamedRoads - - override func viewDidLoad() { - super.viewDidLoad() - self.title = GDLocalizedString("settings.section.street_preview") - - // Register the specific cell identifier - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "streetPreviewIncludeUnnamedRoads") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return StreetPreviewRow.allCases.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let rowType = StreetPreviewRow(rawValue: indexPath.row) else { - return UITableViewCell() - } - - let identifier = StreetPreviewViewController.cellIdentifiers[rowType] ?? "default" - let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) - cell.selectionStyle = .none - - switch rowType { - case .includeUnnamedRoads: - cell.textLabel?.text = GDLocalizedString("preview.include_unnamed_roads.subtitle") - let switchView = UISwitch(frame: .zero) - switchView.setOn(includeUnnamedRoads, animated: true) - switchView.tag = StreetPreviewRow.includeUnnamedRoads.rawValue - switchView.addTarget(self, action: #selector(toggleIncludeUnnamedRoads(_:)), for: .valueChanged) - cell.accessoryView = switchView - } - - return cell - } - - // Action for the "Include Unnamed Roads" switch - @objc private func toggleIncludeUnnamedRoads(_ sender: UISwitch) { - includeUnnamedRoads = sender.isOn - SettingsContext.shared.previewIntersectionsIncludeUnnamedRoads = sender.isOn - // Optionally, post a notification or perform any additional actions when this setting changes - } -} - diff --git a/apps/ios/GuideDogs/TelemetrySettingsViewController.swift b/apps/ios/GuideDogs/TelemetrySettingsViewController.swift deleted file mode 100644 index 3187f4e3..00000000 --- a/apps/ios/GuideDogs/TelemetrySettingsViewController.swift +++ /dev/null @@ -1,76 +0,0 @@ -import UIKit - -class TelemetrySettingsViewController: UITableViewController { - - private enum TelemetryRow: Int, CaseIterable { - case telemetryOptOut = 0 - } - - private static let cellIdentifiers: [TelemetryRow: String] = [ - .telemetryOptOut: "telemetryOptOut" - ] - - override func viewDidLoad() { - super.viewDidLoad() - self.title = GDLocalizedString("settings.section.telemetry") - - // Register the specific cell identifier - self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "telemetryOptOut") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return TelemetryRow.allCases.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let rowType = TelemetryRow(rawValue: indexPath.row) else { - return UITableViewCell() - } - - let identifier = TelemetrySettingsViewController.cellIdentifiers[rowType] ?? "default" - let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) - - switch rowType { - case .telemetryOptOut: - cell.textLabel?.text = GDLocalizedString("settings.section.share_usage_data") - cell.accessoryType = .none - - let telemetrySwitch = UISwitch() - telemetrySwitch.isOn = !SettingsContext.shared.telemetryOptout - telemetrySwitch.addTarget(self, action: #selector(telemetrySwitchChanged(_:)), for: .valueChanged) - cell.accessoryView = telemetrySwitch - } - - return cell - } - - // MARK: - Action - - @objc private func telemetrySwitchChanged(_ sender: UISwitch) { - let isOptedOut = !sender.isOn - SettingsContext.shared.telemetryOptout = isOptedOut - - let alertTitle = GDLocalizedString("settings.telemetry.optout.alert_title") - let alertMessage = GDLocalizedString("settings.telemetry.optout.alert_message") - - if isOptedOut { - let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.confirm"), style: .default, handler: { _ in - SettingsContext.shared.telemetryOptout = true - sender.isOn = false - })) - alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { _ in - sender.isOn = true - SettingsContext.shared.telemetryOptout = false - })) - self.present(alert, animated: true, completion: nil) - } else { - SettingsContext.shared.telemetryOptout = false - } - } -} - diff --git a/apps/ios/GuideDogs/WebViewController.swift b/apps/ios/GuideDogs/WebViewController.swift deleted file mode 100644 index 740824af..00000000 --- a/apps/ios/GuideDogs/WebViewController.swift +++ /dev/null @@ -1,27 +0,0 @@ -import UIKit -import WebKit - -class WebViewController: UIViewController, WKNavigationDelegate { - - var urlString: String? - private var webView: WKWebView! - - override func loadView() { - webView = WKWebView() - webView.navigationDelegate = self - view = webView - } - - override func viewDidLoad() { - super.viewDidLoad() - - guard let urlString = urlString, let url = URL(string: urlString) else { - print("Invalid URL.") - return - } - - webView.load(URLRequest(url: url)) - webView.allowsBackForwardNavigationGestures = true - } -} - From d199e268fa571d1bddf662bf723230e4c9baab31 Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Tue, 19 Nov 2024 16:47:56 -0500 Subject: [PATCH 5/7] Fixed callout screen --- .../en-US.lproj/Localizable.strings | 12 + .../CalloutSettingsViewController.swift | 57 ++- .../Settings/SettingsViewController.swift | 402 ------------------ 3 files changed, 53 insertions(+), 418 deletions(-) diff --git a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings index 88d6fc9e..536c18aa 100644 --- a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings +++ b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings @@ -404,6 +404,17 @@ /* Language display name. %1$@ is a language name, %2$@ is the country name. e.g. "English (United Kingdom)" {NumberedPlaceholder="%1$@", "%2$@"} */ "settings.language.language_name" = "%1$@ (%2$@)"; +"menu.manage_callouts.all" = "Allow Callouts"; + +"menu.manage_callouts.poi" = "Places and Landmarks"; + +"menu.manage_callouts.mobility" = "Mobility"; + +"menu.manage_callouts.beacon" = "Distance to the Audio Beacon"; + +"menu.manage_callouts.shake" = "Repeat Callouts"; + + //------------------------------------------------------------------------------ // MARK: Settings (Sections) //------------------------------------------------------------------------------ @@ -430,6 +441,7 @@ "settings.section.telemetry" = "Manage data collection and privacy."; + //------------------------------------------------------------------------------ // MARK: Settings (Audio) //------------------------------------------------------------------------------ diff --git a/apps/ios/GuideDogs/CalloutSettingsViewController.swift b/apps/ios/GuideDogs/CalloutSettingsViewController.swift index 8e0eed16..3c8c6925 100644 --- a/apps/ios/GuideDogs/CalloutSettingsViewController.swift +++ b/apps/ios/GuideDogs/CalloutSettingsViewController.swift @@ -1,13 +1,19 @@ import UIKit -class CalloutSettingsViewController: UITableViewController { +class CalloutSettingsViewController: UITableViewController, CalloutSettingsCellViewDelegate { + + // MARK: - CalloutSettingsCellViewDelegate + func onCalloutSettingChanged(_ type: CalloutSettingCellType) { + // Handle callout setting changes here + print("Callout setting changed for type: \(type)") + } private enum CalloutsRow: Int, CaseIterable { case all = 0 - case poi - case mobility - case beacon - case shake + case poi = 1 + case mobility = 2 + case beacon = 3 + case shake = 4 } private static let cellIdentifiers: [CalloutsRow: String] = [ @@ -23,13 +29,13 @@ class CalloutSettingsViewController: UITableViewController { private var poiCalloutsEnabled = true private var mobilityCalloutsEnabled = true private var beaconCalloutsEnabled = true - private var shakeCalloutsEnabled = true + private var shakeCalloutsEnabled = false override func viewDidLoad() { super.viewDidLoad() self.title = GDLocalizedString("menu.manage_callouts") - // Register the cells + // Register all cell identifiers for identifier in CalloutSettingsViewController.cellIdentifiers.values { self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier) } @@ -40,63 +46,82 @@ class CalloutSettingsViewController: UITableViewController { } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // Only show additional rows when "Allow Callouts" is enabled return automaticCalloutsEnabled ? CalloutsRow.allCases.count : 1 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + // Determine the row type guard let rowType = CalloutsRow(rawValue: indexPath.row) else { return UITableViewCell() } + // Get the identifier for the row type let identifier = CalloutSettingsViewController.cellIdentifiers[rowType] ?? "default" let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) - cell.selectionStyle = .none + cell.selectionStyle = .none // Disable selection - // Configure the cell based on the row type + // Configure the toggle for each row let switchView = UISwitch(frame: .zero) switchView.addTarget(self, action: #selector(toggleCalloutSetting(_:)), for: .valueChanged) switchView.tag = rowType.rawValue cell.accessoryView = switchView + // Configure the row content dynamically switch rowType { case .all: - cell.textLabel?.text = GDLocalizedString("Allow Callouts") + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.all") switchView.setOn(automaticCalloutsEnabled, animated: true) case .poi: - cell.textLabel?.text = GDLocalizedString("Places and Landmarks") + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.poi") switchView.setOn(poiCalloutsEnabled, animated: true) case .mobility: - cell.textLabel?.text = GDLocalizedString("Mobility") + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.mobility") switchView.setOn(mobilityCalloutsEnabled, animated: true) case .beacon: - cell.textLabel?.text = GDLocalizedString("Distance to the Audio Beacon") + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.beacon") switchView.setOn(beaconCalloutsEnabled, animated: true) case .shake: - cell.textLabel?.text = GDLocalizedString("Repeat Callouts") + cell.textLabel?.text = GDLocalizedString("menu.manage_callouts.shake") switchView.setOn(shakeCalloutsEnabled, animated: true) } return cell } - // Toggle actions for each callout setting switch + // MARK: - Toggle Actions + @objc private func toggleCalloutSetting(_ sender: UISwitch) { + // Determine which toggle was changed based on its tag switch CalloutsRow(rawValue: sender.tag) { case .all: automaticCalloutsEnabled = sender.isOn SettingsContext.shared.automaticCalloutsEnabled = sender.isOn - tableView.reloadData() // Refresh the table to show/hide rows based on the "all" toggle + logCalloutToggle(for: "all", state: sender.isOn) + tableView.reloadData() // Refresh table to show/hide rows case .poi: poiCalloutsEnabled = sender.isOn + logCalloutToggle(for: "poi", state: sender.isOn) case .mobility: mobilityCalloutsEnabled = sender.isOn + logCalloutToggle(for: "mobility", state: sender.isOn) case .beacon: beaconCalloutsEnabled = sender.isOn + logCalloutToggle(for: "beacon", state: sender.isOn) case .shake: shakeCalloutsEnabled = sender.isOn + logCalloutToggle(for: "shake", state: sender.isOn) case .none: break } } + + // MARK: - Logging Function + + private func logCalloutToggle(for type: String, state: Bool) { + let logMessage = "Toggled \(type) callouts to: \(state)" + GDLogActionInfo(logMessage) // Use GDLog for logging + GDATelemetry.track("callout_toggle", with: ["type": type, "state": "\(state)"]) + } } diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index 2721b0af..a04fea4d 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -10,109 +10,6 @@ // SettingsViewController.swift // Soundscape // -/* -import UIKit -import AppCenterAnalytics - -class SettingsViewController: BaseTableViewController { - - @IBOutlet weak var largeBannerContainerView: UIView! // IBOutlet for the banner container view - - private enum Section: Int, CaseIterable { - case general = 0 - case audio - case callouts - case streetPreview - case troubleshooting - case about - case telemetry - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.title = GDLocalizedString("settings.screen_title") - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell") - - GDLogActionInfo("Opened 'Settings'") - GDATelemetry.trackScreenView("settings") - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return Section.allCases.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 1 // Each section has one row to navigate to its own view controller - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell", for: indexPath) - cell.accessoryType = .disclosureIndicator // Show an arrow to indicate navigation - - switch Section(rawValue: indexPath.section) { - case .general: - cell.textLabel?.text = GDLocalizedString("settings.section.general") - case .audio: - cell.textLabel?.text = GDLocalizedString("settings.audio.media_controls") - case .callouts: - cell.textLabel?.text = GDLocalizedString("menu.manage_callouts") - case .streetPreview: - cell.textLabel?.text = GDLocalizedString("settings.section.street_preview") - case .troubleshooting: - cell.textLabel?.text = GDLocalizedString("settings.section.troubleshooting") - case .about: - cell.textLabel?.text = GDLocalizedString("settings.section.about") - case .telemetry: - cell.textLabel?.text = GDLocalizedString("settings.section.telemetry") - default: - cell.textLabel?.text = "" - } - return cell - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let section = Section(rawValue: indexPath.section) - let viewController: UIViewController - - switch section { - case .general: - viewController = GeneralSettingsViewController() - case .audio: - viewController = AudioSettingsViewController() - case .callouts: - viewController = CalloutSettingsViewController() - case .streetPreview: - viewController = StreetPreviewViewController() - case .troubleshooting: - viewController = TroubleshootingViewController() - case .about: - viewController = AboutSettingsViewController() // Navigates to AboutSettingsViewController - case .telemetry: - viewController = TelemetrySettingsViewController() - default: - return - } - - navigationController?.pushViewController(viewController, animated: true) - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch Section(rawValue: section) { - case .general: return GDLocalizedString("settings.section.general") - case .audio: return GDLocalizedString("settings.audio.media_controls") - case .callouts: return GDLocalizedString("menu.manage_callouts") - case .about: return GDLocalizedString("settings.section.about") - case .streetPreview: return GDLocalizedString("preview.title") - case .troubleshooting: return GDLocalizedString("settings.section.troubleshooting") - case .telemetry: return GDLocalizedString("settings.section.telemetry") - default: return nil - } - } -} -*/ import UIKit @@ -422,302 +319,3 @@ extension SettingsViewController: LargeBannerContainerView { tableView.reloadData() } } -/* - import UIKit - import AppCenterAnalytics - - class SettingsViewController: BaseTableViewController { - - private enum Section: Int, CaseIterable { - case general = 0 - case audio = 1 - case callouts = 2 - case streetPreview = 3 - case troubleshooting = 4 - case about = 5 - case telemetry = 6 - } - - private enum CalloutsRow: Int, CaseIterable { - case all = 0 - case poi = 1 - case mobility = 2 - case beacon = 3 - case shake = 4 - } - - private static let cellIdentifiers: [IndexPath: String] = [ - IndexPath(row: 0, section: Section.general.rawValue): "languageAndRegion", - IndexPath(row: 1, section: Section.general.rawValue): "voice", - IndexPath(row: 2, section: Section.general.rawValue): "beaconSettings", - IndexPath(row: 3, section: Section.general.rawValue): "volumeSettings", - IndexPath(row: 4, section: Section.general.rawValue): "manageDevices", - IndexPath(row: 5, section: Section.general.rawValue): "siriShortcuts", - - IndexPath(row: 0, section: Section.audio.rawValue): "mixAudio", - - IndexPath(row: CalloutsRow.all.rawValue, section: Section.callouts.rawValue): "allCallouts", - IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue): "poiCallouts", - IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue): "mobilityCallouts", - IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue): "beaconCallouts", - IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue): "shakeCallouts", - - IndexPath(row: 0, section: Section.streetPreview.rawValue): "streetPreview", - IndexPath(row: 0, section: Section.troubleshooting.rawValue): "troubleshooting", - IndexPath(row: 0, section: Section.about.rawValue): "about", - IndexPath(row: 0, section: Section.telemetry.rawValue): "telemetry" - ] - - private static let collapsibleCalloutIndexPaths: [IndexPath] = [ - IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue), - IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue), - IndexPath(row: CalloutsRow.beacon.rawValue, section: Section.callouts.rawValue), - IndexPath(row: CalloutsRow.shake.rawValue, section: Section.callouts.rawValue) - ] - - // MARK: Properties - - @IBOutlet weak var largeBannerContainerView: UIView! - - private var expandedSections: Set = [] - - // Section Descriptions - private static let sectionDescriptions: [Section: String] = [ - .general: GDLocalizedString("settings.section.general"), - .audio: GDLocalizedString("settings.audio.media_controls"), - .callouts: GDLocalizedString("menu.manage_callouts"), - .streetPreview: GDLocalizedString("settings.section.street_preview"), - .troubleshooting: GDLocalizedString("settings.section.troubleshooting"), - .about: GDLocalizedString("settings.section.about"), - .telemetry: GDLocalizedString("settings.section.telemetry") - ] - - // MARK: View Life Cycle - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - GDLogActionInfo("Opened 'Settings'") - - GDATelemetry.trackScreenView("settings") - - self.title = GDLocalizedString("settings.screen_title") - expandedSections = [] - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return Section.allCases.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - guard let sectionType = Section(rawValue: section) else { return 0 } - - if expandedSections.contains(section) { - switch sectionType { - case .general: return 6 - case .audio: return 1 - case .callouts: - return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 0 - case .streetPreview, .troubleshooting, .about, .telemetry: - return 1 - } - } - return 0 - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return expandedSections.contains(indexPath.section) ? UITableView.automaticDimension : 0 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard expandedSections.contains(indexPath.section) else { - return UITableViewCell() - } - - let identifier = SettingsViewController.cellIdentifiers[indexPath] - let cell = tableView.dequeueReusableCell(withIdentifier: identifier ?? "default", for: indexPath) - - switch Section(rawValue: indexPath.section) { - case .callouts: - configureCalloutCell(cell as! CalloutSettingsCellView, at: indexPath) - case .telemetry: - (cell as! TelemetrySettingsTableViewCell).parent = self - case .audio: - (cell as! MixAudioSettingCell).delegate = self - default: - break - } - - return cell - } - - private func configureCalloutCell(_ cell: CalloutSettingsCellView, at indexPath: IndexPath) { - cell.delegate = self - if let rowType = CalloutsRow(rawValue: indexPath.row) { - switch rowType { - case .all: - cell.type = .all - case .poi: - cell.type = .poi - case .mobility: - cell.type = .mobility - case .beacon: - cell.type = .beacon - case .shake: - cell.type = .shake - } - } - } - - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - guard let sectionType = Section(rawValue: section) else { return nil } - - switch sectionType { - case .general: return GDLocalizedString("settings.section.general") - case .audio: return GDLocalizedString("settings.audio.media_controls") - case .callouts: return GDLocalizedString("menu.manage_callouts") - case .about: return GDLocalizedString("settings.section.about") - case .streetPreview: return GDLocalizedString("preview.title") - case .troubleshooting: return GDLocalizedString("settings.section.troubleshooting") - case .telemetry: return GDLocalizedString("settings.section.telemetry") - } - } - - override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - guard let sectionType = Section(rawValue: section) else { return nil } - - - if expandedSections.contains(section) { - return SettingsViewController.sectionDescriptions[sectionType] - } - - switch sectionType { - case .audio: return GDLocalizedString("settings.audio.mix_with_others.description") - case .streetPreview: return GDLocalizedString("preview.include_unnamed_roads.subtitle") - case .telemetry: return GDLocalizedString("settings.section.telemetry.footer") - default: return nil - } - } - - override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - guard let header = view as? UITableViewHeaderFooterView else { return } - header.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTap(_:)))) - header.tag = section - - - header.textLabel?.textColor = .white - header.textLabel?.font = UIFont.boldSystemFont(ofSize: 18) - - - header.contentView.backgroundColor = UIColor(named: "HeaderBackgroundColor") - header.layer.borderColor = UIColor.clear.cgColor - header.layer.borderWidth = 0.0 - header.layer.cornerRadius = 8.0 - header.layer.masksToBounds = true - - header.contentView.layoutMargins = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) - - // ">" icon - let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) - chevronImageView.tintColor = .white - chevronImageView.translatesAutoresizingMaskIntoConstraints = false - - header.contentView.addSubview(chevronImageView) - - NSLayoutConstraint.activate([ - chevronImageView.trailingAnchor.constraint(equalTo: header.contentView.trailingAnchor, constant: -15), - chevronImageView.centerYAnchor.constraint(equalTo: header.contentView.centerYAnchor), - chevronImageView.widthAnchor.constraint(equalToConstant: 20), - chevronImageView.heightAnchor.constraint(equalToConstant: 20) - ]) - } - - @objc private func handleHeaderTap(_ gesture: UITapGestureRecognizer) { - guard let header = gesture.view as? UITableViewHeaderFooterView else { return } - let section = header.tag - - tableView.beginUpdates() - if expandedSections.contains(section) { - expandedSections.remove(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section collapsed") - } else { - expandedSections.insert(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section expanded") - } - tableView.endUpdates() - } - - } - - extension SettingsViewController: MixAudioSettingCellDelegate { - func onSettingValueChanged(_ cell: MixAudioSettingCell, settingSwitch: UISwitch) { - guard settingSwitch.isOn else { - updateSetting(true) - return - } - - let alert = UIAlertController(title: GDLocalizedString("general.alert.confirmation_title"), - message: GDLocalizedString("setting.audio.mix_with_others.confirmation"), - preferredStyle: .alert) - - let mixAction = UIAlertAction(title: GDLocalizedString("settings.audio.mix_with_others.title"), style: .default) { [weak self] (_) in - self?.updateSetting(false) - self?.focusOnCell(cell) - } - alert.addAction(mixAction) - alert.preferredAction = mixAction - - alert.addAction(UIAlertAction(title: GDLocalizedString("general.alert.cancel"), style: .cancel, handler: { [weak self] (_) in - settingSwitch.isOn = false - GDATelemetry.track("settings.mix_audio.cancel", with: ["context": "app_settings"]) - self?.focusOnCell(cell) - })) - - present(alert, animated: true) - } - - private func updateSetting(_ newValue: Bool) { - SettingsContext.shared.audioSessionMixesWithOthers = newValue - AppContext.shared.audioEngine.mixWithOthers = newValue - - GDATelemetry.track("settings.mix_audio", - with: ["value": "\(SettingsContext.shared.audioSessionMixesWithOthers)", - "context": "app_settings"]) - } - - private func focusOnCell(_ cell: MixAudioSettingCell) { - DispatchQueue.main.async { - UIAccessibility.post(notification: .layoutChanged, argument: cell) - } - } - } - - extension SettingsViewController: CalloutSettingsCellViewDelegate { - func onCalloutSettingChanged(_ type: CalloutSettingCellType) { - guard type == .all else { - return - } - - let indexPaths = SettingsViewController.collapsibleCalloutIndexPaths - - tableView.beginUpdates() - if SettingsContext.shared.automaticCalloutsEnabled && !tableView.contains(indexPaths: indexPaths) { - tableView.insertRows(at: indexPaths, with: .automatic) - } else if !SettingsContext.shared.automaticCalloutsEnabled && tableView.contains(indexPaths: indexPaths) { - tableView.deleteRows(at: indexPaths, with: .automatic) - } - tableView.endUpdates() - } - } - - extension SettingsViewController: LargeBannerContainerView { - - func setLargeBannerHeight(_ height: CGFloat) { - largeBannerContainerView.setHeight(height) - tableView.reloadData() - } - } - */ From d98946b741d8d037e714a133c0b74794f1ad2d6f Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Mon, 2 Dec 2024 22:26:23 -0500 Subject: [PATCH 6/7] changed around settings page --- .../Settings/SettingsViewController.swift | 125 ++++++------- .../GeneralSettingsViewController.swift | 167 +++++++++--------- 2 files changed, 139 insertions(+), 153 deletions(-) diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index a04fea4d..ec95b154 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -1,4 +1,3 @@ -// // SettingsViewController.swift // Soundscape // @@ -6,12 +5,6 @@ // Licensed under the MIT License. // -// -// SettingsViewController.swift -// Soundscape -// - - import UIKit import AppCenterAnalytics @@ -20,11 +13,12 @@ class SettingsViewController: BaseTableViewController { private enum Section: Int, CaseIterable { case general = 0 case audio = 1 - case callouts = 2 - case streetPreview = 3 - case troubleshooting = 4 - case about = 5 - case telemetry = 6 + case beacon = 2 // New Beacon section + case callouts = 3 + case streetPreview = 4 + case troubleshooting = 5 + case about = 6 + case telemetry = 7 } private enum CalloutsRow: Int, CaseIterable { @@ -38,13 +32,14 @@ class SettingsViewController: BaseTableViewController { private static let cellIdentifiers: [IndexPath: String] = [ IndexPath(row: 0, section: Section.general.rawValue): "languageAndRegion", IndexPath(row: 1, section: Section.general.rawValue): "voice", - IndexPath(row: 2, section: Section.general.rawValue): "beaconSettings", - IndexPath(row: 3, section: Section.general.rawValue): "volumeSettings", - IndexPath(row: 4, section: Section.general.rawValue): "manageDevices", - IndexPath(row: 5, section: Section.general.rawValue): "siriShortcuts", + IndexPath(row: 2, section: Section.general.rawValue): "volumeSettings", + IndexPath(row: 3, section: Section.general.rawValue): "manageDevices", + IndexPath(row: 4, section: Section.general.rawValue): "siriShortcuts", IndexPath(row: 0, section: Section.audio.rawValue): "mixAudio", + IndexPath(row: 0, section: Section.beacon.rawValue): "beaconSettings", // Added for beacon section + IndexPath(row: CalloutsRow.all.rawValue, section: Section.callouts.rawValue): "allCallouts", IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue): "poiCallouts", IndexPath(row: CalloutsRow.mobility.rawValue, section: Section.callouts.rawValue): "mobilityCallouts", @@ -72,13 +67,14 @@ class SettingsViewController: BaseTableViewController { // Section Descriptions private static let sectionDescriptions: [Section: String] = [ - .general: GDLocalizedString("settings.section.general"), - .audio: GDLocalizedString("settings.audio.media_controls"), - .callouts: GDLocalizedString("menu.manage_callouts"), - .streetPreview: GDLocalizedString("settings.section.street_preview"), - .troubleshooting: GDLocalizedString("settings.section.troubleshooting"), - .about: GDLocalizedString("settings.section.about"), - .telemetry: GDLocalizedString("settings.section.telemetry") + .general: "General settings for the app.", + .audio: "Control how audio interacts with other media.", + .beacon: "Settings for beacon management.", // Description for beacon section + .callouts: "Manage the callouts that help navigate.", + .streetPreview: "Settings for including unnamed roads.", + .troubleshooting: "Options for troubleshooting the app.", + .about: "Information about the app.", + .telemetry: "Manage data collection and privacy." ] // MARK: View Life Cycle @@ -91,7 +87,7 @@ class SettingsViewController: BaseTableViewController { GDATelemetry.trackScreenView("settings") self.title = GDLocalizedString("settings.screen_title") - expandedSections = [] + expandedSections = [] // Initialize or reset expanded sections } override func numberOfSections(in tableView: UITableView) -> Int { @@ -101,17 +97,15 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let sectionType = Section(rawValue: section) else { return 0 } - if expandedSections.contains(section) { - switch sectionType { - case .general: return 6 - case .audio: return 1 - case .callouts: - return SettingsContext.shared.automaticCalloutsEnabled ? 5 : 0 - case .streetPreview, .troubleshooting, .about, .telemetry: - return 1 - } + switch sectionType { + case .general: return expandedSections.contains(section) ? 5 : 0 + case .audio: return expandedSections.contains(section) ? 1 : 0 + case .beacon: return expandedSections.contains(section) ? 1 : 0 // Logic for beacon section + case .callouts: + return SettingsContext.shared.automaticCalloutsEnabled && expandedSections.contains(section) ? 5 : 0 + case .streetPreview, .troubleshooting, .about, .telemetry: + return expandedSections.contains(section) ? 1 : 0 } - return 0 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { @@ -120,7 +114,7 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard expandedSections.contains(indexPath.section) else { - return UITableViewCell() + return UITableViewCell() // Return an empty cell if the section is not expanded } let identifier = SettingsViewController.cellIdentifiers[indexPath] @@ -145,7 +139,7 @@ class SettingsViewController: BaseTableViewController { if let rowType = CalloutsRow(rawValue: indexPath.row) { switch rowType { case .all: - cell.type = .all + cell.type = .all // Ensure this matches with your CalloutSettingCellType case .poi: cell.type = .poi case .mobility: @@ -158,13 +152,13 @@ class SettingsViewController: BaseTableViewController { } } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { guard let sectionType = Section(rawValue: section) else { return nil } switch sectionType { case .general: return GDLocalizedString("settings.section.general") case .audio: return GDLocalizedString("settings.audio.media_controls") + case .beacon: return GDLocalizedString("Beacon") // Title for beacon section case .callouts: return GDLocalizedString("menu.manage_callouts") case .about: return GDLocalizedString("settings.section.about") case .streetPreview: return GDLocalizedString("preview.title") @@ -176,7 +170,6 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { guard let sectionType = Section(rawValue: section) else { return nil } - if expandedSections.contains(section) { return SettingsViewController.sectionDescriptions[sectionType] } @@ -194,24 +187,13 @@ class SettingsViewController: BaseTableViewController { header.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTap(_:)))) header.tag = section - header.textLabel?.textColor = .white header.textLabel?.font = UIFont.boldSystemFont(ofSize: 18) - - header.contentView.backgroundColor = UIColor(named: "HeaderBackgroundColor") - header.layer.borderColor = UIColor.clear.cgColor - header.layer.borderWidth = 0.0 - header.layer.cornerRadius = 8.0 - header.layer.masksToBounds = true - - header.contentView.layoutMargins = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15) - // ">" icon let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) chevronImageView.tintColor = .white chevronImageView.translatesAutoresizingMaskIntoConstraints = false - header.contentView.addSubview(chevronImageView) NSLayoutConstraint.activate([ @@ -223,32 +205,29 @@ class SettingsViewController: BaseTableViewController { } @objc private func handleHeaderTap(_ gesture: UITapGestureRecognizer) { - guard let header = gesture.view as? UITableViewHeaderFooterView else { return } - let section = header.tag - - if section == Section.general.rawValue { - // Navigate to GeneralSettingsViewController - let generalSettingsVC = GeneralSettingsViewController() - self.navigationController?.pushViewController(generalSettingsVC, animated: true) - } else if section == Section.callouts.rawValue { - // Navigate to CalloutsSettingsViewController - let calloutsSettingsVC = CalloutSettingsViewController() - self.navigationController?.pushViewController(calloutsSettingsVC, animated: true) - } else { - // Toggle expand/collapse for other sections - tableView.beginUpdates() - if expandedSections.contains(section) { - expandedSections.remove(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section collapsed") + guard let header = gesture.view as? UITableViewHeaderFooterView else { return } + let section = header.tag + + if section == Section.callouts.rawValue { + // Navigate to CalloutsSettingsViewController + let calloutsSettingsVC = CalloutSettingsViewController() + self.navigationController?.pushViewController(calloutsSettingsVC, animated: true) } else { - expandedSections.insert(section) - tableView.reloadSections(IndexSet(integer: section), with: .automatic) - UIAccessibility.post(notification: .announcement, argument: "Section expanded") + // Toggle expand/collapse for other sections + tableView.beginUpdates() + if expandedSections.contains(section) { + expandedSections.remove(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section collapsed") + } else { + expandedSections.insert(section) + tableView.reloadSections(IndexSet(integer: section), with: .automatic) + UIAccessibility.post(notification: .announcement, argument: "Section expanded") + } + tableView.endUpdates() } - tableView.endUpdates() - } } + } extension SettingsViewController: MixAudioSettingCellDelegate { @@ -302,13 +281,11 @@ extension SettingsViewController: CalloutSettingsCellViewDelegate { let indexPaths = SettingsViewController.collapsibleCalloutIndexPaths - tableView.beginUpdates() if SettingsContext.shared.automaticCalloutsEnabled && !tableView.contains(indexPaths: indexPaths) { tableView.insertRows(at: indexPaths, with: .automatic) } else if !SettingsContext.shared.automaticCalloutsEnabled && tableView.contains(indexPaths: indexPaths) { tableView.deleteRows(at: indexPaths, with: .automatic) } - tableView.endUpdates() } } diff --git a/apps/ios/GuideDogs/GeneralSettingsViewController.swift b/apps/ios/GuideDogs/GeneralSettingsViewController.swift index 2952e252..1b797ae8 100644 --- a/apps/ios/GuideDogs/GeneralSettingsViewController.swift +++ b/apps/ios/GuideDogs/GeneralSettingsViewController.swift @@ -1,34 +1,59 @@ +// +// GeneralSettingsViewController.swift +// Soundscape +// +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// + import UIKit class GeneralSettingsViewController: UITableViewController { - + private enum GeneralRow: Int, CaseIterable { case languageAndRegion = 0 - case voice - case beaconSettings - case volumeSettings - case manageDevices - case siriShortcuts + case voice = 1 + case beaconSettings = 2 + case volumeSettings = 3 + case manageDevices = 4 + case siriShortcuts = 5 } - - // Row labels for each setting option in the General section - private let generalItems = [ - "Language & Region", - "Voice", - "Beacon Settings", - "Volume Settings", - "Manage Devices", - "Siri Shortcuts" + + private static let cellIdentifiers: [GeneralRow: String] = [ + .languageAndRegion: "languageAndRegion", + .voice: "voice", + .beaconSettings: "beaconSettings", + .volumeSettings: "volumeSettings", + .manageDevices: "manageDevices", + .siriShortcuts: "siriShortcuts" ] - + + + private static let rowDescriptions: [GeneralRow: String] = [ + .languageAndRegion: GDLocalizedString("settings.general.language_and_region"), + .voice: GDLocalizedString("settings.general.voice"), + .beaconSettings: GDLocalizedString("settings.general.beacon_settings"), + .volumeSettings: GDLocalizedString("settings.general.volume_settings"), + .manageDevices: GDLocalizedString("settings.general.manage_devices"), + .siriShortcuts: GDLocalizedString("settings.general.siri_shortcuts") + ] + override func viewDidLoad() { super.viewDidLoad() self.title = GDLocalizedString("settings.section.general") - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "GeneralCell") + + // Register the default UITableViewCell class for reuse + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "languageAndRegion") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "voice") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "beaconSettings") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "volumeSettings") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "manageDevices") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "siriShortcuts") } + override func numberOfSections(in tableView: UITableView) -> Int { - return 1 // Only one section for general items + return 1 // Single section for General Settings } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -36,88 +61,72 @@ class GeneralSettingsViewController: UITableViewController { } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "GeneralCell", for: indexPath) - - if let rowType = GeneralRow(rawValue: indexPath.row) { - cell.textLabel?.text = generalItems[indexPath.row] - - // Customize cells if needed (e.g., add switches or controls) - switch rowType { - case .languageAndRegion, .voice, .beaconSettings, .manageDevices, .siriShortcuts: - cell.accessoryType = .disclosureIndicator // These would open additional settings screens - case .volumeSettings: - // Example: Adding a slider for volume control directly in the cell - let volumeSlider = UISlider() - volumeSlider.value = 0.5 // Default value; adjust based on saved settings - volumeSlider.addTarget(self, action: #selector(volumeSliderChanged(_:)), for: .valueChanged) - cell.accessoryView = volumeSlider - } + guard let row = GeneralRow(rawValue: indexPath.row) else { + fatalError("Unexpected row in General Settings") } + let cellIdentifier = GeneralSettingsViewController.cellIdentifiers[row] ?? "default" + let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) + cell.textLabel?.text = GeneralSettingsViewController.rowDescriptions[row] + cell.accessoryType = .disclosureIndicator return cell } - - // Handle row selection + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - if let rowType = GeneralRow(rawValue: indexPath.row) { - switch rowType { - case .languageAndRegion: - openLanguageAndRegionSettings() - case .voice: - openVoiceSettings() - case .beaconSettings: - openBeaconSettings() - case .manageDevices: - openManageDevicesSettings() - case .siriShortcuts: - openSiriShortcutsSettings() - case .volumeSettings: - // Volume setting already has a slider; no further action required here - break - } + guard let row = GeneralRow(rawValue: indexPath.row) else { return } + navigateToDetail(for: row) + } + + private func navigateToDetail(for row: GeneralRow) { + switch row { + case .languageAndRegion: + navigateToLanguageAndRegion() + case .voice: + navigateToVoiceSettings() + case .beaconSettings: + navigateToBeaconSettings() + case .volumeSettings: + navigateToVolumeSettings() + case .manageDevices: + navigateToManageDevices() + case .siriShortcuts: + navigateToSiriShortcuts() } } - // Helper methods for each setting action - private func openLanguageAndRegionSettings() { - // Logic to load or modify Language & Region settings. - showActionAlert(title: "Language & Region", message: "This opens Language & Region settings.") + private func navigateToLanguageAndRegion() { + // Push detailed Language and Region screen + GDLogActionInfo("Opened 'Language and Region Settings'") } - private func openVoiceSettings() { - // Logic to load or modify Voice settings. - showActionAlert(title: "Voice", message: "This opens Voice settings.") + private func navigateToVoiceSettings() { + // Push detailed Voice Settings screen + GDLogActionInfo("Opened 'Voice Settings'") } - private func openBeaconSettings() { - // Logic to load or modify Beacon settings. - showActionAlert(title: "Beacon Settings", message: "This opens Beacon Settings.") + private func navigateToBeaconSettings() { + // Push detailed Beacon Settings screen + GDLogActionInfo("Opened 'Beacon Settings'") } - private func openManageDevicesSettings() { - // Logic to load or modify Manage Devices settings. - showActionAlert(title: "Manage Devices", message: "This opens Manage Devices settings.") + private func navigateToVolumeSettings() { + // Push detailed Volume Settings screen + GDLogActionInfo("Opened 'Volume Settings'") } - private func openSiriShortcutsSettings() { - // Logic to load or modify Siri Shortcuts settings. - showActionAlert(title: "Siri Shortcuts", message: "This opens Siri Shortcuts settings.") + private func navigateToManageDevices() { + // Push detailed Manage Devices screen + GDLogActionInfo("Opened 'Manage Devices'") } - // Volume slider action - @objc private func volumeSliderChanged(_ sender: UISlider) { - let volume = sender.value - // Logic to save or update volume settings in SettingsContext or similar - print("Volume changed to: \(volume)") + private func navigateToSiriShortcuts() { + // Push detailed Siri Shortcuts screen + GDLogActionInfo("Opened 'Siri Shortcuts'") } - // Helper method to show action alerts as placeholders - private func showActionAlert(title: String, message: String) { - let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - present(alertController, animated: true, completion: nil) + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return GDLocalizedString("settings.section.general.description") } } - From 741d05ef251f23358b8f60c6a77f10b75be66f23 Mon Sep 17 00:00:00 2001 From: MBtheOtaku Date: Tue, 3 Dec 2024 16:54:12 -0500 Subject: [PATCH 7/7] section name change --- .../en-US.lproj/Localizable.strings | 2 +- .../Settings/SettingsViewController.swift | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings index 536c18aa..be0e47bb 100644 --- a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings +++ b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings @@ -743,7 +743,7 @@ //------------------------------------------------------------------------------ /* Street Preview, View title, Street Preview is functionality that allows the user to select any location in the world to preview the area at street level in order to familiarise and build a mental map of the space. {NumberedPlaceholder="Soundscape Street Preview"} */ -"preview.title" = "Soundscape Street Preview"; +"preview.title" = "Street Preview"; /* Text displayed when the current location is not known while previewing */ "preview.current_intersection_unknown.label" = "Current Location Unknown"; diff --git a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift index ec95b154..7a631fc7 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift @@ -13,7 +13,7 @@ class SettingsViewController: BaseTableViewController { private enum Section: Int, CaseIterable { case general = 0 case audio = 1 - case beacon = 2 // New Beacon section + case beacon = 2 case callouts = 3 case streetPreview = 4 case troubleshooting = 5 @@ -38,7 +38,8 @@ class SettingsViewController: BaseTableViewController { IndexPath(row: 0, section: Section.audio.rawValue): "mixAudio", - IndexPath(row: 0, section: Section.beacon.rawValue): "beaconSettings", // Added for beacon section + IndexPath(row: 0, section: Section.beacon.rawValue): "beaconSettings", + IndexPath(row: CalloutsRow.all.rawValue, section: Section.callouts.rawValue): "allCallouts", IndexPath(row: CalloutsRow.poi.rawValue, section: Section.callouts.rawValue): "poiCallouts", @@ -69,7 +70,7 @@ class SettingsViewController: BaseTableViewController { private static let sectionDescriptions: [Section: String] = [ .general: "General settings for the app.", .audio: "Control how audio interacts with other media.", - .beacon: "Settings for beacon management.", // Description for beacon section + .beacon: "Settings for beacon management.", .callouts: "Manage the callouts that help navigate.", .streetPreview: "Settings for including unnamed roads.", .troubleshooting: "Options for troubleshooting the app.", @@ -87,7 +88,7 @@ class SettingsViewController: BaseTableViewController { GDATelemetry.trackScreenView("settings") self.title = GDLocalizedString("settings.screen_title") - expandedSections = [] // Initialize or reset expanded sections + expandedSections = [] } override func numberOfSections(in tableView: UITableView) -> Int { @@ -96,11 +97,12 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let sectionType = Section(rawValue: section) else { return 0 } - + + switch sectionType { case .general: return expandedSections.contains(section) ? 5 : 0 case .audio: return expandedSections.contains(section) ? 1 : 0 - case .beacon: return expandedSections.contains(section) ? 1 : 0 // Logic for beacon section + case .beacon: return expandedSections.contains(section) ? 1 : 0 case .callouts: return SettingsContext.shared.automaticCalloutsEnabled && expandedSections.contains(section) ? 5 : 0 case .streetPreview, .troubleshooting, .about, .telemetry: @@ -114,7 +116,7 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard expandedSections.contains(indexPath.section) else { - return UITableViewCell() // Return an empty cell if the section is not expanded + return UITableViewCell() } let identifier = SettingsViewController.cellIdentifiers[indexPath] @@ -139,7 +141,7 @@ class SettingsViewController: BaseTableViewController { if let rowType = CalloutsRow(rawValue: indexPath.row) { switch rowType { case .all: - cell.type = .all // Ensure this matches with your CalloutSettingCellType + cell.type = .all case .poi: cell.type = .poi case .mobility: @@ -158,7 +160,7 @@ class SettingsViewController: BaseTableViewController { switch sectionType { case .general: return GDLocalizedString("settings.section.general") case .audio: return GDLocalizedString("settings.audio.media_controls") - case .beacon: return GDLocalizedString("Beacon") // Title for beacon section + case .beacon: return GDLocalizedString("Beacon") case .callouts: return GDLocalizedString("menu.manage_callouts") case .about: return GDLocalizedString("settings.section.about") case .streetPreview: return GDLocalizedString("preview.title") @@ -184,13 +186,19 @@ class SettingsViewController: BaseTableViewController { override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { guard let header = view as? UITableViewHeaderFooterView else { return } + header.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHeaderTap(_:)))) header.tag = section header.textLabel?.textColor = .white header.textLabel?.font = UIFont.boldSystemFont(ofSize: 18) header.contentView.backgroundColor = UIColor(named: "HeaderBackgroundColor") - + + // Accessibility + header.accessibilityTraits = .header + header.accessibilityLabel = SettingsViewController.sectionDescriptions[Section(rawValue: section)!] + header.accessibilityHint = "Double tap to expand or collapse this section" + let chevronImageView = UIImageView(image: UIImage(systemName: "chevron.right")) chevronImageView.tintColor = .white chevronImageView.translatesAutoresizingMaskIntoConstraints = false @@ -213,7 +221,6 @@ class SettingsViewController: BaseTableViewController { let calloutsSettingsVC = CalloutSettingsViewController() self.navigationController?.pushViewController(calloutsSettingsVC, animated: true) } else { - // Toggle expand/collapse for other sections tableView.beginUpdates() if expandedSections.contains(section) { expandedSections.remove(section)