Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions SwiftLeeds.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,16 @@
AEDC22532898281300746247 /* MyConferenceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEDC22522898281300746247 /* MyConferenceViewModel.swift */; };
AEDC22552898288F00746247 /* Schedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEDC22542898288F00746247 /* Schedule.swift */; };
AEDC2257289C65D500746247 /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEDC2256289C65D500746247 /* Calendar.swift */; };
E3569AEE2E5A1D0200BC9556 /* ShimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AED2E5A1D0200BC9556 /* ShimmerView.swift */; };
E3569AEF2E5A1D0200BC9556 /* ShimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AED2E5A1D0200BC9556 /* ShimmerView.swift */; };
E3569AF42E5A2F1D00BC9556 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF12E5A2F1D00BC9556 /* SettingsView.swift */; };
E3569AF52E5A2F1D00BC9556 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF22E5A2F1D00BC9556 /* SettingsViewModel.swift */; };
E3569AF92E5A301D00BC9556 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF82E5A301D00BC9556 /* ThemeManager.swift */; };
E3569B002E5A55D000BC9556 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AFF2E5A55D000BC9556 /* UserDefaultsKeys.swift */; };
E3569B052E5B902B00BC9556 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AFF2E5A55D000BC9556 /* UserDefaultsKeys.swift */; };
E3569B062E5B903800BC9556 /* ShimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AED2E5A1D0200BC9556 /* ShimmerView.swift */; };
E3569B072E5B904400BC9556 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF82E5A301D00BC9556 /* ThemeManager.swift */; };
E3569B082E5B905100BC9556 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF12E5A2F1D00BC9556 /* SettingsView.swift */; };
E3569B092E5B905700BC9556 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3569AF22E5A2F1D00BC9556 /* SettingsViewModel.swift */; };
FA1F7EF7287CB71600E12F8C /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA1F7EF6287CB71600E12F8C /* HeaderView.swift */; };
FA534D8228A1909300A3BFBB /* Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA534D8128A1909300A3BFBB /* Local.swift */; };
FA534D8828A1939500A3BFBB /* LocalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA534D8728A1939500A3BFBB /* LocalViewModel.swift */; };
Expand Down Expand Up @@ -264,6 +272,10 @@
AEDC22542898288F00746247 /* Schedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Schedule.swift; sourceTree = "<group>"; };
AEDC2256289C65D500746247 /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = "<group>"; };
E3569AED2E5A1D0200BC9556 /* ShimmerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShimmerView.swift; sourceTree = "<group>"; };
E3569AF12E5A2F1D00BC9556 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
E3569AF22E5A2F1D00BC9556 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
E3569AF82E5A301D00BC9556 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
E3569AFF2E5A55D000BC9556 /* UserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsKeys.swift; sourceTree = "<group>"; };
FA1F7EF6287CB71600E12F8C /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = "<group>"; };
FA534D8128A1909300A3BFBB /* Local.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Local.swift; sourceTree = "<group>"; };
FA534D8728A1939500A3BFBB /* LocalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -487,6 +499,7 @@
AEDC0DAF28675D060078A153 /* My Conference */,
0B4B1A492A4858DC00ED7EA9 /* Sponsors */,
AEDC0DAC286759570078A153 /* Tab */,
E3569AF32E5A2F1D00BC9556 /* Settings */,
);
path = Views;
sourceTree = "<group>";
Expand All @@ -498,6 +511,8 @@
AECB29832741ABFD00CDC983 /* Info.plist */,
AECB295627417F9D00CDC983 /* SwiftLeedsApp.swift */,
740162D92A7053A000C2D1B3 /* AppState.swift */,
E3569AF82E5A301D00BC9556 /* ThemeManager.swift */,
E3569AFF2E5A55D000BC9556 /* UserDefaultsKeys.swift */,
);
path = App;
sourceTree = "<group>";
Expand Down Expand Up @@ -546,6 +561,15 @@
path = "My Conference";
sourceTree = "<group>";
};
E3569AF32E5A2F1D00BC9556 /* Settings */ = {
isa = PBXGroup;
children = (
E3569AF12E5A2F1D00BC9556 /* SettingsView.swift */,
E3569AF22E5A2F1D00BC9556 /* SettingsViewModel.swift */,
);
path = Settings;
sourceTree = "<group>";
};
FA57DE412875B03800911F03 /* Common */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -830,6 +854,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E3569AF42E5A2F1D00BC9556 /* SettingsView.swift in Sources */,
E3569AF52E5A2F1D00BC9556 /* SettingsViewModel.swift in Sources */,
0B4CB3E028EAF58A00246E62 /* Schedule.swift in Sources */,
0B4CB3EE28EAF5FB00246E62 /* String.swift in Sources */,
0B4CB3DF28EAF58400246E62 /* Constants.swift in Sources */,
Expand All @@ -840,6 +866,7 @@
0B4CB3ED28EAF5F200246E62 /* SwiftLeedsContainer.swift in Sources */,
0B4CB3EC28EAF5E900246E62 /* ActivityView.swift in Sources */,
0B4CB3F228EAF61600246E62 /* WebView.swift in Sources */,
E3569B052E5B902B00BC9556 /* UserDefaultsKeys.swift in Sources */,
AE1CDBF02AC05B2B00E83420 /* HttpMethod.swift in Sources */,
0B4B1A512A48FB6400ED7EA9 /* SponsorsViewModel.swift in Sources */,
0B4CB3EA28EAF5D900246E62 /* Color.swift in Sources */,
Expand All @@ -853,10 +880,10 @@
0B4CB3E928EAF5D200246E62 /* Local.swift in Sources */,
0B4CB3E228EAF59900246E62 /* Calendar.swift in Sources */,
0B910A352A48FEC100648B32 /* SponsorTileView.swift in Sources */,
E3569B062E5B903800BC9556 /* ShimmerView.swift in Sources */,
AE1CDBED2AC05B2B00E83420 /* Request.swift in Sources */,
AE9367972A9354CC00F2DB3F /* Helper.swift in Sources */,
0B4CB3E528EAF5B700246E62 /* Speaker.swift in Sources */,
E3569AEE2E5A1D0200BC9556 /* ShimmerView.swift in Sources */,
0B4CB3E828EAF5CD00246E62 /* AnnouncementCell.swift in Sources */,
0B4CB3DD28EAF57900246E62 /* MyConferenceView.swift in Sources */,
0B4CB3CA28EAF19100246E62 /* ContentView.swift in Sources */,
Expand All @@ -868,6 +895,7 @@
0B4CB3F428EAF62100246E62 /* CommonTileView.swift in Sources */,
0B4CB3C828EAF19100246E62 /* SwiftLeedsAppClipApp.swift in Sources */,
0B4CB3F328EAF61B00246E62 /* CommonTileButton.swift in Sources */,
E3569B072E5B904400BC9556 /* ThemeManager.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -901,6 +929,7 @@
AED26F7828676A9900E06064 /* AboutView.swift in Sources */,
FA57DE502875B09900911F03 /* SponsorTileView.swift in Sources */,
394653AB288BB7C800212E1C /* SpeakerView.swift in Sources */,
E3569B002E5A55D000BC9556 /* UserDefaultsKeys.swift in Sources */,
FA57DE4D2875B08600911F03 /* LinearGradient.swift in Sources */,
394653A9288BB47A00212E1C /* SectionHeader.swift in Sources */,
FA57DE552875B0C100911F03 /* SquishyButtonStyle.swift in Sources */,
Expand All @@ -917,13 +946,16 @@
AE8C1B2428BFCFC700AF7318 /* Presentation.swift in Sources */,
AE1CDBE92AC058C300E83420 /* URLSession.swift in Sources */,
2A3831122884A96600030002 /* FancyHeaderView.swift in Sources */,
E3569B082E5B905100BC9556 /* SettingsView.swift in Sources */,
FA57DE4B2875B06B00911F03 /* SwiftLeedsContainer.swift in Sources */,
74F5EF892A49CECB008D9413 /* SidebarView.swift in Sources */,
FAAB819128844EBC001159BB /* View+MeasureSize.swift in Sources */,
E3569AF92E5A301D00BC9556 /* ThemeManager.swift in Sources */,
AECB298227418DA200CDC983 /* Color.swift in Sources */,
AE1CDBE12AC0586500E83420 /* HttpMethod.swift in Sources */,
FA534D8828A1939500A3BFBB /* LocalViewModel.swift in Sources */,
AE5EFD75289E7CBF00464FE1 /* ActivityView.swift in Sources */,
E3569B092E5B905700BC9556 /* SettingsViewModel.swift in Sources */,
AED26F7028675DA000E06064 /* AnnouncementCell.swift in Sources */,
AEB06BD128CF8D2100E51967 /* WebView.swift in Sources */,
AE8C1B2828C4B36B00AF7318 /* AppDelegate+Push.swift in Sources */,
Expand Down Expand Up @@ -997,6 +1029,7 @@
0B4CB3D628EAF19100246E62 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-Space AppIcon-Olympics";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -1035,6 +1068,7 @@
0B4CB3D728EAF19100246E62 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-Space AppIcon-Olympics";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
Expand Down Expand Up @@ -1252,6 +1286,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-Space AppIcon-Olympics";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SwiftLeeds/SwiftLeeds.entitlements;
Expand Down Expand Up @@ -1288,6 +1323,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "AppIcon-Space AppIcon-Olympics";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SwiftLeeds/SwiftLeeds.entitlements;
Expand Down
2 changes: 1 addition & 1 deletion SwiftLeeds/App/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation
enum TabItems: Int {
case conference, location, about, sponsors
case conference, location, about, sponsors, settings
}

final class AppState: ObservableObject {
Expand Down
2 changes: 2 additions & 0 deletions SwiftLeeds/App/SwiftLeedsApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ struct SwiftLeedsApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

@StateObject private var appState = AppState()
@StateObject private var themeManager = ThemeManager.shared

var body: some Scene {
WindowGroup {
Tabs()
.environmentObject(appState)
.environmentObject(themeManager)
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: handleUserActivity)
}
}
Expand Down
87 changes: 87 additions & 0 deletions SwiftLeeds/App/ThemeManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// ThemeManager.swift
// SwiftLeeds
//
// Created by Adam Rush on 23/08/2025.
//

import SwiftUI
import UIKit

/// Available theme options for the application
enum ThemeOption: String, CaseIterable {
case system = "system"
case light = "light"
case dark = "dark"

/// User-friendly display name for the theme option
var displayName: String {
switch self {
case .system: return "System"
case .light: return "Light"
case .dark: return "Dark"
}
}

/// Corresponding UIUserInterfaceStyle for the theme
var userInterfaceStyle: UIUserInterfaceStyle {
switch self {
case .light: return .light
case .dark: return .dark
case .system: return .unspecified
}
}
}

/// Manages the application's theme settings and appearance
final class ThemeManager: ObservableObject {
/// Shared singleton instance
static let shared = ThemeManager()

/// Currently selected theme, automatically synced with UserDefaults
@Published var currentTheme: ThemeOption = .system

private init() {
loadTheme()
applyTheme(currentTheme)
}

/// Updates the application theme and persists the selection
/// - Parameter theme: The new theme to apply
func setTheme(_ theme: ThemeOption) {
currentTheme = theme
UserDefaults.standard.set(theme.rawValue, forKey: UserDefaultsKeys.selectedTheme)
applyTheme(theme)
}

/// Loads the saved theme preference from UserDefaults
private func loadTheme() {
if let savedTheme = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectedTheme),
let theme = ThemeOption(rawValue: savedTheme) {
currentTheme = theme
}
}

/// Applies the specified theme to the application UI
/// - Parameter theme: The theme to apply
private func applyTheme(_ theme: ThemeOption) {
DispatchQueue.main.async {
self.updateUserInterfaceStyle(theme.userInterfaceStyle)
}
}

/// Updates the user interface style for all windows in the current scene
/// - Parameter style: The UIUserInterfaceStyle to apply
private func updateUserInterfaceStyle(_ style: UIUserInterfaceStyle) {
guard let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first else {
print("Warning: Unable to find window scene for theme application")
return
}

windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = style
}
}
}
13 changes: 13 additions & 0 deletions SwiftLeeds/App/UserDefaultsKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// UserDefaultsKeys.swift
// SwiftLeeds
//
// Created by Adam Rush on 23/08/2025.
//

import Foundation

enum UserDefaultsKeys {
static let selectedTheme = "selectedTheme"
static let selectedAppIcon = "selectedAppIcon"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading