Skip to content

Commit

Permalink
Refactor FXIOS-9331 [Theming] Fix ThemeManager issues (#20667)
Browse files Browse the repository at this point in the history
* Change some names and organize

* Streamline brightness functions

* Minor corrections

* Fix automatic brightness bug

* Begone windowThemeState!

* Marvel setup, Phase 9000

* I dreamed some improvements, lord forgive me

* Minor cleanup and re-org

* Fix remaining bug

* Testing, last, but most important?

* Fix le typo
  • Loading branch information
adudenamedruby committed Jun 17, 2024
1 parent dd68270 commit bb145bf
Show file tree
Hide file tree
Showing 127 changed files with 394 additions and 475 deletions.
174 changes: 60 additions & 114 deletions BrowserKit/Sources/Common/Theming/DefaultThemeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public final class DefaultThemeManager: ThemeManager, Notifiable {

// MARK: - Variables

private var windowThemeState: [WindowUUID: Theme] = [:]
private var windows: [WindowUUID: UIWindow] = [:]
private var allWindowUUIDs: [WindowUUID] { return Array(windows.keys) }
public var notificationCenter: NotificationProtocol
Expand All @@ -44,10 +43,6 @@ public final class DefaultThemeManager: ThemeManager, Notifiable {
return userDefaults.bool(forKey: ThemeKeys.NightMode.isOn)
}

private func privateModeIsOn(for window: WindowUUID) -> Bool {
return getPrivateThemeIsOn(for: window)
}

public var systemThemeIsOn: Bool {
return userDefaults.bool(forKey: ThemeKeys.systemThemeIsOn)
}
Expand Down Expand Up @@ -83,132 +78,102 @@ public final class DefaultThemeManager: ThemeManager, Notifiable {
UIApplication.didBecomeActiveNotification])
}

// MARK: - ThemeManager

public func windowNonspecificTheme() -> Theme {
switch getNormalSavedTheme() {
case .dark, .nightMode, .privateMode: return DarkTheme()
case .light: return LightTheme()
}
}

public func windowDidClose(uuid: WindowUUID) {
windows.removeValue(forKey: uuid)
windowThemeState.removeValue(forKey: uuid)
}

public func setWindow(_ window: UIWindow, for uuid: WindowUUID) {
windows[uuid] = window
updateSavedTheme(to: getNormalSavedTheme())
updateCurrentTheme(to: fetchSavedThemeType(for: uuid), for: uuid)
}

public func currentTheme(for window: WindowUUID?) -> Theme {
// MARK: - Themeing general functions
public func getCurrentTheme(for window: WindowUUID?) -> Theme {
guard let window else {
assertionFailure("Attempt to get the theme for a nil window UUID.")
return DarkTheme()
}

return windowThemeState[window] ?? DarkTheme()
return getThemeFrom(type: determineThemeType(for: window))
}

public func changeCurrentTheme(_ newTheme: ThemeType, for window: WindowUUID) {
guard currentTheme(for: window).type != newTheme else { return }

updateSavedTheme(to: newTheme)

// Although we may have only explicitly changed the state on one specific window,
// we want to be sure we update all windows in case the Light/Dark theme changed.
public func applyThemeUpdatesToWindows() {
allWindowUUIDs.forEach {
updateCurrentTheme(to: fetchSavedThemeType(for: $0), for: $0, notify: false)
applyThemeChanges(for: $0, using: determineThemeType(for: $0))
}

// After updating all windows, notify (once). We send the UUID of the window for
// which the change originated though more than 1 window may ultimately update its UI.
notifyCurrentThemeDidChange(for: window)
}

public func reloadTheme(for window: WindowUUID) {
updateCurrentTheme(to: fetchSavedThemeType(for: window), for: window)
// MARK: - Manual theme functions
public func setManualTheme(to newTheme: ThemeType) {
updateSavedTheme(to: newTheme)
applyThemeUpdatesToWindows()
}

public func systemThemeChanged() {
allWindowUUIDs.forEach { uuid in
// Ignore if:
// the system theme is off
// OR night mode is on
// OR private mode is on
guard systemThemeIsOn,
!nightModeIsOn,
!privateModeIsOn(for: uuid)
else { return }

changeCurrentTheme(getSystemThemeType(), for: uuid)
}
public func getUserManualTheme() -> ThemeType {
guard let savedThemeDescription = userDefaults.string(forKey: ThemeKeys.themeName),
let savedTheme = ThemeType(rawValue: savedThemeDescription)
else { return getThemeTypeBasedOnSystem() }

return savedTheme
}

// MARK: - System theme functions
public func setSystemTheme(isOn: Bool) {
userDefaults.set(isOn, forKey: ThemeKeys.systemThemeIsOn)
applyThemeUpdatesToWindows()
}

if isOn {
systemThemeChanged()
} else if automaticBrightnessIsOn {
updateThemeBasedOnBrightness()
} else {
allWindowUUIDs.forEach { reloadTheme(for: $0) }
}
private func getThemeTypeBasedOnSystem() -> ThemeType {
return UIScreen.main.traitCollection.userInterfaceStyle == .dark ? ThemeType.dark : ThemeType.light
}

// MARK: - Private theme functions
public func setPrivateTheme(isOn: Bool, for window: WindowUUID) {
let currentSetting = getPrivateThemeIsOn(for: window)
guard currentSetting != isOn else { return }
guard getPrivateThemeIsOn(for: window) != isOn else { return }

var settings: KeyedPrivateModeFlags
= userDefaults.object(forKey: ThemeKeys.PrivateMode.byWindowUUID) as? KeyedPrivateModeFlags ?? [:]

settings[window.uuidString] = NSNumber(value: isOn)
userDefaults.set(settings, forKey: ThemeKeys.PrivateMode.byWindowUUID)

updateCurrentTheme(to: fetchSavedThemeType(for: window), for: window)
applyThemeChanges(for: window, using: determineThemeType(for: window))
}

public func getPrivateThemeIsOn(for window: WindowUUID) -> Bool {
let settings = userDefaults.object(forKey: ThemeKeys.PrivateMode.byWindowUUID) as? KeyedPrivateModeFlags
if settings == nil {
migrateSingleWindowPrivateDefaultsToMultiWindow(for: window)
}
if settings == nil { migrateSingleWindowPrivateDefaultsToMultiWindow(for: window) }

let boxedBool = settings?[window.uuidString] as? NSNumber
return boxedBool?.boolValue ?? false
}

// MARK: - Automatic brightness theme functions
public func setAutomaticBrightness(isOn: Bool) {
guard automaticBrightnessIsOn != isOn else { return }

userDefaults.set(isOn, forKey: ThemeKeys.AutomaticBrightness.isOn)
brightnessChanged()
applyThemeUpdatesToWindows()
}

public func setAutomaticBrightnessValue(_ value: Float) {
userDefaults.set(value, forKey: ThemeKeys.AutomaticBrightness.thresholdValue)
brightnessChanged()
applyThemeUpdatesToWindows()
}

private func getThemeTypeBasedOnBrightness() -> ThemeType {
return Float(UIScreen.main.brightness) < automaticBrightnessValue ? .dark : .light
}

public func brightnessChanged() {
if automaticBrightnessIsOn {
updateThemeBasedOnBrightness()
// MARK: - Window specific functions
public func windowNonspecificTheme() -> Theme {
switch getUserManualTheme() {
case .dark, .nightMode, .privateMode: return DarkTheme()
case .light: return LightTheme()
}
}

public func getNormalSavedTheme() -> ThemeType {
guard let savedThemeDescription = userDefaults.string(forKey: ThemeKeys.themeName),
let savedTheme = ThemeType(rawValue: savedThemeDescription)
else { return getSystemThemeType() }
public func windowDidClose(uuid: WindowUUID) {
windows.removeValue(forKey: uuid)
}

return savedTheme
public func setWindow(_ window: UIWindow, for uuid: WindowUUID) {
windows[uuid] = window
updateSavedTheme(to: getUserManualTheme())
applyThemeChanges(for: uuid, using: determineThemeType(for: uuid))
}

// MARK: - Private methods
// MARK: - Private helper methods

private func migrateSingleWindowPrivateDefaultsToMultiWindow(for window: WindowUUID) {
// Migrate old private setting to our window-based settings
Expand All @@ -218,42 +183,36 @@ public final class DefaultThemeManager: ThemeManager, Notifiable {
}

private func updateSavedTheme(to newTheme: ThemeType) {
guard !newTheme.isOverridingThemeType() else { return }
guard !systemThemeIsOn else { return }
userDefaults.set(newTheme.rawValue, forKey: ThemeKeys.themeName)
}

private func updateCurrentTheme(to newTheme: ThemeType, for window: WindowUUID, notify: Bool = true) {
windowThemeState[window] = newThemeForType(newTheme)

private func applyThemeChanges(for window: WindowUUID, using newTheme: ThemeType) {
// Overwrite the user interface style on the window attached to our scene
// once we have multiple scenes we need to update all of them
let style = self.currentTheme(for: window).type.getInterfaceStyle()
let style = self.getCurrentTheme(for: window).type.getInterfaceStyle()
self.windows[window]?.overrideUserInterfaceStyle = style
if notify {
notifyCurrentThemeDidChange(for: window)
}
notifyCurrentThemeDidChange(for: window)
}

private func notifyCurrentThemeDidChange(for window: WindowUUID) {
mainQueue.ensureMainThread { [weak self] in
self?.notificationCenter.post(name: .ThemeDidChange, withUserInfo: window.userInfo)
self?.notificationCenter.post(
name: .ThemeDidChange,
withUserInfo: window.userInfo
)
}
}

private func fetchSavedThemeType(for window: WindowUUID) -> ThemeType {
if privateModeIsOn(for: window) { return .privateMode }
private func determineThemeType(for window: WindowUUID) -> ThemeType {
if getPrivateThemeIsOn(for: window) { return .privateMode }
if nightModeIsOn { return .nightMode }
if systemThemeIsOn { return getSystemThemeType() }
if systemThemeIsOn { return getThemeTypeBasedOnSystem() }
if automaticBrightnessIsOn { return getThemeTypeBasedOnBrightness() }

return getNormalSavedTheme()
return getUserManualTheme()
}

private func getSystemThemeType() -> ThemeType {
return UIScreen.main.traitCollection.userInterfaceStyle == .dark ? ThemeType.dark : ThemeType.light
}

private func newThemeForType(_ type: ThemeType) -> Theme {
private func getThemeFrom(type: ThemeType) -> Theme {
switch type {
case .light:
return LightTheme()
Expand All @@ -266,26 +225,13 @@ public final class DefaultThemeManager: ThemeManager, Notifiable {
}
}

private func updateThemeBasedOnBrightness() {
allWindowUUIDs.forEach { uuid in
let currentValue = Float(UIScreen.main.brightness)

if currentValue < automaticBrightnessValue {
changeCurrentTheme(.dark, for: uuid)
} else {
changeCurrentTheme(.light, for: uuid)
}
}
}

// MARK: - Notifiable

public func handleNotifications(_ notification: Notification) {
switch notification.name {
case UIScreen.brightnessDidChangeNotification:
brightnessChanged()
case UIApplication.didBecomeActiveNotification:
self.systemThemeChanged()
case UIScreen.brightnessDidChangeNotification,
UIApplication.didBecomeActiveNotification:
applyThemeUpdatesToWindows()
default:
return
}
Expand Down
10 changes: 4 additions & 6 deletions BrowserKit/Sources/Common/Theming/ThemeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ import UIKit

public protocol ThemeManager {
// Current theme
func currentTheme(for window: WindowUUID?) -> Theme
func getCurrentTheme(for window: WindowUUID?) -> Theme

// System theme and brightness settings
var systemThemeIsOn: Bool { get }
var automaticBrightnessIsOn: Bool { get }
var automaticBrightnessValue: Float { get }
func systemThemeChanged()
func setSystemTheme(isOn: Bool)
func setManualTheme(to newTheme: ThemeType)
func getUserManualTheme() -> ThemeType
func setAutomaticBrightness(isOn: Bool)
func setAutomaticBrightnessValue(_ value: Float)
func brightnessChanged()
func getNormalSavedTheme() -> ThemeType

// Window management and window-specific themeing
func changeCurrentTheme(_ newTheme: ThemeType, for window: WindowUUID)
func applyThemeUpdatesToWindows()
func setPrivateTheme(isOn: Bool, for window: WindowUUID)
func getPrivateThemeIsOn(for window: WindowUUID) -> Bool
func reloadTheme(for window: WindowUUID)
func setWindow(_ window: UIWindow, for uuid: WindowUUID)
func windowDidClose(uuid: WindowUUID)

Expand Down
10 changes: 0 additions & 10 deletions BrowserKit/Sources/Common/Theming/ThemeType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,4 @@ public enum ThemeType: String {
case .light: UIBlurEffect.Style.extraLight
}
}

/// An overriding theme is a type of theme that overrides whatever theme
/// the user currently has selected, because they would be in a special
/// theming case, like private mode.
public func isOverridingThemeType() -> Bool {
switch self {
case .nightMode, .privateMode: return true
case .dark, .light: return false
}
}
}
2 changes: 1 addition & 1 deletion BrowserKit/Sources/Common/Theming/Themeable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ extension Themeable {
public func updateThemeApplicableSubviews(_ view: UIView, for window: WindowUUID?) {
guard let uuid = (view as? ThemeUUIDIdentifiable)?.currentWindowUUID ?? window else { return }
assert(uuid != .unavailable, "Theme applicable view has `unavailable` window UUID. Unexpected.")
let theme = themeManager.currentTheme(for: uuid)
let theme = themeManager.getCurrentTheme(for: uuid)
let themeViews = getAllSubviews(for: view, ofType: ThemeApplicable.self)
themeViews.forEach { $0.applyTheme(theme: theme) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public class BottomSheetViewController: UIViewController,

public func applyTheme() {
guard let uuid = (self.view as? ThemeUUIDIdentifiable)?.currentWindowUUID else { return }
contentView.backgroundColor = themeManager.currentTheme(for: uuid).colors.layer1
contentView.backgroundColor = themeManager.getCurrentTheme(for: uuid).colors.layer1
sheetView.layer.shadowOpacity = viewModel.shadowOpacity

if useDimmedBackground {
Expand Down
2 changes: 1 addition & 1 deletion firefox-ios/Client/AccessoryViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class AccessoryViewProvider: UIView, Themeable, InjectedThemeUUIDIdentifiable {
}

func applyTheme() {
let theme = themeManager.currentTheme(for: windowUUID)
let theme = themeManager.getCurrentTheme(for: windowUUID)

backgroundColor = theme.colors.layer5
[previousButton, nextButton, doneButton].forEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class CredentialAutofillCoordinator: BaseCoordinator {
// MARK: - Methods

private func currentTheme() -> Theme {
return themeManager.currentTheme(for: windowUUID)
return themeManager.getCurrentTheme(for: windowUUID)
}

func showCreditCardAutofill(creditCard: CreditCard?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct SceneSetupHelper {
// Setting the initial theme correctly as we don't have a window attached yet to let ThemeManager set it
let themeManager: ThemeManager = AppContainer.shared.resolve()
themeManager.setWindow(window, for: windowUUID)
window.overrideUserInterfaceStyle = themeManager.currentTheme(for: windowUUID).type.getInterfaceStyle()
window.overrideUserInterfaceStyle = themeManager.getCurrentTheme(for: windowUUID).type.getInterfaceStyle()

return window
}
Expand Down
2 changes: 1 addition & 1 deletion firefox-ios/Client/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class SettingsCoordinator: BaseCoordinator,
let viewModel = WallpaperSettingsViewModel(
wallpaperManager: wallpaperManager,
tabManager: tabManager,
theme: themeManager.currentTheme(for: windowUUID)
theme: themeManager.getCurrentTheme(for: windowUUID)
)
let wallpaperVC = WallpaperSettingsViewController(viewModel: viewModel, windowUUID: windowUUID)
wallpaperVC.settingsDelegate = self
Expand Down
Loading

0 comments on commit bb145bf

Please sign in to comment.